Skip to content

Commit

Permalink
Add infinitely nested objs.
Browse files Browse the repository at this point in the history
* a b c d e f is '1'
* a is b c d e f
  • Loading branch information
maryrosecook committed Feb 26, 2013
1 parent 8d2dca7 commit e5ab062
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 40 deletions.
73 changes: 66 additions & 7 deletions spec/interpreter.spec.js
Expand Up @@ -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'";
Expand Down Expand Up @@ -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.");
});
});

Expand All @@ -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
Expand Down
52 changes: 52 additions & 0 deletions spec/parser.spec.js
Expand Up @@ -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(){
Expand All @@ -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(){
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion src/grammar.js
Expand Up @@ -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'); }",
Expand Down
92 changes: 60 additions & 32 deletions src/interpreter.js
Expand Up @@ -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);
})
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
});

Expand Down Expand Up @@ -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;
}
};
Expand Down

0 comments on commit e5ab062

Please sign in to comment.