Last active
May 8, 2022 21:03
-
-
Save lhorie/3095cf62023b74d27fc9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var readtable = { | |
"(": form, | |
" ": space, "\t": space, "\n": space, "\r": space, | |
} | |
var white = " \t\n\r" | |
var atomEnd = " \t\n\r);" | |
var formEnd = ")" | |
var escape = "\\" | |
function parse(s) { | |
s.marker = s.cursor | |
return (readtable[s.ch] || atom)(s) | |
} | |
function next(s) {s.ch = s.code.charAt(++s.cursor)} | |
function token(s) {return s.code.slice(s.marker, s.cursor)} | |
function is(set, s) {return set.indexOf(s.ch) > -1} | |
function form(s) { | |
var list = [] | |
next(s) | |
while (!is(formEnd, s)) { | |
var item = parse(s) | |
if (item) list.push(item) | |
} | |
next(s) | |
return list | |
} | |
function space(s) { | |
next(s) | |
while (is(white, s)) next(s) | |
} | |
function atom(s) { | |
do {ch(s)} while (!is(atomEnd, s) && !(s.ch in readtable)) | |
return token(s) | |
} | |
function ch(s) { | |
if (is(escape, s)) next(s) | |
next(s) | |
} | |
function p(s) { | |
return parse({cursor: 0, marker: 0, ch: "(", code: "(program " + s + ")"}) | |
} |
-
Your
unary()
function is broken for the increment/decrement expressions. The correct version is below:function unary(op, type, postfix) { return function(form) { return { type: type || "UnaryExpression" operator: op, argument: compile(form[1]), prefix: !postfix }) } }
-
Throw this into a new repo. It would make it easier for you and those of us with suggestions.
-
Idea: try adding this function and converting all your nodes to use this. It'll simplify things a little and make things look nice. (this style look familiar? 😉)
function n(type, opts) {
(opts = opts || {}).type = type
return opts
}
Here's what the result would look like (bug fix incorporated):
function n(type, opts) {
(opts = opts || {}).type = type
return opts
}
var special = {
"==": binary("=="),
"!=": binary("!="),
"===": binary("==="),
"!==": binary("!=="),
"<": binary("<"),
"<=": binary("<="),
">": binary(">"),
">=": binary(">="),
"<<": binary("<<"),
">>": binary(">>"),
">>>": binary(">>>"),
"+": binary("+"),
"-": binary("-"),
"*": binary("*"),
"/": binary("/"),
"%": binary("%"),
"|": binary("|"),
"^": binary("^"),
"&": binary("&"),
"in": binary("in"),
"instanceof": binary("instanceof"),
"&&": binary("&&", "LogicalExpression"),
"||": binary("||", "LogicalExpression"),
"=": binary("=", "AssignmentExpression"),
"+=": binary("+=", "AssignmentExpression"),
"-=": binary("-=", "AssignmentExpression"),
"*=": binary("*=", "AssignmentExpression"),
"/=": binary("/=", "AssignmentExpression"),
"%=": binary("%=", "AssignmentExpression"),
"<<=": binary("<<=", "AssignmentExpression"),
">>=": binary(">>=", "AssignmentExpression"),
">>>=": binary(">>>=", "AssignmentExpression"),
"|=": binary("|=", "AssignmentExpression"),
"^=": binary("^=", "AssignmentExpression"),
"&=": binary("&=", "AssignmentExpression"),
"-_": unary("-"),
"+_": unary("+"),
"!": unary("!"),
"~": unary("~"),
"delete": unary("delete"),
"typeof": unary("typeof"),
"void": unary("void"),
"++": unary("++", "UpdateExpression"),
"_++": unary("++", "UpdateExpression", true),
"--": unary("--", "UpdateExpression"),
"_--": unary("--", "UpdateExpression", true),
".": function(form) {
return n("MemberExpression", {
object: compile(form[1]), //expression
property: compile(form[2]), //expression
computed: true, // true = a[b], false = a.b FIXME
})
},
"?:": function(form) {
return n("ConditionalExpression", {
test: compile(form[1]), //expression
consequent: compile(form[2]), //expression
alternate: compile(form[3]), //expression
})
},
"break": function(form) {
//identifier || null
return n("BreakStatement", {label: compile(form[1])})
},
"case": function(form) {
return n("SwitchCase", {
test: compile(form[1]), //expression
consequent: form.slice(2).map(compileStatement), //statements
})
},
"continue": function(form) {
//identifier || null
return n("ContinueStatement", {label: compile(form[1])})
},
"debugger": function(form) {
return n("DebuggerStatement")
},
"do-while": function(form) {
return n("DoWhileStatement", {
body: form.slice(0, -1).map(compileStatement), //statement
test: compile(form[form.length - 1]), //expression
})
},
"for": function(form) {
return n("ForStatement", {
init: compile(form[1]), //variable declaration || expression || null
test: compile(form[2]), //expression || null
update: compile(form[3]), //expression || null
body: form.slice(4).map(compileStatement), //statement
})
},
"for-in": function(form) {
return n("ForInStatement", {
left: compile(form[1]), //variable declaration || expression
right: compile(form[2]), //expression
body: form.slice(3).map(compileStatement), //statement
})
},
"function": function(form) {
return n("FunctionExpression", {
id: compile(form[1]), //identifier
params: form[2].map(compile), //patterns
body: n("BlockStatement", {
body: form.slice(3).map(compileStatement), //statement
})
})
},
"if": function(form) {
return n("IfStatement", {
test: compile(form[1]), //expression
consequent: compileStatement(form[2]), //statement
alternate: compileStatement(form[3]), //statement || null
})
},
"new": function(form) {
return n("NewExpression", {
callee: compile(form[1]), //expression
arguments: form.slice(2).map(compile), //expressions
})
},
"return": function(form) {
//expression || null
return n("ReturnStatement", {argument: compile(form[1])})
},
"switch": function(form) {
return n("SwitchStatement", {
discriminant: compile(form[1]), //expression
cases: form.slice(2).map(compile), //switch cases
})
},
"this": function(form) {
return n("ThisExpression")
},
"throw": function(form) {
//expression
return n("ThrowStatement",{argument: compile(form[1])})
},
"try": function(form) {
//FIXME
return n("TryStatement", {
block: compile(form[1]), //block statement FIXME signature
handler: n("CatchClause", {
param: compile(form[2]), //pattern
body: compile(form[3]), //block statement
}),
finalizer: compile(form[4]), //block statement
})
},
"var": function(form) {
var declarations = []
for (var i = 1; i < form.length; i += 2) {
declarations.push(n("VariableDeclarator", {
id: compile(form[i]), //pattern
init: compile(form[i + 1]), //expression || null
}))
}
return n("VariableDeclaration", {
declarations: declarations,
kind: "var",
})
},
"while": function(form) {
return n("WhileStatement", {
test: compile(form[1]), //expression
body: compileStatement(form[2]), //statement FIXME splice
})
},
"with": function(form) {
return n("WithStatement", {
object: compile(form[1]), //expression
body: compileStatement(form[2]), //statement FIXME splice
})
},
"program": function(form) {
return n("Program", {
body: form.slice(1).map(compileStatement), //statements
})
},
"empty": function(form) {
return n("EmptyStatement")
}
"label": function(form) {
return n("LabeledStatement", {
label: compile(form[1]), //identifier
body: compileStatement(form[2]), // statement
})
},
"block": function(form) {
return n("BlockStatement", {
body: form.slice(1).map(compileStatement), //statements
})
},
"seq": function(form) {
return n("SequenceExpression", {
expressions: form.slice(1).map(compile), //expressions
})
},
"array": function(form) {
return n("ArrayExpression", {
elements: form.slice(1).map(compile), //expressions || null
})
},
"object": function(form) {
var properties = []
for (var i = 0; i < form.length; i += 2) {
properties.push(n("Property", {
key: compile(form[i]), //literal || identifier
value: compile(form[i + 1]), //expression
kind: "init" // | "get" | "set"; FIXME getter/setter
}))
}
return n("ObjectExpression", {properties: properties})
},
}
function unary(op, type, postfix) {
return function(form) {
return n(type || "UnaryExpression", {
operator: op,
argument: compile(form[1]),
prefix: !postfix
})
}
}
function binary(op, type) {
return function(form) {
return n(type || "BinaryExpression", {
operator: "+",
left: compile(form[1]),
right: compile(form[2])
})
}
}
function call(form) {
return n("CallExpression", {
callee: compile(form[1]), //expression
arguments: form.slice(2).map(compile), //expressions
})
}
function statement(node) {
if (node.type.indexOf("Expression") < 0) return node;
if (node.type == "FunctionExpression") {
node.type = "FunctionDeclaration"
return node
}
return n("ExpressionStatement", {expression: node})
}
function compile(tree) {
if (tree == null) return null
if (tree instanceof Array) {
return (special[tree[0]] || call).call(special, tree)
}
else {
try {
var node = JSON.parse(tree)
return n("Literal", {value: node === null ? "null" : node})
}
catch (e) {
return n("Identifier", {name: tree})
}
}
}
function compileStatement(tree) {
return statement(compile(tree))
}
yeah maybe I should clean up the code and put it up in a repo when I get some time. I was mostly just toying w/ this code, but it's grown quite a bit since I posted this gist and it's not so toy-ish anymore.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
work in progress compiler: