Created
September 10, 2014 07:41
-
-
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/
This file contains hidden or 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
#!/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); | |
}); | |
}); |
This file contains hidden or 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 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; | |
} | |
}; |
This file contains hidden or 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
;; 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