Skip to content

Commit 8125486

Browse files
committed
remove hack of storing scheme lists in estree nodes
1 parent 8e97aad commit 8125486

File tree

3 files changed

+113
-176
lines changed

3 files changed

+113
-176
lines changed

src/cse-machine/scheme-macros.ts

Lines changed: 111 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,14 @@ export function schemeEval(
144144
rest = args
145145
} else if (isImproperList(args)) {
146146
;[argsList, rest] = flattenImproperList(args)
147-
argsList.forEach(arg => {
147+
argsList.forEach((arg: any) => {
148148
if (!(arg instanceof _Symbol)) {
149149
return handleRuntimeError(
150150
context,
151151
new errors.ExceptionError(new Error('Invalid arguments for lambda!'))
152152
)
153153
}
154+
return
154155
})
155156
if (rest !== null && !(rest instanceof _Symbol)) {
156157
return handleRuntimeError(
@@ -160,13 +161,14 @@ export function schemeEval(
160161
}
161162
} else if (isList(args)) {
162163
argsList = flattenList(args) as _Symbol[]
163-
argsList.forEach(arg => {
164+
argsList.forEach((arg: any) => {
164165
if (!(arg instanceof _Symbol)) {
165166
return handleRuntimeError(
166167
context,
167168
new errors.ExceptionError(new Error('Invalid arguments for lambda!'))
168169
)
169170
}
171+
return
170172
})
171173
} else {
172174
return handleRuntimeError(
@@ -177,7 +179,7 @@ export function schemeEval(
177179

178180
// convert the args to estree pattern
179181
const params: (es.Identifier | es.RestElement)[] = argsList.map(arg =>
180-
makeDummyIdentifierNode(arg.sym)
182+
makeDummyIdentifierNode(encode(arg.sym))
181183
)
182184

183185
let body_elements = parsedList.slice(2)
@@ -189,7 +191,7 @@ export function schemeEval(
189191
if (rest !== null) {
190192
params.push({
191193
type: 'RestElement',
192-
argument: makeDummyIdentifierNode(rest.sym)
194+
argument: makeDummyIdentifierNode(encode(rest.sym))
193195
})
194196
body = arrayToList([
195197
new _Symbol('begin'),
@@ -206,11 +208,10 @@ export function schemeEval(
206208
const lambda = {
207209
type: 'ArrowFunctionExpression',
208210
params: params,
209-
body: body as any,
210-
modified: true
211+
body: convertToEvalExpression(body)
211212
}
212213

213-
control.push(lambda as unknown as es.ArrowFunctionExpression)
214+
control.push(lambda as es.ArrowFunctionExpression)
214215
return
215216

216217
case 'define':
@@ -269,10 +270,9 @@ export function schemeEval(
269270
{
270271
type: 'VariableDeclarator',
271272
id: makeDummyIdentifierNode(encode(variable.sym)),
272-
init: value
273+
init: convertToEvalExpression(value)
273274
}
274-
],
275-
modified: true
275+
]
276276
}
277277

278278
control.push(definition as es.VariableDeclaration)
@@ -298,8 +298,7 @@ export function schemeEval(
298298
type: 'AssignmentExpression',
299299
operator: '=',
300300
left: makeDummyIdentifierNode(encode(set_variable.sym)),
301-
right: set_value,
302-
modified: true
301+
right: convertToEvalExpression(set_value)
303302
}
304303

305304
control.push(assignment as es.AssignmentExpression)
@@ -328,10 +327,9 @@ export function schemeEval(
328327
// estree ConditionalExpression
329328
const conditional = {
330329
type: 'ConditionalExpression',
331-
test: truthyCondition as any,
332-
consequent,
333-
alternate,
334-
modified: true
330+
test: convertToEvalExpression(truthyCondition),
331+
consequent: convertToEvalExpression(consequent),
332+
alternate: alternate ? convertToEvalExpression(alternate) : undefined
335333
}
336334

337335
control.push(conditional as es.ConditionalExpression)
@@ -398,10 +396,15 @@ export function schemeEval(
398396
// at this point, we assume that syntax-rules is verified
399397
// and parsed correctly already.
400398
const syntaxRulesList = flattenList(syntaxRules)
401-
if (!(syntaxRulesList[0] instanceof _Symbol) || syntaxRulesList[0].sym !== 'syntax-rules') {
399+
if (
400+
!(syntaxRulesList[0] instanceof _Symbol) ||
401+
syntaxRulesList[0].sym !== 'syntax-rules'
402+
) {
402403
return handleRuntimeError(
403404
context,
404-
new errors.ExceptionError(new Error('define-syntax requires a syntax-rules transformer!'))
405+
new errors.ExceptionError(
406+
new Error('define-syntax requires a syntax-rules transformer!')
407+
)
405408
)
406409
}
407410
if (syntaxRulesList.length < 3) {
@@ -441,8 +444,8 @@ export function schemeEval(
441444
const appln = {
442445
type: 'CallExpression',
443446
optional: false,
444-
callee: procedure,
445-
arguments: args
447+
callee: convertToEvalExpression(procedure) as es.Expression,
448+
arguments: args.map(convertToEvalExpression) // unfortunately, each one needs to be converted.
446449
}
447450
control.push(appln as es.CallExpression)
448451
return
@@ -510,167 +513,101 @@ export function makeDummyEvalExpression(callee: string, argument: string): es.Ca
510513
}
511514

512515
/**
513-
* Because we have passed estree nodes with list elements
514-
* to the control, if any future estree functions require
515-
* the values within the nodes to be evaluated, we use this
516-
* function to re-parse the modified estree nodes to avoid any errors.
516+
* Convert a scheme expression (that is meant to be evaluated)
517+
* into an estree expression, using eval.
518+
* this will let us avoid the "hack" of storing Scheme lists
519+
* in estree nodes.
520+
* @param expression
521+
* @returns estree expression
517522
*/
518-
export function reparseEstreeNode(node: any): es.Node {
519-
// if the node is an estree node, we recursively reparse it.
520-
if (node.type) {
521-
if (!node.modified) {
522-
return node
523-
}
524-
switch (node.type) {
525-
case 'ArrowFunctionExpression':
526-
return {
527-
type: 'ArrowFunctionExpression',
528-
params: node.params.map((param: any) => reparseEstreeNode(param) as es.Identifier | es.RestElement),
529-
body: reparseEstreeNode(node.body) as es.BlockStatement
530-
} as es.Node
531-
case 'VariableDeclaration':
532-
return {
533-
type: 'VariableDeclaration',
534-
kind: node.kind,
535-
declarations: node.declarations.map((decl: any) => reparseEstreeNode(decl) as es.VariableDeclarator)
536-
} as es.Node
537-
case 'VariableDeclarator':
538-
return {
539-
type: 'VariableDeclarator',
540-
id: reparseEstreeNode(node.id) as es.Identifier,
541-
init: reparseEstreeNode(node.init)
542-
} as es.Node
543-
case 'AssignmentExpression':
544-
return {
545-
type: 'AssignmentExpression',
546-
operator: node.operator,
547-
left: reparseEstreeNode(node.left) as es.Identifier,
548-
right: reparseEstreeNode(node.right)
549-
} as es.Node
550-
case 'ConditionalExpression':
551-
return {
552-
type: 'ConditionalExpression',
553-
test: reparseEstreeNode(node.test),
554-
consequent: reparseEstreeNode(node.consequent),
555-
alternate: reparseEstreeNode(node.alternate)
556-
} as es.Node
557-
case 'CallExpression':
558-
return {
559-
type: 'CallExpression',
560-
optional: false,
561-
callee: reparseEstreeNode(node.callee),
562-
arguments: node.arguments.map((arg: any) => reparseEstreeNode(arg))
563-
} as es.Node
564-
case 'Identifier':
565-
return {
523+
export function convertToEvalExpression(expression: SchemeControlItems): es.CallExpression {
524+
function convertToEstreeExpression(expression: SchemeControlItems): es.Expression {
525+
/*
526+
cases to consider:
527+
- list
528+
- pair/improper list
529+
- symbol
530+
- number
531+
- boolean
532+
- string
533+
*/
534+
if (isList(expression)) {
535+
// make a call expression to list
536+
// with the elements of the list as its arguments.
537+
const args = flattenList(expression).map(convertToEstreeExpression)
538+
return {
539+
type: 'CallExpression',
540+
optional: false,
541+
callee: {
566542
type: 'Identifier',
567-
name: node.name
568-
} as es.Node
569-
case 'RestElement':
570-
return {
571-
type: 'RestElement',
572-
argument: reparseEstreeNode(node.argument) as es.Identifier
573-
} as es.Node
574-
default:
575-
// no other node was touched by schemeEval.
576-
// return it as is.
577-
return node
578-
}
579-
}
580-
// if the node is not an estree node, there are several possibilities:
581-
// 1. it is a list/improper list
582-
// 2. it is a symbol
583-
// 3. it is a number
584-
// 4. it is a boolean
585-
// 5. it is a string
586-
// we need to handle each of these cases.
587-
if (isList(node)) {
588-
// if it is a list, we can be lazy and reparse the list as a
589-
// CallExpression to the list procedure- followed by a call to eval.
590-
// this will ensure that the list is evaluated.
591-
592-
// this also handles null.
593-
const items = flattenList(node)
594-
const evalledItems = items.map((item: any) => reparseEstreeNode(item))
595-
const listCall = {
596-
type: 'CallExpression',
597-
optional: false,
598-
callee: {
599-
type: 'Identifier',
600-
name: 'list'
601-
},
602-
arguments: evalledItems
603-
}
604-
return {
605-
type: 'CallExpression',
606-
optional: false,
607-
callee: {
608-
type: 'Identifier',
609-
name: encode('eval')
610-
},
611-
arguments: [listCall as es.CallExpression]
612-
}
613-
} else if (isImproperList(node)) {
614-
// we can treat the improper list as a recursive CallExpression of cons
615-
// followed by a call to eval.
616-
const pairCall = {
617-
type: 'CallExpression',
618-
optional: false,
619-
callee: {
620-
type: 'Identifier',
621-
name: 'cons'
622-
},
623-
arguments: [
624-
reparseEstreeNode(node[0]),
625-
reparseEstreeNode(node[1])
626-
]
627-
}
628-
return {
629-
type: 'CallExpression',
630-
optional: false,
631-
callee: {
632-
type: 'Identifier',
633-
name: encode('eval')
634-
},
635-
arguments: [pairCall as es.CallExpression]
636-
}
637-
} else if (node instanceof _Symbol) {
638-
// if it is a symbol, we can just return an Identifier node.
639-
return {
640-
type: 'Identifier',
641-
name: node.sym
642-
}
643-
} else if (is_number(node)) {
644-
// if it is a number, we treat it as a call to
645-
// the string->number function.
646-
return {
647-
type: 'CallExpression',
648-
optional: false,
649-
callee: {
650-
type: 'Identifier',
651-
name: encode('string->number')
652-
},
653-
arguments: [
654-
{
655-
type: 'Literal',
656-
value: node.toString()
657-
}
658-
]
659-
}
660-
} else if (typeof node === 'boolean') {
661-
return {
662-
type: 'Literal',
663-
value: node
543+
name: 'list'
544+
},
545+
arguments: args
546+
}
547+
} else if (isImproperList(expression)) {
548+
// make a call to cons
549+
// with the car and cdr as its arguments.
550+
const [car, cdr] = expression as [SchemeControlItems, SchemeControlItems]
551+
return {
552+
type: 'CallExpression',
553+
optional: false,
554+
callee: {
555+
type: 'Identifier',
556+
name: 'cons'
557+
},
558+
arguments: [convertToEstreeExpression(car), convertToEstreeExpression(cdr)]
559+
}
560+
} else if (expression instanceof _Symbol) {
561+
// make a call to string->symbol
562+
// with the symbol name as its argument.
563+
return {
564+
type: 'CallExpression',
565+
optional: false,
566+
callee: {
567+
type: 'Identifier',
568+
name: encode('string->symbol')
569+
},
570+
arguments: [
571+
{
572+
type: 'Literal',
573+
value: expression.sym
574+
}
575+
]
576+
}
577+
} else if (is_number(expression)) {
578+
// make a call to string->number
579+
// with the number toString() as its argument.
580+
return {
581+
type: 'CallExpression',
582+
optional: false,
583+
callee: {
584+
type: 'Identifier',
585+
name: encode('string->number')
586+
},
587+
arguments: [
588+
{
589+
type: 'Literal',
590+
value: (expression as any).toString()
591+
}
592+
]
593+
}
664594
}
665-
} else if (typeof node === 'string') {
595+
// if we're here, then it is a boolean or string.
596+
// just return the literal value.
666597
return {
667598
type: 'Literal',
668-
value: node
599+
value: expression as boolean | string
669600
}
670601
}
671-
// if we get to this point, just return undefined
602+
603+
// make a call expression to eval with the single expression as its component.
672604
return {
673-
type: 'Literal',
674-
value: "undefined"
605+
type: 'CallExpression',
606+
optional: false,
607+
callee: {
608+
type: 'Identifier',
609+
name: encode('eval')
610+
},
611+
arguments: [convertToEstreeExpression(expression) as es.Expression]
675612
}
676613
}

src/stdlib/scheme.prelude.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,4 @@ export const schemeFullPrelude = `
121121
(if test
122122
(begin val ...)
123123
(cond next-clauses ...)))))
124-
`
124+
`

0 commit comments

Comments
 (0)