1
0
Fork 0
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:
Jeremy Ashkenas 2009-12-13 20:29:44 -05:00
parent 91ac495c8c
commit 896440df7c
8 changed files with 868 additions and 4056 deletions

View file

@ -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

View file

@ -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()

View file

@ -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:

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

1274
parser.rb

File diff suppressed because it is too large Load diff

View file

@ -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