mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
first draft of adding classes to CoffeeScript
This commit is contained in:
parent
7d39fe1c56
commit
1c7e4c4203
10 changed files with 321 additions and 168 deletions
|
@ -288,7 +288,7 @@
|
|||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(super|this|extends)\b</string>
|
||||
<string>\b(super|this|extends|class)\b</string>
|
||||
<key>name</key>
|
||||
<string>variable.language.coffee</string>
|
||||
</dict>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
}
|
||||
};
|
||||
// Precedence ===========================================================
|
||||
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
|
||||
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
|
||||
// Grammar ==============================================================
|
||||
grammar = {
|
||||
// All parsing will end in this rule, being the trunk of the AST.
|
||||
|
@ -41,7 +41,7 @@
|
|||
],
|
||||
// All types of expressions in our language. The basic unit of CoffeeScript
|
||||
// is the expression.
|
||||
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("Throw"), o("Return"), o("While"), o("For"), o("Switch"), o("Extends"), o("Splat"), o("Existence"), o("Comment")],
|
||||
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("Throw"), o("Return"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence"), o("Comment")],
|
||||
// A block of expressions. Note that the Rewriter will convert some postfix
|
||||
// forms into blocks for us, by altering the token stream.
|
||||
Block: [o("INDENT Expressions OUTDENT", function() {
|
||||
|
@ -292,6 +292,19 @@
|
|||
// An object literal.
|
||||
Object: [o("{ AssignList }", function() {
|
||||
return new ObjectNode($2);
|
||||
}), o("{ IndentedAssignList }", function() {
|
||||
return new ObjectNode($2);
|
||||
})
|
||||
],
|
||||
// A class literal.
|
||||
Class: [o("CLASS Value", function() {
|
||||
return new ClassNode($2);
|
||||
}), o("CLASS Value EXTENDS Value", function() {
|
||||
return new ClassNode($2, $4);
|
||||
}), o("CLASS Value IndentedAssignList", function() {
|
||||
return new ClassNode($2, null, $3);
|
||||
}), o("CLASS Value EXTENDS Value IndentedAssignList", function() {
|
||||
return new ClassNode($2, $4, $5);
|
||||
})
|
||||
],
|
||||
// Assignment within an object literal (comma or newline separated).
|
||||
|
@ -305,7 +318,10 @@
|
|||
return $1.concat([$3]);
|
||||
}), o("AssignList , TERMINATOR AssignObj", function() {
|
||||
return $1.concat([$4]);
|
||||
}), o("INDENT AssignList OUTDENT", function() {
|
||||
})
|
||||
],
|
||||
// A list of assignments in a block indentation.
|
||||
IndentedAssignList: [o("INDENT AssignList OUTDENT", function() {
|
||||
return $2;
|
||||
})
|
||||
],
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
exports.Lexer = (lex = function lex() { });
|
||||
// Constants ============================================================
|
||||
// Keywords that CoffeScript shares in common with JS.
|
||||
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends"];
|
||||
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends", "class"];
|
||||
// CoffeeScript-only keywords -- which we're more relaxed about allowing.
|
||||
COFFEE_KEYWORDS = ["then", "unless", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "of", "by", "where", "when"];
|
||||
// The list of keywords passed verbatim to the parser.
|
||||
KEYWORDS = JS_KEYWORDS.concat(COFFEE_KEYWORDS);
|
||||
// The list of keywords that are reserved by JavaScript, but not used, and aren't
|
||||
// used by CoffeeScript. Using these will throw an error at compile time.
|
||||
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "class", "const", "let", "debugger", "enum", "export", "import", "native"];
|
||||
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "const", "let", "debugger", "enum", "export", "import", "native"];
|
||||
// JavaScript keywords and reserved words together, excluding CoffeeScript ones.
|
||||
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
|
||||
// Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
|
||||
|
|
44
lib/nodes.js
44
lib/nodes.js
|
@ -1,5 +1,5 @@
|
|||
(function(){
|
||||
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, inherit, merge, statement;
|
||||
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, inherit, merge, statement;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
(typeof process !== "undefined" && process !== null) ? process.mixin(require('scope')) : (this.exports = this);
|
||||
// Some helper functions
|
||||
|
@ -658,6 +658,44 @@
|
|||
return '{' + inner + '}';
|
||||
}
|
||||
}));
|
||||
// A class literal, including optional superclass and constructor.
|
||||
ClassNode = (exports.ClassNode = inherit(BaseNode, {
|
||||
type: 'Class',
|
||||
constructor: function constructor(variable, parent, props) {
|
||||
this.children = compact(flatten([(this.variable = variable), (this.parent = parent), (this.properties = props || [])]));
|
||||
return this;
|
||||
},
|
||||
compile_node: function compile_node(o) {
|
||||
var _a, _b, _c, construct, extension, func, prop, props, ret, returns, val;
|
||||
extension = this.parent && new ExtendsNode(this.variable, this.parent);
|
||||
constructor = null;
|
||||
props = new Expressions();
|
||||
o.top = true;
|
||||
ret = del(o, 'returns');
|
||||
_a = this.properties;
|
||||
for (_b = 0, _c = _a.length; _b < _c; _b++) {
|
||||
prop = _a[_b];
|
||||
if (prop.variable.base.value === 'constructor') {
|
||||
func = prop.value;
|
||||
func.body.push(new ReturnNode(new LiteralNode('this')));
|
||||
constructor = new AssignNode(this.variable, func);
|
||||
} else {
|
||||
val = new ValueNode(this.variable, [new AccessorNode(prop.variable, 'prototype')]);
|
||||
prop = new AssignNode(val, prop.value);
|
||||
props.push(prop);
|
||||
}
|
||||
}
|
||||
if (!(constructor)) {
|
||||
constructor = new AssignNode(this.variable, new CodeNode());
|
||||
}
|
||||
construct = this.idt() + constructor.compile(o) + ';\n';
|
||||
props = props.empty() ? '' : props.compile(o) + '\n';
|
||||
extension = extension ? extension.compile(o) + '\n' : '';
|
||||
returns = ret ? '\n' + this.idt() + 'return ' + this.variable.compile(o) + ';' : '';
|
||||
return construct + extension + props + returns;
|
||||
}
|
||||
}));
|
||||
statement(ClassNode);
|
||||
// An array literal.
|
||||
ArrayNode = (exports.ArrayNode = inherit(BaseNode, {
|
||||
type: 'Array',
|
||||
|
@ -826,8 +864,8 @@
|
|||
CodeNode = (exports.CodeNode = inherit(BaseNode, {
|
||||
type: 'Code',
|
||||
constructor: function constructor(params, body, tag) {
|
||||
this.params = params;
|
||||
this.body = body;
|
||||
this.params = params || [];
|
||||
this.body = body || new Expressions();
|
||||
this.bound = tag === 'boundfunc';
|
||||
return this;
|
||||
},
|
||||
|
|
320
lib/parser.js
320
lib/parser.js
File diff suppressed because one or more lines are too long
|
@ -31,7 +31,7 @@ operators: [
|
|||
["right", 'INDENT']
|
||||
["left", 'OUTDENT']
|
||||
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
|
||||
["right", 'FOR', 'NEW', 'SUPER']
|
||||
["right", 'FOR', 'NEW', 'SUPER', 'CLASS']
|
||||
["left", 'EXTENDS']
|
||||
["right", 'ASSIGN', 'RETURN']
|
||||
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']
|
||||
|
@ -72,6 +72,7 @@ grammar: {
|
|||
o "For"
|
||||
o "Switch"
|
||||
o "Extends"
|
||||
o "Class"
|
||||
o "Splat"
|
||||
o "Existence"
|
||||
o "Comment"
|
||||
|
@ -257,6 +258,15 @@ grammar: {
|
|||
# An object literal.
|
||||
Object: [
|
||||
o "{ AssignList }", -> new ObjectNode($2)
|
||||
o "{ IndentedAssignList }", -> new ObjectNode($2)
|
||||
]
|
||||
|
||||
# A class literal.
|
||||
Class: [
|
||||
o "CLASS Value", -> new ClassNode($2)
|
||||
o "CLASS Value EXTENDS Value", -> new ClassNode($2, $4)
|
||||
o "CLASS Value IndentedAssignList", -> new ClassNode($2, null, $3)
|
||||
o "CLASS Value EXTENDS Value IndentedAssignList", -> new ClassNode($2, $4, $5)
|
||||
]
|
||||
|
||||
# Assignment within an object literal (comma or newline separated).
|
||||
|
@ -266,6 +276,10 @@ grammar: {
|
|||
o "AssignList , AssignObj", -> $1.concat [$3]
|
||||
o "AssignList TERMINATOR AssignObj", -> $1.concat [$3]
|
||||
o "AssignList , TERMINATOR AssignObj", -> $1.concat [$4]
|
||||
]
|
||||
|
||||
# A list of assignments in a block indentation.
|
||||
IndentedAssignList: [
|
||||
o "INDENT AssignList OUTDENT", -> $2
|
||||
]
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ JS_KEYWORDS: [
|
|||
"break", "continue",
|
||||
"for", "in", "while",
|
||||
"delete", "instanceof", "typeof",
|
||||
"switch", "super", "extends"
|
||||
"switch", "super", "extends", "class"
|
||||
]
|
||||
|
||||
# CoffeeScript-only keywords -- which we're more relaxed about allowing.
|
||||
|
@ -37,7 +37,7 @@ KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS
|
|||
# The list of keywords that are reserved by JavaScript, but not used, and aren't
|
||||
# used by CoffeeScript. Using these will throw an error at compile time.
|
||||
RESERVED: [
|
||||
"case", "default", "do", "function", "var", "void", "with", "class"
|
||||
"case", "default", "do", "function", "var", "void", "with"
|
||||
"const", "let", "debugger", "enum", "export", "import", "native"
|
||||
]
|
||||
|
||||
|
|
|
@ -522,6 +522,43 @@ ObjectNode: exports.ObjectNode: inherit BaseNode, {
|
|||
|
||||
}
|
||||
|
||||
# A class literal, including optional superclass and constructor.
|
||||
ClassNode: exports.ClassNode: inherit BaseNode, {
|
||||
type: 'Class'
|
||||
|
||||
constructor: (variable, parent, props) ->
|
||||
@children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
|
||||
this
|
||||
|
||||
compile_node: (o) ->
|
||||
extension: @parent and new ExtendsNode(@variable, @parent)
|
||||
constructor: null
|
||||
props: new Expressions()
|
||||
o.top: true
|
||||
ret: del o, 'returns'
|
||||
|
||||
for prop in @properties
|
||||
if prop.variable.base.value is 'constructor'
|
||||
func: prop.value
|
||||
func.body.push(new ReturnNode(new LiteralNode('this')))
|
||||
constructor: new AssignNode(@variable, func)
|
||||
else
|
||||
val: new ValueNode(@variable, [new AccessorNode(prop.variable, 'prototype')])
|
||||
prop: new AssignNode(val, prop.value)
|
||||
props.push prop
|
||||
|
||||
constructor: new AssignNode(@variable, new CodeNode()) unless constructor
|
||||
|
||||
construct: @idt() + constructor.compile(o) + ';\n'
|
||||
props: if props.empty() then '' else props.compile(o) + '\n'
|
||||
extension: if extension then extension.compile(o) + '\n' else ''
|
||||
returns: if ret then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else ''
|
||||
construct + extension + props + returns
|
||||
|
||||
}
|
||||
|
||||
statement ClassNode
|
||||
|
||||
# An array literal.
|
||||
ArrayNode: exports.ArrayNode: inherit BaseNode, {
|
||||
type: 'Array'
|
||||
|
@ -650,8 +687,8 @@ CodeNode: exports.CodeNode: inherit BaseNode, {
|
|||
type: 'Code'
|
||||
|
||||
constructor: (params, body, tag) ->
|
||||
@params: params
|
||||
@body: body
|
||||
@params: params or []
|
||||
@body: body or new Expressions()
|
||||
@bound: tag is 'boundfunc'
|
||||
this
|
||||
|
||||
|
|
36
test/test_classes.coffee
Normal file
36
test/test_classes.coffee
Normal file
|
@ -0,0 +1,36 @@
|
|||
class Base
|
||||
func: (string) ->
|
||||
'zero/' + string
|
||||
|
||||
class FirstChild extends Base
|
||||
func: (string) ->
|
||||
super('one/') + string
|
||||
|
||||
class SecondChild extends FirstChild
|
||||
func: (string) ->
|
||||
super('two/') + string
|
||||
|
||||
class ThirdChild extends SecondChild
|
||||
constructor: ->
|
||||
@array: [1, 2, 3]
|
||||
func: (string) ->
|
||||
super('three/') + string
|
||||
|
||||
result: (new ThirdChild()).func 'four'
|
||||
|
||||
ok result is 'zero/one/two/three/four'
|
||||
|
||||
|
||||
class TopClass
|
||||
constructor: (arg) ->
|
||||
@prop: 'top-' + arg
|
||||
|
||||
class SuperClass extends TopClass
|
||||
constructor: (arg) ->
|
||||
super 'super-' + arg
|
||||
|
||||
class SubClass extends SuperClass
|
||||
constructor: ->
|
||||
super 'sub'
|
||||
|
||||
ok (new SubClass()).prop is 'top-super-sub'
|
Loading…
Reference in a new issue