Commits

Brian McKenna committed f4388ae Merge with conflicts

Merge remote-tracking branch 'origin' into constraints

Conflicts:
package.json
src/compile.js
src/node-repl.js

  • Participants
  • Parent commits cffd33b, b498c12
  • Branches constraints

Comments (0)

Files changed (7)

   "dependencies": {
     "escodegen": "0.0.22",
     "source-map": "0.1.8",
-    "underscore": "1.4.3",
+    "underscore": "1.5.2",
     "unicode-categories": "0.9.1"
   },
   "devDependencies": {
                 var maxTagPath = _.max(tagPaths, function(t) {
                     return t.path.length;
                 });
-                var maxPath = maxTagPath != -Infinity ? maxTagPath.path : [];
+                var maxPath = maxTagPath === -Infinity ? [] : maxTagPath.path;
 
                 var body = [];
                 if(vars) {
     }
 
     return {
-        type: typedNode.attribute.type,
+        type: typedNode.attribute,
         output: escodegen.generate(jsNode, {
             comment: true
         })
     return source;
 }
 
+function colorLog(color) {
+    var args = [].slice.call(arguments, 1);
+
+    args[0] = '\u001b[' + color + 'm' + args[0];
+    args[args.length - 1] = args[args.length - 1] + '\u001b[0m';
+
+    console.log.apply(console, args);
+}
+
 function nodeRepl(opts) {
     var readline = require('readline'),
         fs = require('fs'),
         stdin = process.openStdin(),
         repl = readline.createInterface(stdin, stdout),
 
-        env = {},
         sources = {},
-        aliases = {},
         sandbox = getSandbox();
 
+    var block = [];
+    var inBlock = false;
+
     // Prologue
     console.log("Roy: " + opts.info.description);
     console.log(opts.info.author);
         var metacommand = line.replace(/^\s+/, '').split(' '),
             compiled,
             output,
+            joined,
 
             filename,
             source,
             tokens,
             ast;
 
-
         // Check for a "metacommand"
         // e.g. ":q" or ":l test.roy"
         try {
-            switch(metacommand[0]) {
-            case ":q":
-                // Exit
-                process.exit();
-                break;
-            case ":l":
-                // Load
-                filename = metacommand[1];
-                source = getFileContents(filename);
-                compiled = compile(source, {nodejs: true, filename: ".", run: true});
-                break;
-            case ":t":
-                if(metacommand[1] in env) {
-                    console.log(env[metacommand[1]].toString());
-                } else {
-                    colorLog(33, metacommand[1], "is not defined.");
-                }
-                break;
-            case ":s":
-                // Source
-                if(sources[metacommand[1]]) {
-                    colorLog(33, metacommand[1], "=", prettyPrint(sources[metacommand[1]]));
-                } else {
-                    if(metacommand[1]){
-                        colorLog(33, metacommand[1], "is not defined.");
-                    } else {
-                        console.log("Usage :s command ");
-                        console.log(":s [identifier] :: show original code about identifier.");
-                    }
-                }
-                break;
-            case ":?":
-                // Help
-                colorLog(32, "Commands available from the prompt");
-                console.log(":l -- load and run an external file");
-                console.log(":q -- exit REPL");
-                console.log(":s -- show original code about identifier");
-                console.log(":t -- show the type of the identifier");
-                console.log(":? -- show help");
-                break;
-            default:
-                // The line isn't a metacommand
+            if (!inBlock && /^:/.test(metacommand[0])) {
+                compiled = processMeta(metacommand, sources);
+            } else if (/(=|->|→|\(|\{|\[|\bthen|\b(do|match)\s+.+?)\s*$/.test(line)) {
+                // A block is starting.
+                // E.g.: let, lambda, object, match, etc.
+                // Remember that, change the prompt to signify we're in a block,
+                // and start keeping track of the lines in this block.
+                inBlock = true;
+                repl.setPrompt('.... ');
+                block.push(line);
+            } else if (inBlock && /\S/.test(line)) {
+                // We're still in the block.
+                block.push(line);
+            } else {
+                // End of a block.
+                // Add the final line to the block, and reset our stuff.
+                block.push(line);
+                joined = block.join('\n');
+
+                inBlock = false;
+                repl.setPrompt('roy> ');
+                block = [];
 
                 // Remember the source if it's a binding
-                tokens = lexer.tokenise(line);
+                tokens = lexer.tokenise(joined);
                 ast = parser.parse(tokens);
+
                 if(typeof ast.body[0] != 'undefined') {
                     ast.body[0].accept({
+                        // Simple bindings.
+                        // E.g.: let x = 37
                         visitLet: function(n) {
                             sources[n.name] = n.value;
+                        },
+                        // Bindings that are actually functions.
+                        // E.g.: let f x = 37
+                        visitFunction: function(n) {
+                            sources[n.name] = n;
                         }
                     });
                 }
 
                 // Just eval it
                 compiled = compile(line, {nodejs: true, filename: ".", run: true});
-                break;
             }
 
             if(compiled) {
             }
         } catch(e) {
             colorLog(31, (e.stack || e.toString()));
+            // Reset the block because something wasn't formatted properly.
+            block = [];
         }
         repl.prompt();
     });
     repl.prompt();
 }
 
-function writeModule(env, exported, filename) {
+function writeModule(exported, filename) {
     var fs = require('fs'),
         moduleOutput = _.map(exported, function(v, k) {
             if(v instanceof types.TagType) {
 
         extensions = /\.l?roy$/,
 
-        env = {},
-        aliases = {},
         sandbox = getSandbox(),
-        exported,
-        modules;
+        exported;
 
     if(opts.run) {
         // Include the standard library
         if(opts.includePrelude) {
             argv.unshift(path.dirname(__dirname) + '/lib/prelude.roy');
         }
-    } else {
-        modules = [];
-        if(!argv.length || argv[0] != 'lib/prelude.roy') {
-            modules.push(path.dirname(__dirname) + '/lib/prelude');
-        }
-        _.each(modules, function(module) {
-            var moduleTypes = loadModule(module, {filename: '.'});
-            _.each(moduleTypes.env, function(v, k) {
-                env[k] = new types.Variable();
-                env[k] = nodeToType(v, env, aliases);
-            });
-        });
     }
 
     _.each(argv, function(filename) {
             // Write the JavaScript output.
             fs.writeFile(outputPath, compiled.output + '\n//@ sourceMappingURL=' + path.basename(outputPath) + '.map\n', 'utf8');
             fs.writeFile(outputPath + '.map', sourceMap.toString(), 'utf8');
-            writeModule(env, exported, filename.replace(extensions, '.roym'));
+            writeModule(exported, filename.replace(extensions, '.roym'));
         }
     });
 }
     processFlags(argv, opts);
 }
 
+function processMeta(commands, sources) {
+    var compiled,
+        prettyPrint = require('./prettyprint').prettyPrint,
+        source;
+
+    switch(commands[0]) {
+    case ":q":
+        // Exit
+        process.exit();
+        break;
+    case ":l":
+        // Load
+        source = getFileContents(commands[1]);
+        return compile(source, {nodejs: true, filename: ".", run: true});
+    case ":s":
+        // Source
+        if(sources[commands[1]]) {
+            colorLog(33, commands[1], "=", prettyPrint(sources[commands[1]]));
+        } else {
+            if(commands[1]){
+                colorLog(33, commands[1], "is not defined.");
+            }else{
+                console.log("Usage :s command ");
+                console.log(":s [identifier] :: show original code about identifier.");
+            }
+        }
+        break;
+    case ":?":
+        // Help
+        colorLog(32, "Commands available from the prompt");
+        console.log(":l -- load and run an external file");
+        console.log(":q -- exit REPL");
+        console.log(":s -- show original code about identifier");
+        console.log(":? -- show help");
+        break;
+    default:
+        colorLog(31, "Invalid command");
+        console.log(":? for help");
+        break;
+    }
+}
+
 function main() {
     var argv = process.argv.slice(2),
         fs = require('fs'),
         }
     ),
     attributedNode(
+        'TypeRowObject',
+        ['row', 'values'],
+        function(A) {
+            var self = this;
+            return this.attribute.map(function(attribute) {
+                return nodes.TypeRowObject(self.row, self.values).withAttribute(attribute);
+            });
+        },
+        function(f) {
+            return nodes.TypeRowObject(this.row, this.values).withAttribute(f(this));
+        }
+    ),
+    attributedNode(
         'TypeObject',
         ['values'],
         function(A) {

src/typegrammar.js

         ["GENERIC", "$$ = new yy.Generic($1);"],
         ["[ type ]", "$$ = new yy.TypeArray($2);"],
         ["( typeList )", "$$ = new yy.TypeObject($2);"],
+        ["{ GENERIC | optTypePairs }", "$$ = new yy.TypeRowObject($2, $4);"],
         ["{ optTypePairs }", "$$ = new yy.TypeObject($2);"]
     ],
     "typeList": [
         ["MATCH", "$$ = $1;"],
         ["CASE", "$$ = $1;"],
         ["DO", "$$ = $1;"],
-        ["RETURN", "$$ = $1;"],
         ["WITH", "$$ = $1;"],
-        ["WHERE", "$$ = $1;"],
         ["IDENTIFIER", "$$ = $1;"]
     ]
 };

src/typeinference.js

             return [];
         },
         visitTypeArray: function() {
-            throw new Error("TODO: visitTypeArray");
+            return freeTypeVariables(typeNode.value);
+        },
+        visitTypeRowObject: function() {
+            return _.flatten([typeNode.row].concat(_.map(typeNode.values, function(v) {
+                return freeTypeVariables(v);
+            })));
         },
         visitTypeObject: function() {
             return _.flatten(_.map(typeNode.values, function(v) {
                         return new t.ArrayType(n);
                     });
                 },
-                visitTypeObject: function() {
+                visitTypeRowObject: function() {
                     return arraySequence(_.map(node.values, function(v, k) {
                         return nodeToType(v, bindings, aliases).map(function(type) {
                             return [k, type];
                         });
-                    })).chain(function(o) {
-                        return freshVariable.map(function(row) {
-                            return new t.RowObjectType(row, _.object(o));
+                    })).map(function(o) {
+                        return new t.RowObjectType(node.row, _.object(o));
+                    });
+                },
+                visitTypeObject: function() {
+                    return arraySequence(_.map(node.values, function(v, k) {
+                        return nodeToType(v, bindings, aliases).map(function(type) {
+                            return [k, type];
                         });
+                    })).map(function(o) {
+                        return new t.ObjectType(_.object(o));
                     });
                 }
             });
         return recurse(node);
     });
 }
+exports.nodeToType = nodeToType;
 
 function tails(xs) {
     return _.map(_.range(xs.length), function(i) {

test/TypeParserSpec.js

+describe('compiler', function(){
+    var jison = require('jison'),
+        typegrammar = require('../src/typegrammar'),
+        typeinference = require('../src/typeinference'),
+        types = require('../src/types'),
+        lexer = require('../src/lexer'),
+        nodes = require('../src/nodes');
+
+    typegrammar.startSymbol = 'oneType';
+    typegrammar.bnf.oneType = [['type EOF', 'return $1;']];
+
+    typeparser = new jison.Parser(typegrammar, { debug: true, noDefaultResolve: true });
+
+    typeparser.yy = nodes;
+
+    typeparser.lexer =  {
+        "lex": function() {
+            var token = this.tokens[this.pos] ? this.tokens[this.pos++] : ['EOF'];
+            if ( token[2] != this.yylineno ) {
+                this.column = 0
+            } else {
+                this.column += token[1].length;
+            }
+
+            this.yytext = token[1];
+            this.yylineno = token[2];
+            return token[0];
+        },
+        "setInput": function(tokens) {
+            this.tokens = tokens;
+            this.pos = 0;
+            this.column = 0;
+        },
+        "upcomingInput": function() {
+            return "";
+        },
+        "showPosition": function() {
+            return 'column: ' + this.column;
+        }
+    };
+
+    function parsedType(s) {
+        var tokens = lexer.tokenise(s);
+        var v = typeparser.parse(tokens);
+        return typeinference.nodeToType(v, {}, {}).evalState(typeinference.GenerateState.init);
+    }
+
+    function expectEqualTypes(subject, target) {
+        // FIXME: Use intentional equality once it is implemented
+        expect(subject.toString()).toEqual(target.toString());
+    }
+
+    it('should parse atomic types', function() {
+        expectEqualTypes( parsedType('Number'), new types.NumberType() );
+        expectEqualTypes( parsedType('Boolean'),new types.BooleanType() );
+        expectEqualTypes( parsedType('String'), new types.StringType() );
+    });
+
+    it('should parse object types', function() {
+        expectEqualTypes( parsedType('{}'), new types.ObjectType({}) );
+        expectEqualTypes( parsedType('{foo:String}'), new types.ObjectType({foo: new types.StringType()}) );
+
+        expectEqualTypes( parsedType('{foo:String, baz:Number}'),
+                          new types.ObjectType({
+                              foo: new types.StringType(),
+                              baz: new types.NumberType()
+                          }));
+        // TODO: expectEqualTypes( parsedType('{\n\tfoo:String\n}'), new types.ObjectType({foo: new types.StringType()}) );
+    });
+
+    it('should parse function types', function() {
+        expectEqualTypes( parsedType('Function(String, String)'),
+                          new types.FunctionType([new types.StringType(), new types.StringType()]) );
+    });
+
+    it('should parse array types', function() {
+        expectEqualTypes( parsedType('[Number]'),
+                          new types.ArrayType(new types.NumberType()) );
+    });
+});