Skip to content

Instantly share code, notes, and snippets.

@lhorie
Last active May 8, 2022 21:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lhorie/3095cf62023b74d27fc9 to your computer and use it in GitHub Desktop.
Save lhorie/3095cf62023b74d27fc9 to your computer and use it in GitHub Desktop.
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 + ")"})
}
@lhorie
Copy link
Author

lhorie commented Sep 28, 2015

work in progress compiler:

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 {
            type: "MemberExpression",
            object: compile(form[1]), //expression
            property: compile(form[2]), //expression
            computed: true, // true = a[b], false = a.b FIXME
        }
    },

    "?:": function(form) {
        return {
            type: "ConditionalExpression",
            test: compile(form[1]), //expression
            consequent: compile(form[2]), //expression
            alternate: compile(form[3]), //expression
        }
    },

    "break": function(form) {
        return {
            type: "BreakStatement",
            label: compile(form[1]), //identifier || null
        }
    },
    "case": function(form) {
        return {
            type: "SwitchCase",
            test: compile(form[1]), //expression
            consequent: form.slice(2).map(compileStatement), //statements
        }
    },
    "continue": function(form) {
        return {
            type: "ContinueStatement",
            label: compile(form[1]), //identifier || null
        }
    },
    "debugger": function(form) {
        return {type: "DebuggerStatement"}
    },
    "do-while": function(form) {
        return {
            type: "DoWhileStatement",
            body: form.slice(0, -1).map(compileStatement), //statement
            test: compile(form[form.length - 1]), //expression
        }
    },
    "for": function(form) {
        return {
            type: "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 {
            type: "ForInStatement",
            left: compile(form[1]), //variable declaration || expression
            right: compile(form[2]), //expression
            body: form.slice(3).map(compileStatement), //statement
        }
    },
    "function": function(form) {
        return {
            type: "FunctionExpression",
            id: compile(form[1]), //identifier
            params: form[2].map(compile), //patterns
            body: {
                type: "BlockStatement",
                body: form.slice(3).map(compileStatement), //statement
            }
        }
    },
    "if": function(form) {
        return {
            type: "IfStatement",
            test: compile(form[1]), //expression
            consequent: compileStatement(form[2]), //statement
            alternate: compileStatement(form[3]), //statement || null
        }
    },
    "new": function(form) {
        return {
            type: "NewExpression",
            callee: compile(form[1]), //expression
            arguments: form.slice(2).map(compile), //expressions
        }
    },
    "return": function(form) {
        return {
            type: "ReturnStatement",
            argument: compile(form[1]), //expression || null
        }
    },
    "switch": function(form) {
        return {
            type: "SwitchStatement",
            discriminant: compile(form[1]), //expression
            cases: form.slice(2).map(compile), //switch cases
        }
    },
    "this": function(form) {
        return {type: "ThisExpression"}
    },
    "throw": function(form) {
        return {
            type: "ThrowStatement",
            argument: compile(form[1]), //expression
        }
    },
    "try": function(form) {
        //FIXME
        return {
            type: "TryStatement",
            block: compile(form[1]), //block statement FIXME signature
            handler: {
                type: "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) {
            var declarator = {
                type: "VariableDeclarator",
                id: compile(form[i]), //pattern
                init: compile(form[i + 1]), //expression || null
            }
            declarations.push(declarator)
        }
        return {
            type: "VariableDeclaration",
            declarations: declarations,
            kind: "var",
        }
    },
    "while": function(form) {
        return {
            type: "WhileStatement",
            test: compile(form[1]), //expression
            body: compileStatement(form[2]), //statement FIXME splice
        }
    },
    "with": function(form) {
        return {
            type: "WithStatement",
            object: compile(form[1]), //expression
            body: compileStatement(form[2]), //statement FIXME splice
        }
    },

    "program": function(form) {
        return {
            type: "Program",
            body: form.slice(1).map(compileStatement), //statements
        }
    },
    "empty": function(form) {
        return {type: "EmptyStatement"}
    }
    "label": function(form) {
        return {
            type: "LabeledStatement",
            label: compile(form[1]), //identifier
            body: compileStatement(form[2]), // statement
        }
    },
    "block": function(form) {
        return {
            type: "BlockStatement",
            body: form.slice(1).map(compileStatement), //statements
        }
    },
    "seq": function(form) {
        return {
            type: "SequenceExpression",
            expressions: form.slice(1).map(compile), //expressions
        }
    },
    "array": function(form) {
        return {
            type: "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({
                type: "Property",
                key: compile(form[i]), //literal || identifier
                value: compile(form[i + 1]), //expression
                kind: "init" // | "get" | "set"; FIXME getter/setter
            })
        }
        return {
            type: "ObjectExpression",
            properties: properties
        }
    },
}
function unary(op, type, postfix) {
    return function(form) {
        return {
            type: "UnaryExpression",
            operator: op,
            argument: compile(form[1]),
            prefix: !postfix
        }
    }
}
function binary(op, type) {
    return function(form) {
        return {
            type: type || "BinaryExpression",
            operator: "+",
            left: compile(form[1]),
            right: compile(form[2])
        }
    }
}
function call(form) {
    return {
        type: "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 {
        type: "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 {
                type: "Literal",
                value: node === null ? "null" : node,
            }
        }
        catch (e) {
            return {
                type: "Identifier",
                name: tree,
            }
        }
    }
}
function compileStatement(tree) {
    return statement(compile(tree))
}

@dead-claudia
Copy link

  1. 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
            })
        }
    }
  2. Throw this into a new repo. It would make it easier for you and those of us with suggestions.

  3. 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))
}

@lhorie
Copy link
Author

lhorie commented Oct 27, 2015

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