Slightly altered syntax, similar to Underscore's Function#bind form, highly simplified lexing and parsing and no polluted scope. Passing tests included.

This commit is contained in:
matehat
2010-03-18 00:19:32 -04:00
parent 3b22018296
commit 1f87094628
10 changed files with 134 additions and 195 deletions

View File

@@ -272,14 +272,8 @@
return $2.new_instance(); return $2.new_instance();
}), o("Super") }), o("Super")
], ],
Curry: [o("Value CURRY Value CURRY_SEPARATOR Arguments", function() { Curry: [o("Value CURRY Arguments", function() {
return new CurryNode($1, $3, $5);
}), o("Value CURRY Arguments", function() {
return new CurryNode($1, undefined, $3);
}), o("Value CURRY Value", function() {
return new CurryNode($1, $3); return new CurryNode($1, $3);
}), o("Value CURRY", function() {
return new CurryNode($1);
}) })
], ],
// Extending an object by setting its prototype chain to reference a parent // Extending an object by setting its prototype chain to reference a parent

View File

@@ -330,28 +330,9 @@
// Here we detect the currying operator and change local state in order to // Here we detect the currying operator and change local state in order to
// parse subsequent tokens accordingly. // parse subsequent tokens accordingly.
Lexer.prototype.curry_token = function curry_token() { Lexer.prototype.curry_token = function curry_token() {
var _a; if (this.match(CURRY, 1)) {
if ((typeof (_a = this.currying) !== "undefined" && _a !== null)) {
if (this.match(/^\(/, 0) && [',', '<-'].indexOf(this.value()) !== -1) {
this.token('CALL_START', '(');
this.i += 1;
this.currying = undefined;
return true;
} else if (this.match(CURRY_SEPARATOR, 0)) {
this.token('CURRY_SEPARATOR', ',');
this.i += 1;
return true;
} else if (!this.match(/^\s*[\w@\$_\(]/, 0)) {
this.currying = undefined;
return false;
}
} else if (this.match(CURRY, 1)) {
this.i += 2; this.i += 2;
this.chunk = this.code.slice(this.i);
this.token('CURRY', '<-'); this.token('CURRY', '<-');
if (this.match(/^(\s*[\w@\$_\(])/, 1)) {
this.currying = true;
}
return true; return true;
} }
}; };

View File

@@ -506,32 +506,29 @@
// Node to bind an context and/or some arguments to a function, returning a new function // Node to bind an context and/or some arguments to a function, returning a new function
// After `Underscore.bind` from [Underscore](http://documentcloud.github.com/underscore/). // After `Underscore.bind` from [Underscore](http://documentcloud.github.com/underscore/).
exports.CurryNode = (function() { exports.CurryNode = (function() {
CurryNode = function CurryNode(meth, bind, args) { CurryNode = function CurryNode(meth, args) {
this.children = flatten([(this.meth = meth), (this.bind = bind || literal('this')), (this.args = (args || []))]); this.children = flatten([(this.meth = meth), (this.context = args[0]), (this.args = (args.slice(1) || []))]);
return this; return this;
}; };
__extends(CurryNode, CallNode); __extends(CurryNode, CallNode);
CurryNode.prototype.type = 'Curry'; CurryNode.prototype.type = 'Curry';
CurryNode.prototype.code = "function(func, obj, args) {\n return function() {\n return func.apply(obj || {}, args.concat(__slice.call(arguments, 0)));\n };\n}";
CurryNode.prototype.slice = "Array.prototype.slice";
CurryNode.prototype.arguments = function arguments(o) { CurryNode.prototype.arguments = function arguments(o) {
var _a, _b, _c, arg; var _a, _b, _c, arg;
_a = this.args; _a = this.args;
for (_b = 0, _c = _a.length; _b < _c; _b++) { for (_b = 0, _c = _a.length; _b < _c; _b++) {
arg = _a[_b]; arg = _a[_b];
if (arg instanceof SplatNode) { if (arg instanceof SplatNode) {
return literal(this.compile_splat_arguments(o)); return this.compile_splat_arguments(o);
} }
} }
return new ArrayNode(this.args); return (new ArrayNode(this.args)).compile(o);
}; };
CurryNode.prototype.compile_node = function compile_node(o) { CurryNode.prototype.compile_node = function compile_node(o) {
var call, ref; var body, curried, curry;
o.scope.assign('__curry', this.code, true); body = Expressions.wrap([literal('func.apply(obj || {}, args.concat(Array.prototype.slice.call(arguments, 0)));')]);
o.scope.assign('__slice', this.slice, true); curried = new CodeNode([], body);
ref = new ValueNode(literal('__curry')); curry = new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried]));
call = new CallNode(ref, [this.meth, this.bind, this.arguments(o)]); return (new ParentheticalNode(new CallNode(curry, [this.meth, this.context, literal(this.arguments(o))]))).compile(o);
return call.compile(o);
}; };
return CurryNode; return CurryNode;
}).call(this); }).call(this);

