Skip to content

Commit

Permalink
JS: first work on scope vars; fix for await
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Jun 18, 2020
1 parent cd5f01f commit 692e2ed
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 42 deletions.
8 changes: 4 additions & 4 deletions js/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ func (n AST) String() string {
}

type Scope struct {
Unbound []string
Bound map[string]bool
Unbound map[string]bool
}

//type Source *parse.Input
Expand Down Expand Up @@ -432,7 +433,7 @@ func (n Property) String() string {
}

type BindingName struct {
Data []byte // can be nil TODO: when?
Data []byte
}

func (n BindingName) String() string {
Expand Down Expand Up @@ -630,9 +631,8 @@ func (n MethodDecl) String() string {

type ClassDecl struct {
Name []byte // can be nil
Extends IExpr // can be nil TODO LHS EXPR
Extends IExpr // can be nil
Methods []MethodDecl
Scope
}

func (n ClassDecl) String() string {
Expand Down
35 changes: 15 additions & 20 deletions js/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,18 @@ type Parser struct {
async bool
generator bool
inFor bool

boundVars map[string]bool
unboundVars map[string]bool
scope Scope
}

// Parse returns a JS AST tree of.
func Parse(r *parse.Input) (AST, error) {
p := &Parser{
l: NewLexer(r),
tt: WhitespaceToken, // trick so that next() works
boundVars: map[string]bool{},
unboundVars: map[string]bool{},
l: NewLexer(r),
tt: WhitespaceToken, // trick so that next() works
}

p.next()
ast := p.parseModule()
for name, _ := range p.unboundVars {
if _, ok := p.boundVars[name]; !ok {
ast.Unbound = append(ast.Unbound, name)
}
}

if p.err == nil {
p.err = p.l.Err()
Expand Down Expand Up @@ -108,9 +99,11 @@ func (p *Parser) consume(in string, tt TokenType) bool {
}

func (p *Parser) parseModule() (ast AST) {
p.scope = Scope{map[string]bool{}, map[string]bool{}}
for {
switch p.tt {
case ErrorToken:
ast.Scope = p.scope
return
case ImportToken:
importStmt := p.parseImportStmt()
Expand Down Expand Up @@ -203,7 +196,7 @@ func (p *Parser) parseStmt() (stmt IStmt) {
stmt = &WhileStmt{cond, p.parseStmt()}
case ForToken:
p.next()
await := p.tt == AwaitToken
await := p.async && p.tt == AwaitToken
if await {
p.next()
}
Expand Down Expand Up @@ -618,18 +611,19 @@ func (p *Parser) parseFuncDecl(async, inExpr bool) (funcDecl FuncDecl) {
p.next()
}
if inExpr && (p.tt == IdentifierToken || p.tt == YieldToken || p.tt == AwaitToken) || !inExpr && p.isIdentifierReference(p.tt) {
p.boundVars[string(p.data)] = true
p.scope.Bound[string(p.data)] = true
funcDecl.Name = p.data
p.next()
} else if p.tt != OpenParenToken {
p.fail("function declaration", IdentifierToken, OpenParenToken)
return
}
parentAsync, parentGenerator := p.async, p.generator
p.async, p.generator = funcDecl.Async, funcDecl.Generator
parentAsync, parentGenerator, parentScope := p.async, p.generator, p.scope
p.async, p.generator, p.scope = funcDecl.Async, funcDecl.Generator, Scope{map[string]bool{}, map[string]bool{}}
funcDecl.Params = p.parseFuncParams("function declaration")
funcDecl.Body = p.parseBlockStmt("function declaration")
p.async, p.generator = parentAsync, parentGenerator
funcDecl.Scope = p.scope
p.async, p.generator, p.scope = parentAsync, parentGenerator, parentScope
return
}

Expand Down Expand Up @@ -766,7 +760,7 @@ func (p *Parser) parseBinding() (binding IBinding) {
// binding identifier or binding pattern
if p.tt == IdentifierToken || !p.generator && p.tt == YieldToken || !p.async && p.tt == AwaitToken {
binding = &BindingName{p.data}
p.boundVars[string(p.data)] = true // TODO for array and object
p.scope.Bound[string(p.data)] = true // TODO for array and object
p.next()
} else if p.tt == OpenBracketToken {
p.next()
Expand Down Expand Up @@ -1038,6 +1032,7 @@ func (p *Parser) parseArgs() (args Arguments) {
}

func (p *Parser) parseArrowFunc(async bool, params Params) (arrowFunc ArrowFunc) {
//
if p.tt != ArrowToken {
p.fail("arrow function", ArrowToken)
return
Expand Down Expand Up @@ -1072,8 +1067,8 @@ func (p *Parser) parseExpression(prec OpPrec) IExpr {
switch tt := p.tt; tt {
case IdentifierToken:
left = &LiteralExpr{p.tt, p.data}
if !p.boundVars[string(p.data)] {
p.unboundVars[string(p.data)] = true
if !p.scope.Bound[string(p.data)] {
p.scope.Unbound[string(p.data)] = true
}
p.next()
case StringToken, ThisToken, NullToken, TrueToken, FalseToken, RegExpToken:
Expand Down
86 changes: 68 additions & 18 deletions js/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ func TestParse(t *testing.T) {
{"for (var a in b) {}", "Stmt(for Decl(var Binding(a)) in b Stmt({ }))"},
{"for (var a of b) {}", "Stmt(for Decl(var Binding(a)) of b Stmt({ }))"},
{"for (var a=5 of b) {}", "Stmt(for Decl(var Binding(a = 5)) of b Stmt({ }))"},
{"for await (var a of b) {}", "Stmt(for await Decl(var Binding(a)) of b Stmt({ }))"},
{"for (var a in b) {}", "Stmt(for Decl(var Binding(a)) in b Stmt({ }))"},
{"for (a in b) {}", "Stmt(for a in b Stmt({ }))"},
{"for (a = b;;) {}", "Stmt(for (a=b) ; ; Stmt({ }))"},
Expand Down Expand Up @@ -135,6 +134,7 @@ func TestParse(t *testing.T) {
{"async function a(b = await c){}", "Decl(async function a Params(Binding(b = (await c))) Stmt({ }))"},
{"async function a(){ x = function await(){} }", "Decl(async function a Params() Stmt({ Stmt(x=Decl(function await Params() Stmt({ }))) }))"},
{"async function a(){ x = function b(){ x = await } }", "Decl(async function a Params() Stmt({ Stmt(x=Decl(function b Params() Stmt({ Stmt(x=await) }))) }))"},
{"async function a(){ for await (var a of b) {} }", "Decl(async function a Params() Stmt({ Stmt(for await Decl(var Binding(a)) of b Stmt({ })) }))"},
{"x = {async a(b){}}", "Stmt(x={Method(async a Params(Binding(b)) Stmt({ }))})"},
{"async a => b", "Stmt(async Params(Binding(a)) => Stmt({ Stmt(return b) }))"},

Expand Down Expand Up @@ -300,21 +300,70 @@ func TestParse(t *testing.T) {
}
}

//func TestX(t *testing.T) {
// var tests = []struct {
// js string
// }{
// {"a + yield b"},
// {"function *g(){a + yield b}"},
// }
// for _, tt := range tests {
// fmt.Println()
// fmt.Println(tt.js)
// ast, err := Parse(parse.NewInputString(tt.js))
// fmt.Println(ast)
// fmt.Println(err)
// }
//}
func TestParseScope(t *testing.T) {
var tests = []struct {
js string
bound string
unbound string
}{
{"var a; const b; let c; d;", "a b c", "d"},
{"var {a:b, c=d, ...e};", "a c e", "d"},
{"var [a, b=c, ...d];", "a b d", "c"},
{"function a(b,c){var d; e = 5}", "a,b c d", ",e"},
{"(a,b) => {var c; d = 5}", "a b,c", ",d"},
}
for _, tt := range tests {
t.Run(tt.js, func(t *testing.T) {
ast, err := Parse(parse.NewInputString(tt.js))
if err != io.EOF {
test.Error(t, err)
}
bound := ""
unbound := ""
addVars := func(scope Scope) {
if len(bound) != 0 {
bound += ","
unbound += ","
}
first := true
for name := range scope.Bound {
if !first {
bound += " "
}
bound += name
first = false
}
first = true
for name := range scope.Unbound {
if !first {
unbound += " "
}
unbound += name
first = false
}
}

addVars(ast.Scope)
for _, istmt := range ast.List {
switch stmt := istmt.(type) {
case *FuncDecl:
addVars(stmt.Scope)
case *ClassDecl:
for _, method := range stmt.Methods {
addVars(method.Scope)
}
case *ExprStmt:
switch expr := stmt.Value.(type) {
case *ArrowFunc:
addVars(expr.Scope)
}
}
}
test.String(t, bound, tt.bound)
test.String(t, unbound, tt.unbound)
})
}
}

func TestParseError(t *testing.T) {
var tests = []struct {
Expand All @@ -335,8 +384,9 @@ func TestParseError(t *testing.T) {
{"for(a", "expected 'in', 'of', or ';' instead of EOF in for statement"},
{"for(a;a", "expected ';' instead of EOF in for statement"},
{"for(a;a;a", "expected ')' instead of EOF in for statement"},
{"for await(a;", "expected 'of' instead of ';' in for statement"},
{"for await(a in", "expected 'of' instead of 'in' in for statement"},
{"for await", "expected '(' instead of 'await' in for statement"},
{"async function a(){ for await(a;", "expected 'of' instead of ';' in for statement"},
{"async function a(){ for await(a in", "expected 'of' instead of 'in' in for statement"},
{"for(var a of b", "expected ')' instead of EOF in for statement"},
{"switch", "expected '(' instead of EOF in switch statement"},
{"switch(a", "expected ')' instead of EOF in switch statement"},
Expand Down

0 comments on commit 692e2ed

Please sign in to comment.