Skip to content

Commit 230bf36

Browse files
committed
Disallow decimals without leading and trailing digits, enforce subscript indentation
1 parent 078cd66 commit 230bf36

45 files changed

Lines changed: 586 additions & 301 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/parser/expression.js

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,39 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
363363
node.object = base;
364364
node.callee = this.parseNoCallExpr();
365365
return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls);
366-
} else if (this.eat(tt.dot)) {
366+
} else if (this.match(tt.dot)) {
367+
// require indentation
368+
if (this.hasPlugin("lightscript") && this.isNonIndentedBreakFrom(startPos)) {
369+
if (this.lookahead().type === tt.num) {
370+
this.unexpected(null, "Either indent for array access or use a leading zero for a decimal.");
371+
}
372+
this.unexpected(null, "Indentation required.");
373+
}
374+
375+
// catch malformed decimals (but allow `0.0.toString()`, which is actually valid)
376+
if (this.hasPlugin("lightscript") && base.type === "NumericLiteral") {
377+
if (!(base.extra && base.extra.raw && base.extra.raw.match(/\./))) {
378+
this.unexpected(null, "Numbers with a decimal must end in a number (eg; `1.0`) in LightScript.");
379+
}
380+
}
381+
382+
this.next();
367383
const node = this.startNodeAt(startPos, startLoc);
368384
node.object = base;
369-
node.property = this.parseIdentifier(true);
370-
node.computed = false;
385+
386+
// parse arr.0 as arr[0]
387+
if (this.hasPlugin("lightscript") && this.match(tt.num)) {
388+
node.property = this.parseExprAtom();
389+
node.computed = true;
390+
} else {
391+
node.property = this.parseIdentifier(true);
392+
node.computed = false;
393+
}
371394
base = this.finishNode(node, "MemberExpression");
372395
} else if (this.hasPlugin("lightscript") && this.match(tt.elvis)) {
396+
if (this.isNonIndentedBreakFrom(startPos)) {
397+
this.unexpected(null, "Indentation required.");
398+
}
373399
// `x?.y`
374400
const node = this.startNodeAt(startPos, startLoc);
375401
const op = this.state.value;
@@ -392,7 +418,11 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
392418
this.unexpected();
393419
}
394420
base = this.finishNode(node, "SafeMemberExpression");
395-
} else if (this.hasPlugin("lightscript") && !noCalls && this.eat(tt.tilde)) {
421+
} else if (this.hasPlugin("lightscript") && !noCalls && this.match(tt.tilde)) {
422+
if (this.isNonIndentedBreakFrom(startPos)) {
423+
this.unexpected(null, "Indentation required.");
424+
}
425+
this.next();
396426
const node = this.startNodeAt(startPos, startLoc);
397427
node.left = base;
398428
// allow `this`, Identifier or MemberExpression, but not calls
@@ -402,13 +432,6 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
402432
this.expect(tt.parenL);
403433
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
404434
base = this.finishNode(node, "TildeCallExpression");
405-
} else if (this.hasPlugin("lightscript") && this.isNumberStartingWithDot() && !this.isNonIndentedBreakFrom(startPos)) {
406-
// parses x.0, x.1, etc, as x[0], x[1], etc.
407-
const node = this.startNodeAt(startPos, startLoc);
408-
node.object = base;
409-
node.property = this.parseNumericLiteralMember();
410-
node.computed = true;
411-
base = this.finishNode(node, "MemberExpression");
412435
} else if (!(this.hasPlugin("lightscript") && this.isNonIndentedBreakFrom(startPos)) && this.eat(tt.bracketL)) {
413436
const node = this.startNodeAt(startPos, startLoc);
414437
node.object = base;
@@ -712,6 +735,11 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
712735
this.unexpected(null, "Unmatched `else` (must match indentation of the line with `if`).");
713736
}
714737

738+
case tt.dot:
739+
if (this.hasPlugin("lightscript") && this.lookahead().type === tt.num) {
740+
this.unexpected(null, "Decimal numbers must be prefixed with a `0` in LightScript (eg; `0.1`).");
741+
}
742+
715743
default:
716744
this.unexpected();
717745
}

