-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Refactor Literal
into several subtypes
#4198
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 nonce But 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 |
isAssignable: NO | ||
isComplex: NO | ||
compileNode: (o) -> | ||
code = if o.scope.method?.bound then o.scope.method.context else @value |
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.) |
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. |
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 nonceB
And then you can just ok
it below.
] | ||
|
||
# Alphanumerics are separated from the other **Literal** matchers because | ||
# 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 9
s 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
Literal
nodes 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
Literal
s 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
this
andthis
being indistinguishable(fixes #2009).
Instead returning
new Literal
in the grammar, subtypes of it are now returnedinstead, such as
NumberLiteral
,StringLiteral
andIdentifierLiteral
.new Literal
by itself is only used to represent code chunks that fit no category.StringWithInterpolations
has been added as a subtype ofParens
, andRegexWithInterpolations
as 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.SuperCall
has 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
(outputting2
instead of5 - 3
literally). As a sidebonus, this now also works with hexadecimal number literals, such as
0x02
.Finally, this also improves the output of
coffee --nodes
: