Skip to content
This repository has been archived by the owner on Apr 14, 2020. It is now read-only.

Commit

Permalink
Allow function arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
brianmhunt committed Sep 21, 2016
1 parent bf07810 commit d4e248f
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 28 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Expand Up @@ -38,6 +38,7 @@ globals:
jasmine: true
describe: true
it: true
xit: true
iit: true
ddescribe: true
afterEach: true
Expand Down
53 changes: 52 additions & 1 deletion spec/parserBehaviors.js
Expand Up @@ -147,6 +147,8 @@ describe("the bindings parser", function() {
bindings = new Parser(null, context).parse(binding);
assert.equal(bindings.text(), 'prefixmikepostfix')
})

it.skip("parses interpolated backtick strings (`abc ${var}`)")
})

describe("the parsing of expressions", function() {
Expand Down Expand Up @@ -488,9 +490,12 @@ describe("compound expressions", function() {
}
}
},
context,
obs = observable({
d: d
}),
});

beforeEach(function () {
context = {
a: a,
F1: F1,
Expand All @@ -500,6 +505,7 @@ describe("compound expressions", function() {
z: z,
u: undefined
};
})

// a property of the observable (not the observable's value)
obs.P = y;
Expand Down Expand Up @@ -583,4 +589,49 @@ describe("compound expressions", function() {
it("gets obs['P']", function() {
expect_equal("obs['P']", obs.P)
})

describe("function expressions", function () {
function R() { return arguments }
function R0() { return arguments[0] }
function B() { return { self: this, args: arguments } }
beforeEach(function () { context = { R: R, B: B, R0: R0 } })

it("calls the function with an argument", function () {
expect_deep_equal("R(123)", R(123))
})

it("calls the function with two arguments", function () {
expect_deep_equal("R('x', 'y')", R('x', 'y'))
})

it("calls the function with nested functions", function () {
expect_deep_equal("R(R('xe'))", R(R('xe')))
})

it("calls the function with primitives", function () {
expect_deep_equal(
"R([123], null, false, true, undefined)",
R([123], null, false, true, undefined)
)
})

it("chains argument arrays return", function () {
expect_deep_equal("R0(['x'])[0]", 'x')
})

it("exposes argument object return", function () {
expect_deep_equal("R0({xee: 'xe'}).xee", 'xe')
})

it("chains function calls with arguments", function () {
expect_deep_equal(
"R0(R0([R0({x: R0('1i3')})]))[0].x",
'1i3'
)
})

it.skip("calls functions with .bind", function () {
expect_deep_equal("B.bind('x')()", { self: 'x', args: [] })
})
})
}); // compound functions
13 changes: 10 additions & 3 deletions spec/providerBehaviour.js
Expand Up @@ -37,6 +37,7 @@ var instance,
Parser,
Expression,
Identifier,
Arguments,
Node,
operators;

Expand All @@ -45,6 +46,7 @@ beforeEach(function() {
Parser = instance.Parser,
Identifier = Parser.Identifier,
Expression = Parser.Expression,
Arguments = Parser.Arguments,
Node = Parser.Node,
operators = Node.operators;
})
Expand Down Expand Up @@ -609,9 +611,10 @@ describe("the build_tree function", function() {
},
parser, nodes, root;
parser = new Parser(null, context);
var fake_args = new Arguments(null, [])
nodes = [
// the third argument is the same as _deref_call
new Identifier(parser, 'x', [true]),
new Identifier(parser, 'x', [fake_args]),
operators['|'],
0x80
];
Expand Down Expand Up @@ -643,13 +646,16 @@ describe("Identifier", function() {
ident = new Identifier({}, 'x', refs);
assert.equal(ident.dereference('1'), 1)
})

it("does nothing with empty array references", function() {
var refs = [],
ident = new Identifier({}, 'x', refs);
assert.equal(ident.dereference('1'), 1)
})

it("applies the functions of the refs to the value", function() {
var refs = [true, true],
var fake_args = new Arguments(null, []),
refs = [fake_args, fake_args],
ident = new Identifier({}, 'x', refs),
g = function() {
return '42'
Expand Down Expand Up @@ -707,7 +713,8 @@ describe("Identifier", function() {
f: f
},
parser = new Parser(null, context),
derefs = [true];
fake_args = new Arguments(null, []),
derefs = [fake_args];
assert.equal(new Identifier(parser, 'f', derefs).get_value(), 'Fv')
})
})
Expand Down
20 changes: 20 additions & 0 deletions src/arguments.js
@@ -0,0 +1,20 @@

import Node from './node';


export default function Arguments(parser, args) {
this.parser = parser;
this.args = args;
}


Arguments.prototype.get_value = function get_value(/* parent */) {
var dereffed_args = [];
for (var i = 0, j = this.args.length; i < j; ++i) {
dereffed_args.push(Node.value_of(this.args[i]));
}
return dereffed_args;
};


Arguments.prototype[Node.isExpressionOrIdentifierSymbol] = true;
7 changes: 4 additions & 3 deletions src/expression.js
Expand Up @@ -19,9 +19,7 @@ Expression.Node = Node;
Expression.prototype.build_tree = function (nodes) {
var root, leaf, op, value;

// console.log("build_tree", nodes.slice(0))

// primer
// Prime the leaf = root node.
leaf = root = new Node(nodes.shift(), nodes.shift(), nodes.shift());

while (nodes) {
Expand Down Expand Up @@ -49,6 +47,9 @@ Expression.prototype.build_tree = function (nodes) {
*
*/
Expression.prototype.get_value = function () {
if ('degenerate_value' in this) {
return this.degenerate_value;
}
if (!this.root) {
this.root = this.build_tree(this.nodes);
}
Expand Down
27 changes: 12 additions & 15 deletions src/identifier.js
@@ -1,5 +1,6 @@

import Node from './node.js';
import Node from './node';
import Arguments from './arguments';

import {
isWriteableObservable
Expand All @@ -13,13 +14,6 @@ export default function Identifier(parser, token, dereferences) {
}


export function value_of(item) {
if (item[Node.isExpressionOrIdentifierSymbol]) {
return item.get_value();
}
return item;
}


/**
* Return the value of the given
Expand All @@ -37,7 +31,7 @@ Identifier.prototype.lookup_value = function (parent) {
globals = parser.globals || {};

if (parent) {
return value_of(parent)[token];
return Node.value_of(parent)[token];
}

// short circuits
Expand Down Expand Up @@ -79,13 +73,16 @@ Identifier.prototype.dereference = function (value) {
i, n;

for (i = 0, n = refs.length; i < n; ++i) {
member = refs[i];
if (member === true) {
value = value.call(last_value || self);
member = Node.value_of(refs[i]);

if (typeof value === 'function' && refs[i] instanceof Arguments) {
// fn(args)
value = value.apply(last_value || self, member);
last_value = value;
} else {
// obj[x] or obj.x dereference. Note that obj may be a function.
last_value = value;
value = value[value_of(member)];
value = Node.value_of(value[member]);
}
}
return value;
Expand Down Expand Up @@ -158,7 +155,7 @@ Identifier.prototype.set_value = function (new_value) {
if (leaf === true) {
root = root();
} else {
root = root[value_of(leaf)];
root = root[Node.value_of(leaf)];
}
}

Expand All @@ -169,7 +166,7 @@ Identifier.prototype.set_value = function (new_value) {

// Call the setter for the leaf.
if (refs[i]) {
this.assign(root, value_of(refs[i]), new_value);
this.assign(root, Node.value_of(refs[i]), new_value);
}
};

Expand Down
12 changes: 12 additions & 0 deletions src/node.js
Expand Up @@ -122,4 +122,16 @@ Node.prototype.get_node_value = function () {
};



//
// Class variables.
//
Node.isExpressionOrIdentifierSymbol = createSymbolOrString("isExpressionOrIdentifierSymbol");


Node.value_of = function value_of(item) {
if (item && item[Node.isExpressionOrIdentifierSymbol]) {
return item.get_value();
}
return item;
};
36 changes: 30 additions & 6 deletions src/parser.js
Expand Up @@ -9,6 +9,7 @@ import {

import Expression from './expression';
import Identifier from './identifier';
import Arguments from './arguments';
import Node from './node';

var escapee = {
Expand Down Expand Up @@ -37,9 +38,10 @@ export default function Parser(node, context, globals) {
this.globals = globals || {};
}

// exported for testing.
// Exposed for testing.
Parser.Expression = Expression;
Parser.Identifier = Identifier;
Parser.Arguments = Arguments;
Parser.Node = Node;

Parser.prototype.white = function () {
Expand Down Expand Up @@ -382,6 +384,31 @@ Parser.prototype.expression = function () {
return new Expression(nodes);
};


/**
* Parse the arguments to a function, returning an Array.
*
*/
Parser.prototype.func_arguments = function () {
var args = [],
ch = this.next('(');

while(ch) {
ch = this.white();
if (ch === ')') {
this.next(')');
return new Arguments(this, args);
} else {
args.push(this.expression());
ch = this.white();
}
if (ch !== ')') { this.next(','); }
}

this.error("Bad arguments to function");
};


/**
* A dereference applies to an identifer, being either a function
* call "()" or a membership lookup with square brackets "[member]".
Expand All @@ -394,11 +421,8 @@ Parser.prototype.dereference = function () {

while (ch) {
if (ch === '(') {
// a() function call
this.next('(');
this.white();
this.next(')');
return true; // in Identifier::dereference we check this
// a(...) function call
return this.func_arguments();
} else if (ch === '[') {
// a[x] membership
this.next('[');
Expand Down

0 comments on commit d4e248f

Please sign in to comment.