Permalink
Browse files

Add infinitely nested objs.

* a b c d e f is '1'
* a is b c d e f
  • Loading branch information...
1 parent 8d2dca7 commit e5ab062a7a6240e06e4cf884ad856aabc18831bf @maryrosecook committed Feb 26, 2013
Showing with 182 additions and 40 deletions.
  1. +66 −7 spec/interpreter.spec.js
  2. +52 −0 spec/parser.spec.js
  3. +4 −1 src/grammar.js
  4. +60 −32 src/interpreter.js
View
73 spec/interpreter.spec.js
@@ -59,6 +59,50 @@ describe('interpreter', function() {
});
});
+ describe('non-existent', function(){
+ it('should complain about non-existent top level assignee obj', function() {
+ expect(function() {
+ interpreter.interpret("a b is '1'");
+ }).toThrow("I have not heard of a.");
+ });
+
+ it('should complain about non-existent obj that is attr of obj', function() {
+ expect(function() {
+ interpreter.interpret("a is a thing\na b c is '1'");
+ }).toThrow("I have not heard of a b.");
+ });
+
+ it('should complain about non-existent assigned top level thing', function() {
+ expect(function() {
+ interpreter.interpret("a is b");
+ }).toThrow("I have not heard of b.");
+ });
+
+ it('should complain about attr of obj attr of non-existent assigned obj', function() {
+ expect(function() {
+ interpreter.interpret("a is b c d");
+ }).toThrow("I have not heard of b.");
+ });
+ });
+
+
+ describe('nested attributes', function(){
+ it('should be able to assign scalar to nested attr', function() {
+ var code = "x is a thing\nx y is a thing\nx y z is '1'";
+ var env = interpreter.interpret(code);
+ expect(env.ctx.x.y.z).toEqual('1');
+ });
+ });
+
+
+ describe('nested attributes', function(){
+ it('should be able to assign nested attr to scalar', function() {
+ var code = "x is a thing\nx y is a thing\nx y z is '1'\na is x y z";
+ var env = interpreter.interpret(code);
+ expect(interpreter.resolve(env.ctx.a, env)).toEqual('1');
+ });
+ });
+
describe('scalar assigned to object attribute', function(){
it('should use ref when obj assigned', function() {
var code = "x is a person\ny is a person\ny age is '1'\nx friend is y\ny age is '2'";
@@ -159,13 +203,7 @@ describe('interpreter', function() {
it('should get error if try and use non-existent list', function() {
expect(function(){
interpreter.interpret("add 'sword' to items");
- }).toThrow("I do not know of a list called items.");
- });
-
- it('should get error if try and use non-existent list', function() {
- expect(function(){
- interpreter.interpret("add '1' to isla items");
- }).toThrow("I do not know of a list called isla items.");
+ }).toThrow("I have not heard of items.");
});
});
@@ -181,6 +219,27 @@ describe('interpreter', function() {
expect(env.ctx.items.items()).toEqual([{ ref: "mary" }]);
});
+ it('should be able to add scalar attr of obj that is attr of obj to a list', function() {
+ var code = "i is a list\na is a thing\na b is a thing\na b c is '1'\nadd a b c to i";
+ var env = interpreter.interpret(code);
+ expect(env.ctx.i.items()).toEqual(['1']);
+ });
+
+
+ it('should be able to add obj attr of obj that is attr of obj to a list', function() {
+ var code = "i is a list\na is a thing\na b is a thing\na b c is a thing\nadd a b c to i";
+ var env = interpreter.interpret(code);
+ expect(env.ctx.i.items()).toEqual([{ ref: ['a', 'b', 'c']}]);
+ });
+
+
+ it('should be able to add to list that is attr of obj that is attr of obj', function() {
+ var code = "a is a thing\na b is a thing\na b c is a list\nadd '1' to a b c";
+ var env = interpreter.interpret(code);
+ expect(env.ctx.a.b.c.items()).toEqual(['1']);
+ });
+
+
it('should not mistake two objects w/o refs as identical', function() {
// here because object equality check was a.ref === b.ref
// before you could add resolved objs to Lists
View
52 spec/parser.spec.js
@@ -70,6 +70,34 @@ describe('parser', function() {
{is: ["is"]},
{value: [{literal: [{string: ['1']}]}]}]}]}]}]});
});
+
+ it('should allow assignment to several attrs deep', function() {
+ checkAst(p.parse("isla jacket sleeve color is 'red'"),
+ {root: [{block: [{expression:
+ [{value_assignment:
+ [{assignee: [{object:
+ [{identifier: ["isla"]},
+ {identifier: ["jacket"]},
+ {identifier: ["sleeve"]},
+ {identifier: ["color"]}]}]},
+ {is: ["is"]},
+ {value: [{literal: [{string: ['red']}]}]}]}]}]}]});
+ });
+ });
+
+ describe('assignment of nested object attribute', function(){
+ it('should allow assignment', function() {
+ checkAst(p.parse("color is isla jacket sleeve color"),
+ {root: [{block: [{expression:
+ [{value_assignment:
+ [{assignee: [{scalar: [{identifier: ["color"]}]}]},
+ {is: ["is"]},
+ {value: [{variable:
+ [{object: [{identifier: ["isla"]},
+ {identifier: ["jacket"]},
+ {identifier: ["sleeve"]},
+ {identifier: ["color"]}]}]}]}]}]}]}]});
+ });
});
describe('type assignment', function(){
@@ -92,6 +120,18 @@ describe('parser', function() {
{is_a: ["is a"]},
{identifier: ["girl"]}]}]}]}]});
});
+
+ it('should allow assignment to an nested object attribute', function() {
+ checkAst(p.parse("mary friend niece is a girl"),
+ {root: [{block: [{expression:
+ [{type_assignment:
+ [{assignee: [{object:
+ [{identifier: ["mary"]},
+ {identifier: ["friend"]},
+ {identifier: ["niece"]}]}]},
+ {is_a: ["is a"]},
+ {identifier: ["girl"]}]}]}]}]});
+ });
});
describe('blocks', function(){
@@ -211,6 +251,18 @@ describe('parser', function() {
{identifier: ["age"]}]}]}]}]}]}]}]});
});
+ it('should allow invocation with nested object attribute', function() {
+ checkAst(p.parse("write isla jacket sleeve color"),
+ {root: [{block: [{expression:
+ [{invocation:
+ [{identifier: ["write"]},
+ {value: [{variable:
+ [{object: [{identifier: ["isla"]},
+ {identifier: ["jacket"]},
+ {identifier: ["sleeve"]},
+ {identifier: ["color"]}]}]}]}]}]}]}]});
+ });
+
it('should not show string regression', function() {
checkAst(p.parse("write 'My name Isla'"),
{root: [{block: [{expression:
View
5 src/grammar.js
@@ -76,7 +76,10 @@
" = all:identifier { return nnode('scalar', [all], column, 'variable'); }",
"object",
- " = id1:identifier _ id2:identifier { return nnode('object', [syn(id1, 'variable'), syn(id2, 'attribute')], column); }",
+ " = id1:identifier _ id2:identifier rest:object_rest* { return nnode('object', [syn(id1, 'variable'), syn(id2, 'attribute')].concat(rest), column); }",
+
+ "object_rest",
+ " = _ id:identifier { return syn(id, 'attribute'); }",
"is",
" = all:'is' { return nnode('is', [all], column, 'keyword'); }",
View
92 src/interpreter.js
@@ -51,6 +51,7 @@
var assignee = node[0];
var valueNode = interpretAst(node[2], env);
var value = assignmentValue(valueNode);
+
var env = assign(env, assignee, value);
return nreturn(env.ctx);
})
@@ -76,24 +77,15 @@
var currentListEval = evaluateValue(Isla.Parser.extract(assignee,
"assignee", 0),
env);
- if(currentListEval.val === undefined) { // no such list - show error
- var ref = currentListEval.ref;
- throw "I do not know of a list called "
- + (Isla.Utils.type(currentListEval.ref) === "Array"
- ? ref[0] + " " + ref[1] : ref)
- + ".";
- }
- else {
- var operation = Isla.Parser.extract(node, 0, "list_operation", 0).tag;
- var itemEval = interpretAst(Isla.Parser.extract(node, 1), env);
- var item = assignmentValue(itemEval);
+ var operation = Isla.Parser.extract(node, 0, "list_operation", 0).tag;
+ var itemEval = interpretAst(Isla.Parser.extract(node, 1), env);
+ var item = assignmentValue(itemEval);
- var list = currentListEval.val;
- list[operation](item);
+ var list = currentListEval.val;
+ list[operation](item);
- var env = assign(env, assignee, currentListEval.val);
- return nreturn(env.ctx);
- }
+ var env = assign(env, assignee, currentListEval.val);
+ return nreturn(env.ctx);
})
.when("invocation", function(ast, env) {
@@ -143,22 +135,52 @@
.when("scalar", function(node, env) {
var identifier = interpretAst(node.c[0], env);
- return { ref: identifier, val: env.ctx[identifier] };
+ var val = env.ctx[identifier];
+ if (val === undefined) {
+ nonExistentError([identifier]);
+ } else {
+ return { ref: identifier, val: val };
+ }
})
.when("object", function(node, env) {
- // make more specific
- var objId = node.c[0].c[0];
- var attrId = node.c[1].c[0];
- var val = env.ctx[objId] !== undefined &&
- env.ctx[objId][attrId] !== undefined ?
- env.ctx[objId][attrId] : undefined;
- return {
- ref: [objId, attrId], // won't work if assign obj-attr to var
- val: val
+ var objNode = Isla.Parser.extract(node, "object");
+ checkIdentifierParts(objNode, env);
+ var parts = identifierParts(objNode, env);
+ var val = env.ctx;
+ for (var i = 0; i < objNode.length; i++) {
+ var id = Isla.Parser.extract(objNode, i, "identifier", 0);
+ val = val[canonical(id, env)];
}
+
+ return { ref: parts, val: val };
});
+ var nonExistentError = function(identifier) {
+ throw "I have not heard of " + identifier.join(" ") + ".";
+ };
+
+ var checkIdentifierParts = function(objNode, env) {
+ var parts = identifierParts(objNode, env);
+ var val = env.ctx;
+ for (var i = 0; i < parts.length; i++) {
+ if (val === undefined) {
+ nonExistentError(parts.slice(0, i));
+ } else {
+ val = val[canonical(Isla.Parser.extract(objNode, i, "identifier", 0), env)];
+ }
+ }
+ };
+
+ var identifierParts = function(objNode, env) {
+ var parts = [];
+ for (var i = 0; i < objNode.length; i++) {
+ parts.push(Isla.Parser.extract(objNode, i, "identifier", 0));
+ }
+
+ return parts;
+ };
+
var assign = multimethod()
.dispatch(function(__, assigneeNode) {
return assigneeNode.c[0].tag;
@@ -173,10 +195,14 @@
.when("object", function(env, assigneeNode, value) {
var objNode = Isla.Parser.extract(assigneeNode, "assignee", 0, "object");
- var initObjIdentifier = Isla.Parser.extract(objNode, 0, "identifier", 0);
- var objIdentifier = canonical(initObjIdentifier, env);
- var slotIdentifier = Isla.Parser.extract(objNode, 1, "identifier", 0);
- env.ctx[objIdentifier][slotIdentifier] = value;
+ checkIdentifierParts(objNode, env);
+ var slot = env.ctx;
+ for (var i = 0; i < objNode.length - 1; i++) {
+ var id = canonical(Isla.Parser.extract(objNode, i, "identifier", 0), env);
+ slot = slot[id];
+ }
+
+ slot[Isla.Parser.extract(objNode, i, "identifier", 0)] = value;
return env;
});
@@ -241,9 +267,11 @@
var canonical = function(identifier, env) {
var next = env.ctx[identifier];
- if (next.ref !== undefined) {
+ if (next === undefined) { // got scalar - just return id
+ return identifier;
+ } else if (next.ref !== undefined) { // keep going to find canon
return canonical(next.ref, env);
- } else {
+ } else { // got value - return id
return identifier;
}
};

0 comments on commit e5ab062

Please sign in to comment.