File diff suppressed because one or more lines are too long

View File

@@ -384,7 +384,7 @@
// Tokens that indicate the close of a clause of an expression. // Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END); EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);
// Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation. // Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END']; IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', 'CURRY'];
// If preceded by an `IMPLICIT_FUNC`, indicates a function invocation. // If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'EXTENSION', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '@', '->', '=>', '[', '(', '{']; IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'EXTENSION', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '@', '->', '=>', '[', '(', '{'];
// Tokens indicating that the implicit call must enclose a block of expressions. // Tokens indicating that the implicit call must enclose a block of expressions.

View File

@@ -265,10 +265,7 @@ grammar: {
] ]
Curry: [ Curry: [
o "Value CURRY Value CURRY_SEPARATOR Arguments", -> new CurryNode $1, $3, $5 o "Value CURRY Arguments", -> new CurryNode $1, $3
o "Value CURRY Arguments", -> new CurryNode $1, undefined, $3
o "Value CURRY Value", -> new CurryNode $1, $3
o "Value CURRY", -> new CurryNode $1
] ]
# Extending an object by setting its prototype chain to reference a parent # Extending an object by setting its prototype chain to reference a parent

View File

@@ -244,24 +244,9 @@ exports.Lexer: class Lexer
# Here we detect the currying operator and change local state in order to # Here we detect the currying operator and change local state in order to
# parse subsequent tokens accordingly. # parse subsequent tokens accordingly.
curry_token: -> curry_token: ->
if @currying? if @match(CURRY, 1)
if @match(/^\(/, 0) and [',', '<-'].indexOf(@value()) isnt -1
@token 'CALL_START', '('
@i += 1
@currying: undefined
true
else if @match(CURRY_SEPARATOR, 0)
@token 'CURRY_SEPARATOR', ','
@i += 1
true
else if not @match(/^\s*[\w@\$_\(]/, 0)
@currying: undefined
false
else if @match(CURRY, 1)
@i += 2 @i += 2
@chunk = @code.slice(@i)
@token 'CURRY', '<-' @token 'CURRY', '<-'
@currying: true if @match(/^(\s*[\w@\$_\(])/, 1)
true true
# We treat all other single characters as a token. Eg.: `( ) , . !` # We treat all other single characters as a token. Eg.: `( ) , . !`

View File

@@ -393,29 +393,20 @@ exports.CallNode: class CallNode extends BaseNode
# After `Underscore.bind` from [Underscore](http://documentcloud.github.com/underscore/). # After `Underscore.bind` from [Underscore](http://documentcloud.github.com/underscore/).
exports.CurryNode: class CurryNode extends CallNode exports.CurryNode: class CurryNode extends CallNode
type: 'Curry' type: 'Curry'
code: '''
function(func, obj, args) {
return function() {
return func.apply(obj || {}, args.concat(__slice.call(arguments, 0)));
};
}
'''
slice: "Array.prototype.slice"
constructor: (meth, bind, args) -> constructor: (meth, args) ->
@children: flatten [@meth: meth, @bind: bind or literal('this'), @args: (args or [])] @children: flatten [@meth: meth, @context: args[0], @args: (args.slice(1) or [])]
arguments: (o) -> arguments: (o) ->
for arg in @args for arg in @args
return literal(@compile_splat_arguments(o)) if arg instanceof SplatNode return @compile_splat_arguments(o) if arg instanceof SplatNode
new ArrayNode(@args) (new ArrayNode(@args)).compile o
compile_node: (o) -> compile_node: (o) ->
o.scope.assign('__curry', @code, true) body: Expressions.wrap([literal('func.apply(obj || {}, args.concat(Array.prototype.slice.call(arguments, 0)));')])
o.scope.assign('__slice', @slice, true) curried: new CodeNode([], body)
ref: new ValueNode literal('__curry') curry: new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried]))
call: new CallNode ref, [@meth, @bind, @arguments(o)] (new ParentheticalNode(new CallNode(curry, [@meth, @context, literal(@arguments(o))]))).compile o
call.compile(o)

View File

@@ -257,7 +257,7 @@ EXPRESSION_END: pair[1] for pair in BALANCED_PAIRS
EXPRESSION_CLOSE: ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END EXPRESSION_CLOSE: ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
# Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation. # Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation.
IMPLICIT_FUNC: ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END'] IMPLICIT_FUNC: ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', 'CURRY']
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation. # If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL: ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', IMPLICIT_CALL: ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START',

View File

@@ -2,8 +2,8 @@ f: (x,y,z) ->
x*y*z*((@num or 4) + 5) x*y*z*((@num or 4) + 5)
obj: {num: 5} obj: {num: 5}
g: f <- obj, (1,1) g: f <- obj, 1,1
h: f <- (1,2) h: f <- {}, 1,2
i: f <- obj i: f <- obj
ok g(2) is 20 ok g(2) is 20