When I have free time, I’m working on:
https://github.com/ajlopez/RuScript
I’m writing the code following the TDD (Test-Driven Development) workflow (see commit history)
The idea is to have a Ruby interpreter written in JavaScript, so it can run at the browser, or at the server with Node.js. I’m not implementing a compiler yet, because I want to do “baby steps”, and because Ruby semantic could be different to JavaScript one. Ie, the resolution of methods, or check if a variable is local to a function or not, are examples of divergences between the languages. So, I decided to write an interpreter.
I’m applying “dog fooding”: the parser is written using:
https://github.com/ajlopez/SimpleGrammar
I’m applying SimpleGrammar in other projects, like RustScript. There is an implementation in C#, too, named GrammGen). A fragment of the defined rules for RuScript parser:
// Expression Level 1 get('Expression1').generate('Expression'), get('Expression1', 'Plus', 'Expression0') .generate('Expression1', function (values) { return new AddExpression(values[0], values[2]); }), get('Expression1', 'Minus', 'Expression0') .generate('Expression1', function (values) { return new SubtractExpression(values[0], values[2]); }), // Expression Level 0 get('Expression0').generate('Expression1'), get('Expression0', 'Multiply', 'Term') .generate('Expression0', function (values) { return new MultiplyExpression(values[0], values[2]); }), get('Expression0', 'Divide', 'Term') .generate('Expression0', function (values) { return new DivideExpression(values[0], values[2]); }), // Term get('Term').generate('Expression0'), get('Term', 'LeftBracket', 'ExpressionList', 'RightBracket') .generate('Term', function (values) { return new IndexedExpression(values[0], values[2]); }), get('Integer').generate('Term'), get('@@', 'Name').generate('ClassVariable', function (values) { return new ClassVariableExpression(values[1].getName()); }), get('@', 'Name').generate('InstanceVariable', function (values) { return new InstanceVariableExpression(values[1].getName()); }), get('Name').generate('Term'), get('InstanceVariable').generate('Term'), get('ClassVariable').generate('Term'), get('String').generate('Term'), get('Array').generate('Term'), get('LeftParenthesis', 'Expression', 'RightParenthesis') .generate('Expression0', function (values) { return values[1]; }), get('LeftBracket', 'RightBracket') .generate('Array', function (values) { return new ArrayExpression([]); }), get('LeftBracket', 'ExpressionList', 'RightBracket') .generate('Array', function (values) { return new ArrayExpression(values[1]); }),
For each discovered element, the grammar is building an expression, that can be evaluate using a context. A context has the values of the visible variables. Example:
function NameExpression(name) { this.evaluate = function (context) { var value = context.getValue(name); if (typeof value == 'function') return value(); return value; }; this.getName = function () { return name; }; this.setValue = function (context, value) { context.setLocalValue(name, value); } } function InstanceVariableExpression(name) { this.evaluate = function (context) { if (!context.$self.$vars) return null; return context.$self.$vars[name]; }; this.getName = function () { return name; }; this.setValue = function (context, value) { if (!context.$self.$vars) context.$self.$vars = { }; context.$self.$vars[name] = value; } } function ClassVariableExpression(name) { this.evaluate = function (context) { if (!context.$self.$class.$vars) return null; return context.$self.$class.$vars[name]; }; this.getName = function () { return name; }; this.setValue = function (context, value) { if (!context.$self.$class.$vars) context.$self.$class.$vars = { }; context.$self.$class.$vars[name] = value; } } function ConstantExpression(value) { this.evaluate = function () { return value; }; }
I have access to JavaScript features from Ruby, example, a test:
exports['Evaluate JavaScript global name'] = function (test) { global.myglobal = 42; var context = contexts.createContext(); var parser = parsers.createParser("js.myglobal"); //parser.options({ log: true }); var expr = parser.parse("Expression"); var result = expr.value.evaluate(context); test.equal(result, 42); }
The js namespaces allowed the access to JavaScript defined variables, and all its ecosystem, at the browser or at the server.
I want to share my advances at RubyConf 2014 Argentina.
Next steps: access to Node.js require, web site examples using Node.js/Express, console examples, browser examples, etc.
Stay tuned!
Angel “Java” Lopez
http://www.ajlopez.com