src/plugins/lightscript.js

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -134,41 +134,6 @@ pp.parseObjectComprehension = function(node) {
134134
return this.finishNode(node, "ObjectComprehension");
135135
};
136136

137-
pp.isNumberStartingWithDot = function () {
138-
return (
139-
this.match(tt.num) &&
140-
this.state.input.charCodeAt(this.state.start) === 46 // "."
141-
);
142-
};
143-
144-
// the tokenizer reads dots directly into numbers,
145-
// so "x.1" is tokenized as ["x", .1] not ["x", ".", 1]
146-
// therefore, we have to hack, reading the number back into a string
147-
// and parsing out the dot.
148-
149-
pp.parseNumericLiteralMember = function () {
150-
// 0.1 -> 1, 0 -> 0
151-
let numStr = String(this.state.value);
152-
if (numStr.indexOf("0.") === 0) {
153-
numStr = numStr.slice(2);
154-
} else if (numStr !== "0") {
155-
this.unexpected();
156-
}
157-
const num = parseInt(numStr, 10);
158-
159-
// must also remove "." from the "raw" property,
160-
// b/c that's what's inserted in code
161-
162-
const node = this.parseLiteral(num, "NumericLiteral");
163-
if (node.extra.raw.indexOf(".") === 0) {
164-
node.extra.raw = node.extra.raw.slice(1);
165-
} else {
166-
this.unexpected();
167-
}
168-
169-
return node;
170-
};
171-
172137
// c/p parseBlock
173138

174139
pp.parseWhiteBlock = function (allowDirectives?, isExpression?) {

src/tokenizer/index.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ export default class Tokenizer {
297297
//
298298
readToken_dot() {
299299
const next = this.input.charCodeAt(this.state.pos + 1);
300-
if (next >= 48 && next <= 57) {
300+
// in lightscript, numbers cannot start with a naked `.`
301+
if (next >= 48 && next <= 57 && !this.hasPlugin("lightscript")) {
301302
return this.readNumber(true);
302303
}
303304

@@ -688,9 +689,14 @@ export default class Tokenizer {
688689
const octal = this.input.charCodeAt(this.state.pos) === 48;
689690
let isFloat = false;
690691

692+
// for numeric array access (eg; arr.0), don't read floats (numbers with a decimal).
693+
const noFloatsAllowed = this.hasPlugin("lightscript") &&
694+
this.state.tokens.length > 0 &&
695+
this.state.tokens[this.state.tokens.length - 1].type === tt.dot;
696+
691697
if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number");
692698
let next = this.input.charCodeAt(this.state.pos);
693-
if (next === 46) { // '.'
699+
if (next === 46 && !noFloatsAllowed) { // '.'
694700
++this.state.pos;
695701
this.readInt(10);
696702
isFloat = true;
@@ -702,6 +708,10 @@ export default class Tokenizer {
702708
if (this.readInt(10) === null) this.raise(start, "Invalid number");
703709
isFloat = true;
704710
}
711+
// don't read the decimal if it's not followed by a number; `1.` is illegal, must do `1.0`
712+
if (this.hasPlugin("lightscript") && this.input.charCodeAt(this.state.pos - 1) === 46) {
713+
--this.state.pos;
714+
}
705715
if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.state.pos, "Identifier directly after number");
706716

707717
const str = this.input.slice(start, this.state.pos);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Numbers with a decimal must end in a number (eg; `1.0`) in LightScript. (1:3)"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Numbers with a decimal must end in a number (eg; `1.0`) in LightScript. (1:3)"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Indentation required. (2:0)"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Decimal numbers must be prefixed with a `0` in LightScript (eg; `0.1`). (1:0)"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Decimal numbers must be prefixed with a `0` in LightScript (eg; `0.1`). (1:5)"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Decimal numbers must be prefixed with a `0` in LightScript (eg; `0.1`). (1:9)"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Indentation required. (2:0)"
3+
}

0 commit comments

Comments
 (0)