mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
more more, including &&=, ||=
This commit is contained in:
parent
91ac495c8c
commit
896440df7c
8 changed files with 868 additions and 4056 deletions
7
code.jaa
7
code.jaa
|
@ -1,6 +1,5 @@
|
|||
# TODO: switch/case statements
|
||||
# new Function()
|
||||
# Regexes
|
||||
# Better block delimiters
|
||||
|
||||
# Functions:
|
||||
square: x => x * x.
|
||||
|
@ -50,6 +49,10 @@ race: =>
|
|||
if tired then return sleep().
|
||||
race().
|
||||
|
||||
# Conditional operators:
|
||||
good ||= evil
|
||||
wine &&= cheese
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
138
documents.jaa
138
documents.jaa
|
@ -1,78 +1,72 @@
|
|||
# Document Model
|
||||
dc.model.Document: dc.Model.extend({
|
||||
|
||||
# constructor : attributes => this.base(attributes).
|
||||
#
|
||||
# # For display, show either the highlighted search results, or the summary,
|
||||
# # if no highlights are available.
|
||||
# # The import process will take care of this in the future, but the inline
|
||||
# # version of the summary has all runs of whitespace squeezed out.
|
||||
# displaySummary : =>
|
||||
# text: this.get('highlight') or this.get('summary')
|
||||
# if text then text.replace(/\s+/g, ' ') else ''
|
||||
# .
|
||||
#
|
||||
# # Return a list of the document's metadata. Think about caching this on the
|
||||
# # document by binding to Metadata, instead of on-the-fly.
|
||||
# metadata: =>
|
||||
# docId: this.id
|
||||
# _.select(Metadata.models(),
|
||||
# datum => _.any(datum.get('instances'),
|
||||
# instance => instance.document_id == docId.
|
||||
# ).
|
||||
# ).
|
||||
# .
|
||||
#
|
||||
# bookmark: pageNumber =>
|
||||
# bookmark: new dc.model.Bookmark({title: this.get('title'), page_number: pageNumber, document_id: this.id})
|
||||
# Bookmarks.create(bookmark)
|
||||
# .
|
||||
#
|
||||
# # Inspect.
|
||||
# toString: => 'Document ' + this.id + ' "' + this.get('title') + '"'.
|
||||
constructor: attributes => this.base(attributes).
|
||||
|
||||
# For display, show either the highlighted search results, or the summary,
|
||||
# if no highlights are available.
|
||||
# The import process will take care of this in the future, but the inline
|
||||
# version of the summary has all runs of whitespace squeezed out.
|
||||
displaySummary: =>
|
||||
text: this.get('highlight') or this.get('summary')
|
||||
if text then text.replace(/\s+/g, ' ') else ''..
|
||||
|
||||
# Return a list of the document's metadata. Think about caching this on the
|
||||
# document by binding to Metadata, instead of on-the-fly.
|
||||
metadata: =>
|
||||
docId: this.id
|
||||
_.select(Metadata.models()
|
||||
datum => _.any(datum.get('instances')
|
||||
instance => instance.document_id == docId.).).
|
||||
|
||||
bookmark: pageNumber =>
|
||||
bookmark: new dc.model.Bookmark({title: this.get('title'), page_number: pageNumber, document_id: this.id})
|
||||
Bookmarks.create(bookmark).
|
||||
|
||||
# Inspect.
|
||||
toString: => 'Document ' + this.id + ' "' + this.get('title') + '"'.
|
||||
|
||||
})
|
||||
|
||||
# # Document Set
|
||||
# dc.model.DocumentSet : dc.model.RESTfulSet.extend({
|
||||
#
|
||||
# resource : 'documents'
|
||||
#
|
||||
# SELECTION_CHANGED : 'documents:selection_changed'
|
||||
#
|
||||
# constructor : options =>
|
||||
# this.base(options)
|
||||
# _.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText')
|
||||
#
|
||||
# selected : => _.select(this.models(), m => m.get('selected'))
|
||||
#
|
||||
# selectedIds : => _.pluck(this.selected(), 'id')
|
||||
#
|
||||
# countSelected : => this.selected().length
|
||||
#
|
||||
# downloadSelectedViewers : =>
|
||||
# dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip');
|
||||
#
|
||||
# downloadSelectedPDF : =>
|
||||
# return window.open(this.selected()[0].get('pdf_url')) if this.countSelected() <= 1
|
||||
# dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip')
|
||||
#
|
||||
# downloadSelectedFullText : =>
|
||||
# return window.open(this.selected()[0].get('full_text_url')) if this.countSelected() <= 1
|
||||
# dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip')
|
||||
#
|
||||
# # We override "_onModelEvent" to fire selection changed events when documents
|
||||
# # change their selected state.
|
||||
# _onModelEvent : e, model =>
|
||||
# this.base(e, model)
|
||||
# fire : (e == dc.Model.CHANGED and model.hasChanged('selected'))
|
||||
# _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this)) if fire
|
||||
# }
|
||||
#
|
||||
# })
|
||||
#
|
||||
# # The main set of Documents, used by the search tab.
|
||||
# window.Documents : new dc.model.DocumentSet()
|
||||
#
|
||||
# # The set of documents that is used to look at a particular label.
|
||||
# dc.app.LabeledDocuments : new dc.model.DocumentSet()
|
||||
# Document Set
|
||||
dc.model.DocumentSet: dc.model.RESTfulSet.extend({
|
||||
|
||||
resource: 'documents'
|
||||
|
||||
SELECTION_CHANGED: 'documents:selection_changed'
|
||||
|
||||
constructor: options =>
|
||||
this.base(options)
|
||||
_.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText').
|
||||
|
||||
selected: => _.select(this.models(), m => m.get('selected').).
|
||||
|
||||
selectedIds: => _.pluck(this.selected(), 'id').
|
||||
|
||||
countSelected: => this.selected().length.
|
||||
|
||||
downloadSelectedViewers: =>
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip').
|
||||
|
||||
downloadSelectedPDF: =>
|
||||
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('pdf_url')).
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip').
|
||||
|
||||
downloadSelectedFullText: =>
|
||||
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('full_text_url')).
|
||||
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip').
|
||||
|
||||
# We override "_onModelEvent" to fire selection changed events when documents
|
||||
# change their selected state.
|
||||
_onModelEvent: e, model =>
|
||||
this.base(e, model)
|
||||
fire: e == dc.Model.CHANGED and model.hasChanged('selected')
|
||||
if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this))..
|
||||
|
||||
})
|
||||
|
||||
# The main set of Documents, used by the search tab.
|
||||
window.Documents: new dc.model.DocumentSet()
|
||||
|
||||
# The set of documents that is used to look at a particular label.
|
||||
dc.app.LabeledDocuments: new dc.model.DocumentSet()
|
||||
|
|
31
grammar.y
31
grammar.y
|
@ -8,8 +8,8 @@ token STRING
|
|||
token REGEX
|
||||
token TRUE FALSE NULL
|
||||
token IDENTIFIER PROPERTY_ACCESS
|
||||
token CODE
|
||||
token RETURN
|
||||
token CODE PARAM
|
||||
token NEW RETURN
|
||||
|
||||
prechigh
|
||||
nonassoc UMINUS NOT '!'
|
||||
|
@ -128,27 +128,36 @@ rule
|
|||
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
# Add ||= &&=
|
||||
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
||||
;
|
||||
|
||||
|
||||
# Method definition
|
||||
Code:
|
||||
"=>" Expressions "." { result = CodeNode.new([], val[1]) }
|
||||
| ParamList
|
||||
"=>" Expressions "." { result = CodeNode.new(val[0], val[2]) }
|
||||
ParamList "=>" Expression { result = CodeNode.new(val[0], val[2]) }
|
||||
| ParamList "=>" Expressions "." { result = CodeNode.new(val[0], val[2]) }
|
||||
;
|
||||
|
||||
ParamList:
|
||||
/* nothing */ { result = [] }
|
||||
| IDENTIFIER { result = val }
|
||||
| ParamList "," IDENTIFIER { result = val[0] << val[2] }
|
||||
| PARAM { result = val }
|
||||
| ParamList "," PARAM { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
Variable:
|
||||
IDENTIFIER { result = VariableNode.new(val) }
|
||||
| Variable PROPERTY_ACCESS
|
||||
IDENTIFIER { result = val[0] << val[2] }
|
||||
| Variable Accessor { result = val[0] << val[1] }
|
||||
| Call Accessor { result = VariableNode.new(val[0], [val[1]]) }
|
||||
;
|
||||
|
||||
Accessor:
|
||||
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
|
||||
| Index { result = val[0] }
|
||||
;
|
||||
|
||||
Index:
|
||||
"[" Literal "]" { result = IndexNode.new(val[1]) }
|
||||
;
|
||||
|
||||
Object:
|
||||
|
@ -169,6 +178,7 @@ rule
|
|||
# A method call.
|
||||
Call:
|
||||
Variable "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
|
||||
| NEW Variable "(" ArgList ")" { result = CallNode.new(val[1], val[3], true) }
|
||||
;
|
||||
|
||||
# An Array.
|
||||
|
@ -181,6 +191,7 @@ rule
|
|||
/* nothing */ { result = [] }
|
||||
| Expression { result = val }
|
||||
| ArgList "," Expression { result = val[0] << val[2] }
|
||||
| ArgList Terminator Expression { result = val[0] << val[2] }
|
||||
;
|
||||
|
||||
If:
|
||||
|
|
19
lexer.rb
19
lexer.rb
|
@ -3,11 +3,11 @@ class Lexer
|
|||
KEYWORDS = ["if", "else", "then",
|
||||
"true", "false", "null",
|
||||
"and", "or", "is", "aint", "not",
|
||||
"return"]
|
||||
"new", "return"]
|
||||
|
||||
IDENTIFIER = /\A([a-zA-Z$_]\w*)/
|
||||
NUMBER = /\A([0-9]+(\.[0-9]+)?)/
|
||||
STRING = /\A["'](.*?)["']/
|
||||
STRING = /\A("(.*?)"|'(.*?)')/
|
||||
OPERATOR = /\A([+\*&|\/\-%=<>]+)/
|
||||
WHITESPACE = /\A([ \t\r]+)/
|
||||
NEWLINE = /\A([\r\n]+)/
|
||||
|
@ -61,7 +61,7 @@ class Lexer
|
|||
def string_token
|
||||
return false unless string = @chunk[STRING, 1]
|
||||
@tokens << [:STRING, string]
|
||||
@i += string.length + 2
|
||||
@i += string.length
|
||||
end
|
||||
|
||||
def regex_token
|
||||
|
@ -91,9 +91,22 @@ class Lexer
|
|||
return @i += value.length
|
||||
end
|
||||
value = @chunk[OPERATOR, 1]
|
||||
tag_parameters if value && value.match(CODE)
|
||||
value ||= @chunk[0,1]
|
||||
@tokens << [value, value]
|
||||
@i += value.length
|
||||
end
|
||||
|
||||
# The main source of ambiguity in our grammar was Parameter lists (as opposed
|
||||
# to argument lists in method calls). Tag parameter identifiers to avoid this.
|
||||
def tag_parameters
|
||||
index = 0
|
||||
loop do
|
||||
tok = @tokens[index -= 1]
|
||||
next if tok[0] == ','
|
||||
return if tok[0] != :IDENTIFIER
|
||||
tok[0] = :PARAM
|
||||
end
|
||||
end
|
||||
|
||||
end
|
78
nodes.rb
78
nodes.rb
|
@ -5,6 +5,9 @@ class Node
|
|||
def line_ending
|
||||
';'
|
||||
end
|
||||
|
||||
def compile(indent='', last=false)
|
||||
end
|
||||
end
|
||||
|
||||
# Collection of nodes each one representing an expression.
|
||||
|
@ -38,7 +41,7 @@ class LiteralNode < Node
|
|||
@value = value
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
@value.to_s
|
||||
end
|
||||
end
|
||||
|
@ -48,8 +51,8 @@ class ReturnNode < Node
|
|||
@expression = expression
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
"return #{@expression.compile(indent)};"
|
||||
def compile(indent, last=false)
|
||||
"#{indent}return #{@expression.compile(indent)};"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,20 +64,20 @@ end
|
|||
# receiver.method(argument1, argument2)
|
||||
#
|
||||
class CallNode < Node
|
||||
def initialize(variable, arguments=[])
|
||||
@variable, @arguments = variable, arguments
|
||||
def initialize(variable, arguments=[], new_instance=false)
|
||||
@variable, @arguments, @new = variable, arguments, new_instance
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
args = @arguments.map{|a| a.compile(indent) }.join(', ')
|
||||
"#{@variable.compile(indent)}(#{args})"
|
||||
prefix = @new ? "new " : ''
|
||||
"#{prefix}#{@variable.compile(indent)}(#{args})"
|
||||
end
|
||||
end
|
||||
|
||||
class VariableNode < Node
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@properties = []
|
||||
def initialize(name, properties=[])
|
||||
@name, @properties = name, properties
|
||||
end
|
||||
|
||||
def <<(other)
|
||||
|
@ -86,8 +89,30 @@ class VariableNode < Node
|
|||
return !@properties.empty?
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
[@name, @properties].flatten.join('.')
|
||||
def compile(indent, last=false)
|
||||
[@name, @properties].flatten.map { |v|
|
||||
v.respond_to?(:compile) ? v.compile(indent) : v.to_s
|
||||
}.join('')
|
||||
end
|
||||
end
|
||||
|
||||
class AccessorNode
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def compile(indent, last=false)
|
||||
".#{@name}"
|
||||
end
|
||||
end
|
||||
|
||||
class IndexNode
|
||||
def initialize(index)
|
||||
@index = index
|
||||
end
|
||||
|
||||
def compile(indent, last=false)
|
||||
"[#{@index.compile(indent)}]"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -97,10 +122,10 @@ class AssignNode < Node
|
|||
@variable, @value, @context = variable, value, context
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
return "#{@variable}: #{@value.compile(indent + TAB)}" if @context == :object
|
||||
var_part = @variable.compile(indent)
|
||||
var_part = "var " + var_part unless @variable.properties?
|
||||
var_part = "var " + var_part unless @variable.properties? || last
|
||||
"#{var_part} = #{@value.compile(indent)}"
|
||||
end
|
||||
end
|
||||
|
@ -116,6 +141,7 @@ class OpNode < Node
|
|||
"aint" => "!==",
|
||||
'not' => '!',
|
||||
}
|
||||
CONDITIONALS = ['||=', '&&=']
|
||||
|
||||
def initialize(operator, first, second=nil)
|
||||
@first, @second = first, second
|
||||
|
@ -126,9 +152,16 @@ class OpNode < Node
|
|||
@second.nil?
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
return compile_conditional(indent) if CONDITIONALS.include?(@operator)
|
||||
"(#{@first.compile(indent)} #{@operator} #{@second.compile(indent)})"
|
||||
end
|
||||
|
||||
def compile_conditional(indent)
|
||||
first, second = @first.compile(indent), @second.compile(indent)
|
||||
sym = @operator[0..1]
|
||||
"(#{first} = #{first} #{sym} #{second})"
|
||||
end
|
||||
end
|
||||
|
||||
# Method definition.
|
||||
|
@ -138,11 +171,12 @@ class CodeNode < Node
|
|||
@body = body
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
nodes = @body.reduce
|
||||
code = nodes.map { |node|
|
||||
line = node.compile(indent + TAB)
|
||||
line = "return #{line}" if node == nodes.last
|
||||
last = node == nodes.last
|
||||
line = node.compile(indent + TAB, last)
|
||||
line = "return #{line}" if last
|
||||
indent + TAB + line + node.line_ending
|
||||
}.join("\n")
|
||||
"function(#{@params.join(', ')}) {\n#{code}\n#{indent}}"
|
||||
|
@ -154,7 +188,7 @@ class ObjectNode < Node
|
|||
@properties = properties
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
props = @properties.map {|p| indent + TAB + p.compile(indent) }.join(",\n")
|
||||
"{\n#{props}\n#{indent}}"
|
||||
end
|
||||
|
@ -165,7 +199,7 @@ class ArrayNode < Node
|
|||
@objects = objects
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
objects = @objects.map {|o| o.compile(indent) }.join(', ')
|
||||
"[#{objects}]"
|
||||
end
|
||||
|
@ -188,12 +222,12 @@ class IfNode < Node
|
|||
statement? ? '' : ';'
|
||||
end
|
||||
|
||||
def compile(indent)
|
||||
def compile(indent, last=false)
|
||||
statement? ? compile_statement(indent) : compile_ternary(indent)
|
||||
end
|
||||
|
||||
def compile_statement(indent)
|
||||
if_part = "if (#{@condition.compile(indent)}) {\n#{indent + TAB}#{@body.compile(indent + TAB)}\n#{indent}}"
|
||||
if_part = "if (#{@condition.compile(indent)}) {\n#{@body.compile(indent + TAB)}\n#{indent}}"
|
||||
else_part = @else_body ? " else {\n#{@else_body.compile(indent + TAB)}\n#{indent}}" : ''
|
||||
if_part + else_part
|
||||
end
|
||||
|
|
3375
parser.output
3375
parser.output
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,7 @@
|
|||
|
||||
# Parse and print "code.jaa".
|
||||
require "parser.rb"
|
||||
js = Parser.new.parse(File.read('documents.jaa')).compile
|
||||
js = Parser.new.parse(File.read('code.jaa')).compile
|
||||
puts "\n\n"
|
||||
puts js
|
||||
|
||||
|
|
Loading…
Reference in a new issue