Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/codegen/codegen_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -1261,3 +1261,17 @@ test "codegen: semicolon string empty" {
let js = compile("const s be //;;//")
assert_eq(js, "const s = \"\";\n")
}

///|
test "codegen: keyword property names (promise chain)" {
let js = compile("p.then[f].catch[g].finally[h]")
assert_eq(js, "p.then(f).catch(g).finally(h);\n")
}

///|
test "codegen: keyword property access" {
let js = compile("const t be obj.default")
assert_eq(js, "const t = obj.default;\n")
let js2 = compile("const u be obj\\.then")
assert_eq(js2, "const u = obj?.then;\n")
}
118 changes: 118 additions & 0 deletions src/parser/parser.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,124 @@ fn Parser::parse_ident_name(self : Parser) -> String {
}
}

///|
/// Map a keyword token back to its source text. Returns None for
/// non-keyword tokens (identifiers, literals, punctuation).
fn keyword_text(tok : @lexer.TokenKind) -> String? {
match tok {
Const_ => Some("const")
Let_ => Some("let")
Var_ => Some("var")
Be => Some("be")
Add => Some("add")
Sub => Some("sub")
Mul => Some("mul")
Div => Some("div")
Fdiv => Some("fdiv")
Mod => Some("mod")
Neg => Some("neg")
Pow => Some("pow")
Eq => Some("eq")
Neq => Some("neq")
Lt => Some("lt")
Gt => Some("gt")
Le => Some("le")
Ge => Some("ge")
And => Some("and")
Or => Some("or")
Not => Some("not")
Coal => Some("coal")
Band => Some("band")
Bor => Some("bor")
Bxor => Some("bxor")
Bnot => Some("bnot")
Shl => Some("shl")
Shr => Some("shr")
Ushr => Some("ushr")
Fn => Some("fn")
Return_ => Some("return")
To => Some("to")
If_ => Some("if")
Elif => Some("elif")
Else_ => Some("else")
Unless => Some("unless")
Then_ => Some("then")
While_ => Some("while")
Until => Some("until")
Do_ => Some("do")
Yield_ => Some("yield")
For_ => Some("for")
In_ => Some("in")
Range_ => Some("range")
Break_ => Some("break")
Continue_ => Some("continue")
Match_ => Some("match")
When_ => Some("when")
Switch_ => Some("switch")
Case_ => Some("case")
Pipe_ => Some("pipe")
As => Some("as")
Of => Some("of")
Gives => Some("gives")
Typeof_ => Some("typeof")
Void_ => Some("void")
Instanceof_ => Some("instanceof")
New_ => Some("new")
Delete_ => Some("delete")
This_ => Some("this")
Async_ => Some("async")
Await_ => Some("await")
Try_ => Some("try")
Catch_ => Some("catch")
Finally_ => Some("finally")
Throw_ => Some("throw")
Import_ => Some("import")
From_ => Some("from")
Export_ => Some("export")
Default_ => Some("default")
Require_ => Some("require")
Use_ => Some("use")
Mod_ => Some("namespace")
Public_ => Some("public")
All_ => Some("all")
Type_ => Some("type")
With_ => Some("with")
True_ => Some("true")
False_ => Some("false")
Null_ => Some("null")
Nil_ => Some("nil")
Undefined_ => Some("undefined")
Nan_ => Some("nan")
Infinity_ => Some("infinity")
List_ => Some("list")
Object_ => Some("object")
Class_ => Some("class")
Extends_ => Some("extends")
Super_ => Some("super")
Static_ => Some("static")
Private_ => Some("private")
Get_ => Some("get")
Set_ => Some("set")
Blank => Some("blank")
_ => None
}
}

///|
/// Parse a name in property position (after `.` or `\.`).
/// Any keyword is a valid property name there, matching JavaScript,
/// which allows reserved words after `.` (e.g. `promise.then`,
/// `promise.catch`, `promise.finally`).
fn Parser::parse_property_name(self : Parser) -> String {
match keyword_text(self.peek()) {
Some(name) => {
self.pos += 1
name
}
None => self.parse_ident_name()
}
}

///|
fn Parser::parse_var_decl(self : Parser, kind : DeclKind) -> Stmt {
self.pos += 1 // skip const/let/var
Expand Down
4 changes: 2 additions & 2 deletions src/parser/parser_expr.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ fn Parser::parse_postfix(self : Parser) -> Expr {
match self.peek() {
Dot => {
self.pos += 1
let field = self.parse_ident_name()
let field = self.parse_property_name()
if self.peek() == LBracket {
if self.is_computed_bracket() {
// Computed access on property: obj.field[\expr]
Expand Down Expand Up @@ -443,7 +443,7 @@ fn Parser::parse_postfix(self : Parser) -> Expr {
}
OptDot => {
self.pos += 1
let field = self.parse_ident_name()
let field = self.parse_property_name()
if self.peek() == LBracket {
if self.is_computed_bracket() {
self.pos += 1 // skip [
Expand Down
40 changes: 40 additions & 0 deletions src/parser/parser_wbtest.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,43 @@ test "parser: multiple statements" {
let stmts = parse("const x be 1\nconst y be 2\nconst z be x add y")
assert_eq(stmts.length(), 3)
}

///|
test "parser: keyword as property name (then/catch/finally)" {
let stmts = parse("p.then[f]")
assert_eq(stmts.length(), 1)
match stmts[0] {
ExprStmt(MethodCall(Ident(obj), field, args)) => {
assert_eq(obj, "p")
assert_eq(field, "then")
assert_eq(args.length(), 1)
}
_ => fail("expected MethodCall with field 'then'")
}
match parse("p.catch[f]")[0] {
ExprStmt(MethodCall(_, field, _)) => assert_eq(field, "catch")
_ => fail("expected MethodCall with field 'catch'")
}
match parse("p.finally[f]")[0] {
ExprStmt(MethodCall(_, field, _)) => assert_eq(field, "finally")
_ => fail("expected MethodCall with field 'finally'")
}
}

///|
test "parser: keyword as property access (dot / optional dot)" {
match parse("const t be obj.if")[0] {
VarDecl(Const_, _, DotAccess(Ident(o), field)) => {
assert_eq(o, "obj")
assert_eq(field, "if")
}
_ => fail("expected DotAccess with field 'if'")
}
match parse("const t be obj\\.then")[0] {
VarDecl(Const_, _, OptDotAccess(Ident(o), field)) => {
assert_eq(o, "obj")
assert_eq(field, "then")
}
_ => fail("expected OptDotAccess with field 'then'")
}
}
Loading