Skip to content

Instantly share code, notes, and snippets.

@PuercoPop
Created September 10, 2014 07:41
Show Gist options
  • Save PuercoPop/a7af75d0a8c4c3e4522e to your computer and use it in GitHub Desktop.
Save PuercoPop/a7af75d0a8c4c3e4522e to your computer and use it in GitHub Desktop.
Código para mi presentación en LimaJS, diapositivas en https://slides.com/puercopop/escodgen/
#!/usr/bin/js
var fs = require('fs');
var escodegen = require('escodegen');
var inputFile = process.argv[2] ? process.argv[2] : "/home/puercopop/tmp.js";
var outputFile = process.argv[3] ? process.argv[3] : "/home/puercopop/a.js";
var code;
fs.readFile(inputFile, 'utf8', function (err, data) {
if (err) {
console.log(err);
}
code = escodegen.generate(JSON.parse(data));
fs.writeFile(outputFile, code, 'utf8', function(err) {
if (err) return console.log(err);
console.log('Writing', code, 'in', outputFile);
});
});
var esprima = require('esprima');
var parse = esprima.parse;
var escodegen = require('escodegen');
var estraverse = require('estraverse');
var Syntax = estraverse.Syntax;
var generate = escodegen.generate;
var run = function(code, transformation) {
console.log('running', transformation.name);
console.log('Input:', code)
console.log('Output', generate(transformation(parse(code))));
};
function most_simple_constant_foldling(stmt) {
estraverse.replace(
stmt,
{
enter: function(node) {
if (node.type === 'BinaryExpression')
{
if (node.operator === "+" &&
node.left.type === "Literal" &&
node.right.type === "Literal") {
var result = node.left.value + node.right.value;
return { type: "Literal",
value: result};
} else {
return node;
}
}
}
})
return stmt;
};
run('var x = 2 + 4', most_simple_constant_foldling);
run('var x = 2 - 4', most_simple_constant_foldling); // Trips
function too_simple_constant_folding(stmt) {
estraverse.replace(
stmt,
{
enter: function(node) {
if (node.type === Syntax.BinaryExpression &&
node.left.type === Syntax.Literal &&
node.right.type === Syntax.Literal) {
if (node.operator === "+") {
return { type: Syntax.Literal,
value: node.left.value + node.right.value };
} else if ( node.operator === "-") {
return { type: Syntax.Literal,
value: node.left.value - node.right.value };
} else if (node.operator === "*") {
return { type: Syntax.Literal,
value: node.left.value * node.right.value }
} else if (node.operator === "/") {
return { type: Syntax.Literal,
value: node.left.value / node.right.value }
}};
}
})
return stmt; };
run('var x = 2 + 4', too_simple_constant_folding);
run('var x = 4 - 2', too_simple_constant_folding);
run('var x = 4 * 2', too_simple_constant_folding);
run('var x = 4 / 2', too_simple_constant_folding);
run('var x = 4 / 0', too_simple_constant_folding);
function finicky_simple_constant_folding(stmt) {
estraverse.replace(
stmt,
{
enter: function(node) {
if (node.type === Syntax.BinaryExpression &&
node.left.type === Syntax.Literal &&
node.right.type === Syntax.Literal) {
if (node.operator === "+") {
return { type: Syntax.Literal,
value: node.left.value + node.right.value };
} else if ( node.operator === "-") {
return { type: Syntax.Literal,
value: node.left.value - node.right.value };
} else if (node.operator === "*") {
return { type: Syntax.Literal,
value: node.left.value * node.right.value }
} else if (node.operator === "/") {
var result = node.left.value / node.right.value;
if (isFinite(result)) {
return { type: Syntax.Literal,
value: result }
}
}};
}
})
return stmt; };
run('var x = 4 / 0', finicky_simple_constant_folding);
run('var x = 0 / 0', finicky_simple_constant_folding);
// run('var x = 4 - 8', too_simple_constant_folding);
// Numeric Literals can't be negative
// Unary Expression for that.
function wrap_negative_values(node) {
if (node.value < 0) {
return { type: Syntax.UnaryExpression,
operator: "-",
prefix: true,
argument: { type: Syntax.Literal,
value: -node.value}}
} else {
return node;
}
}
function i_hope_it_does_simple_constant_folding(stmt) {
estraverse.replace(
stmt,
{
enter: function(node) {
if (node.type === Syntax.BinaryExpression &&
node.left.type === Syntax.Literal &&
node.right.type === Syntax.Literal) {
if (node.operator === "+") {
return { type: Syntax.Literal,
value: node.left.value + node.right.value };
} else if ( node.operator === "-") {
return wrap_negative_values({ type: Syntax.Literal,
value: node.left.value - node.right.value });
} else if (node.operator === "*") {
return { type: Syntax.Literal,
value: node.left.value * node.right.value }
} else if (node.operator === "/") {
var result = node.left.value / node.right.value;
if (isFinite(result) || !isNaN(result)) {
return { type: Syntax.Literal,
value: result }
}
}};
}
})
return stmt; };
run('var x = 4 - 8', i_hope_it_does_simple_constant_folding);
// TODO: Handle multiple aritiy
run('var x = 4 + 2 + 8', i_hope_it_does_simple_constant_folding);
function alas_simple_constant_folding(stmt) {
var modified = true;
while (modified) {
modified = false;
estraverse.replace(
stmt,
{
enter: function(node) {
if (node.type === Syntax.BinaryExpression &&
node.left.type === Syntax.Literal &&
node.right.type === Syntax.Literal) {
if (node.operator === "+") {
modified = true;
return { type: Syntax.Literal,
value: node.left.value + node.right.value };
} else if ( node.operator === "-") {
modified = true;
return wrap_negative_values({ type: Syntax.Literal,
value: node.left.value - node.right.value });
} else if (node.operator === "*") {
modified = true;
return { type: Syntax.Literal,
value: node.left.value * node.right.value }
} else if (node.operator === "/") {
var result = node.left.value / node.right.value;
if (isFinite(result)) {
modified = true;
return { type: Syntax.Literal,
value: result }
}
}};
}
})
};
return stmt; };
run('var x = 4 + 2 + 8', alas_simple_constant_folding);
// run('var x = 4 * - 8', i_hope_it_does_simple_constant_folding);
//// Handling Unary Expressions is left as an Exercise to the reader. (:
// Return the number as a value if looking at number or false if otherwise;
function lookingAtNumber(node) {
// NaN is a number but is falsy :D
if ( node.type === Syntax.Literal && typeof(node.value) === 'number') {
return node.value;
} else if ( node.type == Syntax.UnaryExpression &&
node.operator === '-' &&
node.argument.type === Syntax.Literal &&
typeof(node.argument.value) === 'number') {
return -node.argument.value;
} else {
return false;
}
};
;; To run do
;; Intermediate Representation
(defclass function-definition ()
((name :initarg :name :reader name)
(arguments :initarg :arguments :reader arguments)
(body :initarg :body :reader body)))
(defclass function-application ()
((name :initarg :name :reader name)
(arguments :initarg :arguments :reader arguments)))
(defclass identifier ()
((name :initarg :name :reader name)))
(defclass num ()
((value :initarg :value :reader value)))
(defclass arith-op ()
((operator :initarg :operator :reader operator)
(operands :initarg :operands :reader operands)))
(defclass conditional-op ()
((test :initarg :test :reader test)
(consequent :initarg :consequent :reader consequent)
(alternate :initarg :alternate :initform nil :reader alternate)))
(defclass block-statement ()
((expressions :initarg :expressions :reader expressions)))
;; Pretty Printing
(defmethod print-object ((obj num) stream)
(print-unreadable-object (obj stream :type t)
(format stream "~A" (value obj))))
(defmethod print-object ((obj arith-op) stream)
(print-unreadable-object (obj stream)
(format stream "~A: ~{~A~^, ~}" (operator obj) (operands obj))))
(defmethod print-object ((obj conditional-op) stream)
(print-unreadable-object (obj stream)
(format stream "If ~A then ~A else ~A"
(test obj) (consequent obj) (alternate obj))))
(defmethod print-object ((obj function-definition) stream)
(print-unreadable-object (obj stream)
(format stream "Function ~A~@[, takes ~A as arguments~]" (name obj) (arguments obj))))
(defmethod print-object ((obj function-application) stream)
(print-unreadable-object (obj stream)
(format stream "Call to ~A with ~{~A~^, ~}" (name obj) (arguments obj))))
(defmethod print-object ((obj identifier) stream)
(print-unreadable-object (obj stream)
(format stream "Var ~A" (name obj))))
(defmethod print-object ((obj block-statement) stream)
(print-unreadable-object (obj stream)
(format stream "CODE BLOCK: ~A ..." (expressions obj))))
;; Serialization to JSON
(defun negate (number)
(make-instance 'num :value (- (value number))))
(defgeneric to-json (obj &key stream )
(:documentation "Serialize object to json"))
(defmethod to-json ((obj num) &key (stream t))
(if (> 0 (value obj))
(format stream
"{ \"type\": \"UnaryExpression\", \"operator\": \"-\", \"prefix\": \"true\", \"argument\": ~A }"
(to-json (negate obj) :stream nil))
(format stream
"{\"type\": \"Literal\", \"value\": ~A }"
(value obj))))
(defmethod to-json ((obj arith-op) &key (stream t))
(with-accessors ((operator operator)
(operands operands))
obj
(cond
((> (length operands) 2)
(format stream
"{ \"type\": \"BinaryExpression\", \"operator\": \"~A\", \"left\": ~A, \"right\": ~A }"
operator
(to-json (first operands) :stream nil)
(to-json (make-instance 'arith-op
:operator operator
:operands (cdr operands))
:stream nil)))
(t (format stream
"{ \"type\": \"BinaryExpression\", \"operator\": \"~A\", \"left\": ~A, \"right\": ~A }"
operator
(to-json (first operands) :stream nil)
(to-json (second operands) :stream nil))))))
(defmethod to-json ((obj conditional-op) &key (stream t))
(format stream "{ \"type\": \"IfStatement\", \"test\": ~A, \"consequent\": ~A, \"alternate\": ~A }" ;; TOOD: Fix for the case the alternate is empty
(to-json (test obj) :stream nil)
(to-json (consequent obj) :stream nil)
(to-json (alternate obj) :stream nil)))
(defmethod to-json ((obj function-definition) &key (stream t))
(format stream
"{ \"type\": \"FunctionDeclaration\", \"id\": { \"type\": \"Identifier\", \"name\": \"~A\" }, \"params\": ~A , \"defaults\": [], \"rest\": null, \"generator\": false, \"expression\": false, \"body\": { \"type\": \"BlockStatement\", \"body\": [~{ ~A~^, ~}]}}"
(name obj)
(process-argument-list (arguments obj))
(mapcar #'as-expression-statements
(mapcar #'(lambda (x) (to-json x :stream nil)) (body obj)))))
(defun process-argument-list (arguments)
"Generate the Array of arguments required by the SpiderMoneky AST."
(format nil "[~{~A~^, ~}]" (mapcar #'to-identifier arguments)))
(defmethod to-json ((obj identifier) &key stream)
(format stream "{\"type\": \"Identifier\", \"name\": \"~A\"}" (name obj)))
(defun to-identifier (identifier)
(format nil "{ \"type\": \"Identifier\", \"name\": \"~A\" }" identifier))
(defun as-expression-statements (stmt)
"Wrap statement in an ExpressionStatement Node."
(format nil "{ \"type\": \"ExpressionStatement\", \"expression\": ~A }" stmt))
(defmethod to-json ((obj function-application) &key (stream t))
(with-expression-statement
(format nil "{ \"type\": \"CallExpression\", \"callee\": ~A, \"arguments\": [ ~{~A~^, ~}] }"
(to-identifier (name obj))
(mapcar #'(lambda (x) (to-json x :stream nil)) (arguments obj)))
stream))
(defmethod to-json ((obj block-statement) &key (stream t))
(format stream
"{ \"type\": \"BlockStatement\", \"body\": [~{~A~^, ~}] }"
(mapcar #'(lambda (x) (to-json x :stream nil)) (expressions obj) )))
(defun with-expression-statement (str stream)
(format stream "{ \"type\": \"ExpressionStatement\", \"expression\": ~A }" str))
(defun with-program-statement (str stream)
(format stream "{ \"type\": \"Program\", \"body\": [~A]}" str))
;; Entry Point
(defun compile-to-js (code &optional (output-file #P"~/tmp.js"))
(with-open-file (out output-file :direction :output :if-exists :supersede)
(to-json (parse code) :stream out)))
;; Parser
(defun parse (expr)
"No error reporting whatsoever."
(cond
((null expr) "")
((atom expr)
(cond
((numberp expr) (parse-number expr))
(t (parse-identifier expr))))
((atom (car expr))
(case (car expr)
((+ - * / ==) (parse-arith-op expr))
(if (parse-conditional-op expr))
(define-function (parse-function-definition expr))
(t (parse-function-call expr))))
(t (parse-block-statement (mapcar #'parse expr)))))
(defun parse-number (expr)
(make-instance 'num
:value expr))
(defun parse-identifier (expr)
(make-instance 'identifier
:name expr))
(defun parse-arith-op (expr)
(make-instance 'arith-op
:operator (car expr)
:operands (map 'list #'parse (cdr expr))))
(defmacro listify (expr)
"Ensure the expression return is a list."
(let ((result expr))
`(or (and (listp ,result) ,result)
(list ,result))))
(defun parse-conditional-op (expr)
;; Guarantee that the expr is a list
(let ((third (listify (parse (third expr))))
(fourth (listify (parse (fourth expr)))))
(make-instance 'conditional-op
:test (parse (second expr))
:consequent (make-instance 'block-statement
:expressions third)
:alternate (make-instance 'block-statement
:expressions fourth))))
(defun parse-function-definition (expr)
(make-instance 'function-definition
:name (second expr)
:arguments (third expr)
:body (mapcar #'parse (cdddr expr))))
(defun parse-function-call (expr)
(make-instance 'function-application
:name (car expr)
:arguments (mapcar #'parse (cdr expr))))
(defun parse-block-statement (expr)
(make-instance 'block-statement
:expressions expr))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment