- 
                Notifications
    You must be signed in to change notification settings 
- Fork 2k
          Refactor Literal into several subtypes
          #4198
        
          New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| Looks pretty good on the surface. Can you please add a test with the new (fixed) behavior of  | 
| I actually thought about adding a test, but then I chose not to because I wasn’t sure if we’d then lock us down on implementation details. I was thinking something like this: nonce = {}
fn = null
(->
  fn = => `this`
).call null
eq nonce, fn.call nonceBut let’s say we wanted to compile  | 
| This may be a little out of scope, but I don't think that we ever want to compile CS  | 
        
          
                src/nodes.coffee
              
                Outdated
          
        
      There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is indented too much, I think.
| Fixed indentation, added test … and also removed  (Regarding the test: What if we decide to compile to ES5 some day?  | 
| @michaelficarra Do you have any opinion on this PR? (Asking since you 👍ed #4192.) | 
        
          
                src/nodes.coffee
              
                Outdated
          
        
      | isComplex: NO | ||
| compileNode: -> [@makeCode "null"] | ||
| class exports.Null extends Literal | ||
| constructor: -> super 'null' | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ThisLiteral constructor is on two lines and this is on one line. Be consistent.
| Agreed with @vendethiel about super having its own node. But I think it should be done as special case subclasses of Call and Access. That's the only way super is used after all. | 
| Special case of Call and Access? Mind elaborating on that? | 
| Sorry, I had ECMAScript super on the mind. You're right, just Call. | 
| I've fixed most things now. Would be happy for some feedback regarding IdentifierLiteral and SuperCall. | 
        
          
                test/functions.coffee
              
                Outdated
          
        
      | nonceB = {} | ||
| fn = null | ||
| (-> | ||
| fn = => [this is nonceA, `this` is nonceB] | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn = => this is nonceA and `this` is nonceBAnd then you can just ok it below.
| # they can also serve as keys in object literals. | ||
| AlphaNumeric: [ | ||
| o 'NUMBER', -> new Literal $1 | ||
| o 'NUMBER', -> new NumberLiteral $1 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to separate InfinityLiteral from NumberLiteral. Whenever parseFloat returns an infinity value, we can render it simply as 2e308.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$ bin/coffee -ne 'Infinity'
Block
  Value IdentifierLiteral: Infinity
Hehe.
CoffeeScript actually allows Infinity = 0. (That's not valid in strict mode.) Not sure if it's intentional.
Don't really know what to do now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we don't really do "strict"... #2337 has been open for a while.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we have test/strict.coffee. #1547
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lydell I'm not talking about a reference to the Infinity global, I'm talking about infinity values (such as the initial value of the Infinity global or the value created by the literals 2e308 or 500 9s in a row).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this what you mean?
exports.NumberLiteral = class NumberLiteral extends Literal
  constructor: (@value) ->
    return new InfinityLiteral if parseFloat(@value) is Infinity
exports.InfinityLiteral = class InfinityLiteral extends Literal
  constructor: ->
    super '2e308'Or should we do the parseFloat check already in the lexer and output an INFINITY token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latter please. Sorry for the delay here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (See the next line.)
5495920    to
    e6519e7      
    Compare
  
    |  | ||
| test "Infinity", -> | ||
| eq Infinity, CoffeeScript.eval "0b#{Array(1024 + 1).join('1')}" | ||
| eq Infinity, CoffeeScript.eval "0o#{Array(342 + 1).join('7')}" | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above two lines actually failed before adding InfinityLiteral. Invalid JS was generated: 0xInfinity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome 😄.
| InfinityLiteral is done. I’ll now continue with improving IdentifierLiteral. | 
| @lydell Maybe submit it as a separate PR? This should be good to merge right now, right? | 
Previously, the parser created `Literal` nodes for many things. This resulted in information loss. Instead of being able to check the node type, we had to use regexes to tell the different types of `Literal`s apart. That was a bit like parsing literals twice: Once in the lexer, and once (or more) in the compiler. It also caused problems, such as `` `this` `` and `this` being indistinguishable (fixes jashkenas#2009). Instead returning `new Literal` in the grammar, subtypes of it are now returned instead, such as `NumberLiteral`, `StringLiteral` and `IdentifierLiteral`. `new Literal` by itself is only used to represent code chunks that fit no category. (While mentioning `NumberLiteral`, there's also `InfinityLiteral` now, which is a subtype of `NumberLiteral`.) `StringWithInterpolations` has been added as a subtype of `Parens`, and `RegexWithInterpolations` as a subtype of `Call`. This makes it easier for other programs to make use of CoffeeScript's "AST" (nodes). For example, it is now possible to distinguish between `"a #{b} c"` and `"a " + b + " c"`. Fixes jashkenas#4192. `SuperCall` has been added as a subtype of `Call`. Note, though, that some information is still lost, especially in the lexer. For example, there is no way to distinguish a heredoc from a regular string, or a heregex without interpolations from a regular regex. Binary and octal number literals are indistinguishable from hexadecimal literals. After the new subtypes were added, they were taken advantage of, removing most regexes in nodes.coffee. `SIMPLENUM` (which matches non-hex integers) had to be kept, though, because such numbers need special handling in JavaScript (for example in `1..toString()`). An especially nice hack to get rid of was using `new String()` for the token value for reserved identifiers (to be able to set a property on them which could survive through the parser). Now it's a good old regular string. In range literals, slices, splices and for loop steps when number literals are involved, CoffeeScript can do some optimizations, such as precomputing the value of, say, `5 - 3` (outputting `2` instead of `5 - 3` literally). As a side bonus, this now also works with hexadecimal number literals, such as `0x02`. Finally, this also improves the output of `coffee --nodes`: # Before: $ bin/coffee -ne 'while true "#{a}" break' Block While Value Bool Block Value Parens Block Op + Value """" Value Parens Block Value "a" "break" # After: $ bin/coffee -ne 'while true "#{a}" break' Block While Value BooleanLiteral: true Block Value StringWithInterpolations Block Op + Value StringLiteral: "" Value Parens Block Value IdentifierLiteral: a StatementLiteral: break
| Ok, I’ll do it in a separate PR. Yes, I consider this PR good to merge. | 
| 👍 | 
Refactor `Literal` into several subtypes
| Thanks. Great work, @lydell. | 
Previously, the parser created
Literalnodes for many things. This resulted ininformation loss. Instead of being able to check the node type, we had to use
regexes to tell the different types of
Literals apart. That was a bit likeparsing literals twice: Once in the lexer, and once (or more) in the compiler.
It also caused problems, such as
thisandthisbeing indistinguishable(fixes #2009).
Instead returning
new Literalin the grammar, subtypes of it are now returnedinstead, such as
NumberLiteral,StringLiteralandIdentifierLiteral.new Literalby itself is only used to represent code chunks that fit no category.StringWithInterpolationshas been added as a subtype ofParens, andRegexWithInterpolationsas a subtype ofCall. This makes it easier for otherprograms to make use of CoffeeScript's "AST" (nodes). For example, it is now
possible to distinguish between
"a #{b} c"and"a " + b + " c". Fixes #4192.SuperCallhas been added as a subtype ofCall.Note, though, that some information is still lost, especially in the lexer. For
example, there is no way to distinguish a heredoc from a regular string, or a
heregex without interpolations from a regular regex.
After the new subtypes were added, they were taken advantage of, removing most
regexes in nodes.coffee.
SIMPLENUM(which matches non-hex integers) had to bekept, though, because such numbers need special handling in JavaScript (for
example in
1..toString()).An especially nice hack to get rid of was using
new String()for the tokenvalue for reserved identifiers (to be able to set a property on them which could
survive through the parser). Now it's a good old regular string.
In range literals, slices, splices and for loop steps when number literals
are involved, CoffeeScript can do some optimizations, such as precomputing the
value of, say,
5 - 3(outputting2instead of5 - 3literally). As a sidebonus, this now also works with hexadecimal number literals, such as
0x02.Finally, this also improves the output of
coffee --nodes: