diff --git a/examples/whitespace.cs b/examples/whitespace.cs new file mode 100644 index 00000000..94fa5e7e --- /dev/null +++ b/examples/whitespace.cs @@ -0,0 +1,12 @@ +# square: x => x * x + +square: x => + x * x + +elements.each(el => + el.click(event => + el.show() + ) +) + +a: 5 \ No newline at end of file diff --git a/examples/whitespace.js b/examples/whitespace.js new file mode 100644 index 00000000..3d0af9e5 --- /dev/null +++ b/examples/whitespace.js @@ -0,0 +1,8 @@ +(function(){ + + // square: x => x * x + var square = function(x) { + return x * x; + }; + var a = 5; +})(); \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index c31d5196..38132871 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -15,6 +15,7 @@ token SUPER token NEWLINE token COMMENT token JS +token INDENT OUTDENT # Declare order of operations. prechigh @@ -32,7 +33,8 @@ prechigh right THROW FOR IN WHILE NEW left UNLESS IF ELSE left ":" '||:' '&&:' - right RETURN + right RETURN INDENT + left OUTDENT preclow # We expect 4 shift/reduce errors for optional syntax. @@ -84,6 +86,11 @@ rule | Comment ; + Block: + Expression Terminator { result = Expressions.new([val[0]]) } + | INDENT Expressions OUTDENT { result = val[1] } + ; + # All tokens that can terminate an expression. Terminator: "\n" @@ -191,14 +198,14 @@ rule # Function definition. Code: - ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) } - | "=>" CodeBody "." { result = CodeNode.new([], val[1]) } + ParamList "=>" CodeBody { result = CodeNode.new(val[0], val[2]) } + | "=>" CodeBody { result = CodeNode.new([], val[1]) } ; # The body of a function. CodeBody: /* nothing */ { result = Expressions.new([]) } - | Expressions { result = val[0] } + | Block { result = val[0] } ; # The parameters to a function definition. @@ -279,15 +286,15 @@ rule # Try/catch/finally exception handling blocks. Try: - TRY Expressions Catch "." { result = TryNode.new(val[1], val[2][0], val[2][1]) } + TRY Expressions Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } | TRY Expressions Catch - FINALLY Expressions "." { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } + FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } ; # A catch clause. Catch: /* nothing */ { result = [nil, nil] } - | CATCH IDENTIFIER Expressions { result = [val[1], val[2]] } + | CATCH IDENTIFIER Block { result = [val[1], val[2]] } ; # Throw an exception. @@ -302,32 +309,31 @@ rule # The while loop. (there is no do..while). While: - WHILE Expression Then - Expressions "." { result = WhileNode.new(val[1], val[3]) } + WHILE Expression Then Block { result = WhileNode.new(val[1], val[3]) } ; # Array comprehensions, including guard and current index. For: Expression FOR IDENTIFIER - IN PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) } + IN PureExpression { result = ForNode.new(val[0], val[4], val[2], nil) } | Expression FOR IDENTIFIER "," IDENTIFIER - IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) } + IN PureExpression { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) } | Expression FOR IDENTIFIER IN PureExpression - IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) } + IF Expression { result = ForNode.new(val[0], val[4], val[2], val[6]) } | Expression FOR IDENTIFIER "," IDENTIFIER IN PureExpression - IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) } + IF Expression { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) } ; # Switch/When blocks. Switch: SWITCH Expression Then - Whens "." { result = val[3].rewrite_condition(val[1]) } + Whens { result = val[3].rewrite_condition(val[1]) } | SWITCH Expression Then - Whens ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } + Whens ELSE Block { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; # The inner list of whens. @@ -338,7 +344,7 @@ rule # An individual when. When: - WHEN Expression Then Expressions { result = IfNode.new(val[1], val[3]) } + WHEN Expression Then Block { result = IfNode.new(val[1], val[3]) } ; # All of the following nutso if-else destructuring is to make the @@ -358,8 +364,8 @@ rule # Terminating else bodies are strictly optional. ElseBody - "." { result = nil } - | ELSE Expressions "." { result = val[1] } + /* nothing */ { result = nil } + | ELSE Block { result = val[1] } ; # All the alternatives for ending an if-else block. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 683de472..bb25ed07 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -28,6 +28,7 @@ module CoffeeScript COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ + INDENT = /\A\n( *)/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -45,6 +46,8 @@ module CoffeeScript @code = code.chomp # Cleanup code by remove extra line breaks @i = 0 # Current character position we're parsing @line = 1 # The current line. + @indent = 0 # The current indent level. + @indents = [] # The stack of all indent levels we are currently within. @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] while @i < @code.length @chunk = @code[@i..-1] @@ -62,6 +65,7 @@ module CoffeeScript return if js_token return if regex_token return if comment_token + return if indent_token return if whitespace_token return literal_token end @@ -118,6 +122,23 @@ module CoffeeScript @i += comment.length end + def indent_token + return false unless indent = @chunk[INDENT, 1] + size = indent.size + return literal_token if size == @indent + if size > @indent + tag = :INDENT + @indent = size + @indents << @indent + else + tag = :OUTDENT + @indents.pop + @indent = @indents.first || 0 + end + @i += (size + 1) + token(tag, size) + end + # Matches and consumes non-meaningful whitespace. def whitespace_token return false unless whitespace = @chunk[WHITESPACE, 1] @@ -182,6 +203,13 @@ module CoffeeScript @tokens.pop if last_value == "\n" end + # Close up all remaining open blocks. + def close_indentation + while indent = @indents.pop + token(:OUTDENT, @indents.first || 0) + end + end + end end \ No newline at end of file