From 79f12352650c06f67419a879652decb6de9e2d2e Mon Sep 17 00:00:00 2001 From: oleiade Date: Mon, 6 Dec 2021 15:01:43 +0100 Subject: [PATCH 001/124] Add import/export syntax support to the parser --- ast/node.go | 63 ++++++++++ parser/statement.go | 274 +++++++++++++++++++++++++++++++++++++++++++ token/token_const.go | 20 ++-- 3 files changed, 349 insertions(+), 8 deletions(-) diff --git a/ast/node.go b/ast/node.go index e9a5389a..4bbc1047 100644 --- a/ast/node.go +++ b/ast/node.go @@ -434,6 +434,60 @@ type ( FunctionDeclaration struct { Function *FunctionLiteral } + + ImportDeclaration struct { + idx0 file.Idx + idx1 file.Idx + ImportClause *ImportClause + FromClause *FromClause + ModuleSpecifier unistring.String + } + + ImportClause struct { + ImportedDefaultBinding *Identifier + NameSpaceImport *NameSpaceImport + NamedImports *NamedImports + } + + NameSpaceImport struct { + ImportedBinding unistring.String + } + + NamedImports struct { + ImportsList []*ImportSpecifier + } + + ImportSpecifier struct { + IdentifierName unistring.String + Alias unistring.String + } + + ExportDeclaration struct { + idx0 file.Idx + idx1 file.Idx + Variable *VariableStatement + NamedExports *NamedExports + ExportFromClause *ExportFromClause + FromClause *FromClause + } + + FromClause struct { + ModuleSpecifier unistring.String + } + ExportFromClause struct { + IsWildcard bool + Alias unistring.String + NamedExports *NamedExports + } + + NamedExports struct { + ExportsList []*ExportSpecifier + } + + ExportSpecifier struct { + IdentifierName unistring.String + Alias unistring.String + } ) // _statementNode @@ -462,6 +516,9 @@ func (*WithStatement) _statementNode() {} func (*LexicalDeclaration) _statementNode() {} func (*FunctionDeclaration) _statementNode() {} +func (*ExportDeclaration) _statementNode() {} +func (*ImportDeclaration) _statementNode() {} + // =========== // // Declaration // // =========== // @@ -608,6 +665,9 @@ func (self *PropertyShort) Idx0() file.Idx { return self.Name.Id func (self *PropertyKeyed) Idx0() file.Idx { return self.Key.Idx0() } func (self *ExpressionBody) Idx0() file.Idx { return self.Expression.Idx0() } +func (self *ExportDeclaration) Idx0() file.Idx { return self.idx0 } +func (self *ImportDeclaration) Idx0() file.Idx { return self.idx0 } + // ==== // // Idx1 // // ==== // @@ -707,3 +767,6 @@ func (self *PropertyShort) Idx1() file.Idx { func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() } func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() } + +func (self *ExportDeclaration) Idx1() file.Idx { return self.idx1 } +func (self *ImportDeclaration) Idx1() file.Idx { return self.idx1 } diff --git a/parser/statement.go b/parser/statement.go index 580d0440..b748fa54 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -9,6 +9,7 @@ import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" + "github.com/dop251/goja/unistring" "github.com/go-sourcemap/sourcemap" ) @@ -85,6 +86,10 @@ func (self *_parser) parseStatement() ast.Statement { return self.parseThrowStatement() case token.TRY: return self.parseTryStatement() + case token.EXPORT: + return self.parseExportDeclaration() + case token.IMPORT: + return self.parseImportDeclaration() } expression := self.parseExpression() @@ -808,6 +813,64 @@ illegal: return &ast.BadStatement{From: idx, To: self.idx} } +// :white_check_mark: export ExportFromClause FromClause; (3. is missing) +// :white_check_mark: export NamedExports; +// :white_check_mark: export VariableStatement +func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { + self.next() + + // TODO: parse `export Declaration` -> function || generator || async function || async generator || let || const + + switch self.token { + case token.MULTIPLY: // FIXME: should also parse NamedExports if '{' + exportFromClause := self.parseExportFromClause() + fromClause := self.parseFromClause() + self.semicolon() + return &ast.ExportDeclaration{ + ExportFromClause: exportFromClause, + FromClause: fromClause, + } + case token.LEFT_BRACE: + namedExports := self.parseNamedExports() + self.semicolon() + return &ast.ExportDeclaration{NamedExports: namedExports} + case token.VAR: + self.next() + return &ast.ExportDeclaration{Variable: self.parseVariableStatement()} + case token.FUNCTION, token.LET, token.CONST: // FIXME: What about function* and async? + break // TODO: implement me! + case token.DEFAULT: // FIXME: current implementation of HoistableDeclaration only implements function? + return &ast.ExportDeclaration{} + default: + namedExports := self.parseNamedExports() + self.semicolon() + return &ast.ExportDeclaration{NamedExports: namedExports} + } + + return nil +} + +func (self *_parser) parseImportDeclaration() *ast.ImportDeclaration { + self.next() + + // _, _, errString := self.scanString(0, false) // look at first character only + if self.token == token.STRING { + moduleSpecifier := self.parseModuleSpecifier() + self.semicolon() + + return &ast.ImportDeclaration{ModuleSpecifier: moduleSpecifier} + } + + importClause := self.parseImportClause() + fromClause := self.parseFromClause() + self.semicolon() + + return &ast.ImportDeclaration{ + ImportClause: importClause, + FromClause: fromClause, + } +} + // Find the next statement after an error (recover) func (self *_parser) nextStatement() { for { @@ -840,3 +903,214 @@ func (self *_parser) nextStatement() { self.next() } } + +func (self *_parser) parseFromClause() *ast.FromClause { + identifier := self.parseIdentifier() + if identifier.Name != "from" { + self.error(self.idx, "expected 'from' keyword, found '%s' instead", identifier.Name) + return nil + } + + // expression := self.parsePrimaryExpression() // module specifier (string literal) + // stringLiteral, ok := expression.(*ast.StringLiteral) + // if !ok { + // self.error(self.idx, "expected module specifier") + // return nil + // } + + return &ast.FromClause{ + ModuleSpecifier: self.parseModuleSpecifier(), + } +} + +func (self *_parser) parseExportFromClause() *ast.ExportFromClause { + if self.token == token.MULTIPLY { // FIXME: rename token to STAR? + self.next() + + if self.token != token.IDENTIFIER { + return &ast.ExportFromClause{IsWildcard: true} + } + + // Is next token a "as" identifier + // nextToken, nextLiteral, _, _, := self.scan() + // literal, _, _, _ := self.scanIdentifier() + if self.literal == "as" { + self.next() + alias := self.parseIdentifier() // TODO: does it really match specification's IdentifierName? + return &ast.ExportFromClause{ + IsWildcard: true, + Alias: alias.Name, + } + } + + return &ast.ExportFromClause{ + IsWildcard: true, + } + } + + return &ast.ExportFromClause{ + IsWildcard: false, + NamedExports: self.parseNamedExports(), + } +} + +func (self *_parser) parseNamedExports() *ast.NamedExports { + _ = self.expect(token.LEFT_BRACE) // FIXME: return idx? + + exportsList := self.parseExportsList() + + self.expect(token.RIGHT_BRACE) + + return &ast.NamedExports{ + ExportsList: exportsList, + } +} + +// MOVE IN EXPRESSION +func (self *_parser) parseExportsList() (exportsList []*ast.ExportSpecifier) { // FIXME: ast.Binding? + for { + exportsList = append(exportsList, self.parseExportSpecifier()) + if self.token != token.COMMA { + break + } + + self.next() + } + + return +} + +func (self *_parser) parseExportSpecifier() *ast.ExportSpecifier { + identifier := self.parseIdentifier() + + // Is the next token an identifier? + if self.token != token.IDENTIFIER { // No "as" identifier found + return &ast.ExportSpecifier{IdentifierName: identifier.Name} + } + + as := self.parseIdentifier() + // We reach this point only if the next token is an identifier. + // Let's now verify if it's "as". + if as.Name != "as" { // FIXME: The specification says 'as' is a keyword in this context + // FAIL + self.error(as.Idx, "Expected 'as' keyword, found '%s' instead", as.Name) + } + + alias := self.parseIdentifier() + + return &ast.ExportSpecifier{ + IdentifierName: identifier.Name, + Alias: alias.Name, + } +} + +func (self *_parser) parseImportClause() *ast.ImportClause { // FIXME: return type? + switch self.token { + case token.LEFT_BRACE: + return &ast.ImportClause{NamedImports: self.parseNamedImports()} + case token.MULTIPLY: + return &ast.ImportClause{NameSpaceImport: self.parseNameSpaceImport()} + case token.IDENTIFIER: + literal, _, _, _ := self.scanIdentifier() + if literal == "*" { + return &ast.ImportClause{NameSpaceImport: self.parseNameSpaceImport()} + } + + importClause := ast.ImportClause{ + ImportedDefaultBinding: self.parseImportedDefaultBinding(), + } + + if self.token == token.COMMA { + self.expect(token.COMMA) + + if self.token == token.MULTIPLY { + importClause.NameSpaceImport = self.parseNameSpaceImport() + } else { + importClause.NamedImports = self.parseNamedImports() + } + } + + return &importClause + } + + return nil // unreachable +} + +func (self *_parser) parseModuleSpecifier() unistring.String { + expression := self.parsePrimaryExpression() // module specifier (string literal) + stringLiteral, ok := expression.(*ast.StringLiteral) + if !ok { + self.error(self.idx, "expected module specifier") + return "" + } + + return stringLiteral.Value +} + +func (self *_parser) parseImportedDefaultBinding() *ast.Identifier { + return self.parseImportedBinding() +} + +// FIXME: return type? +func (self *_parser) parseNameSpaceImport() *ast.NameSpaceImport { + self.expect(token.MULTIPLY) // * + identifier := self.parseIdentifier() + if identifier.Name != "as" { + self.error(self.idx, "expected 'as' identifier, found '%s' instead", identifier.Name) + return nil + } + + return &ast.NameSpaceImport{ImportedBinding: self.parseImportedBinding().Name} +} + +func (self *_parser) parseNamedImports() *ast.NamedImports { + _ = self.expect(token.LEFT_BRACE) // FIXME: should we do something with idx? + + importsList := self.parseImportsList() + + self.expect(token.RIGHT_BRACE) + + return &ast.NamedImports{ + ImportsList: importsList, + } +} + +func (self *_parser) parseImportsList() (importsList []*ast.ImportSpecifier) { + if self.token == token.RIGHT_BRACE { + return + } + + for { + importsList = append(importsList, self.parseImportSpecifier()) + if self.token != token.COMMA { + break + } + + self.next() + } + + return +} + +func (self *_parser) parseImportSpecifier() *ast.ImportSpecifier { + importSpecifier := &ast.ImportSpecifier{ + IdentifierName: self.parseImportedBinding().Name, + } + + if self.token == token.IDENTIFIER && self.literal == "as" { + self.next() // skip "as" + importSpecifier.Alias = self.parseIdentifier().Name + } + + return importSpecifier +} + +func (self *_parser) parseImportedBinding() *ast.Identifier { + return self.parseBindingIdentifier() +} + +func (self *_parser) parseBindingIdentifier() *ast.Identifier { + // FIXME: Ecma 262 defines yield and await as permitted binding identifier, + // but goja does not support async/await nor generators yet? + return self.parseIdentifier() +} diff --git a/token/token_const.go b/token/token_const.go index bdbc15e0..52097ea6 100644 --- a/token/token_const.go +++ b/token/token_const.go @@ -112,6 +112,10 @@ const ( DEBUGGER INSTANCEOF + + // ES6 Modules + EXPORT + IMPORT lastKeyword ) @@ -205,6 +209,8 @@ var token2string = [...]string{ CONTINUE: "continue", DEBUGGER: "debugger", INSTANCEOF: "instanceof", + EXPORT: "export", + IMPORT: "import", } var keywordTable = map[string]_keyword{ @@ -286,6 +292,12 @@ var keywordTable = map[string]_keyword{ "instanceof": { token: INSTANCEOF, }, + "import": { + token: IMPORT, + }, + "export": { + token: EXPORT, + }, "const": { token: CONST, }, @@ -297,18 +309,10 @@ var keywordTable = map[string]_keyword{ token: KEYWORD, futureKeyword: true, }, - "export": { - token: KEYWORD, - futureKeyword: true, - }, "extends": { token: KEYWORD, futureKeyword: true, }, - "import": { - token: KEYWORD, - futureKeyword: true, - }, "super": { token: KEYWORD, futureKeyword: true, From c0a2c6fcd052248c97c71a1b713811a61af90db3 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 8 Dec 2021 17:44:37 +0200 Subject: [PATCH 002/124] fix panics --- compiler_expr.go | 4 ++++ compiler_stmt.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/compiler_expr.go b/compiler_expr.go index ec4aadb9..dec80675 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1780,6 +1780,10 @@ func (e *compiledObjectLiteral) emitGetter(putOnStack bool) { switch prop := prop.(type) { case *ast.PropertyKeyed: keyExpr := e.c.compileExpression(prop.Key) + if keyExpr == nil { + // TODO + return + } computed := false var key unistring.String switch keyExpr := keyExpr.(type) { diff --git a/compiler_stmt.go b/compiler_stmt.go index 05d3b875..5ff05a88 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -52,6 +52,8 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) { case *ast.WithStatement: c.compileWithStatement(v, needResult) case *ast.DebuggerStatement: + case *ast.ImportDeclaration: + // TODO default: panic(fmt.Errorf("Unknown statement type: %T", v)) } From c086747b60b1ce07dfc55f856c3cb05ecf75b340 Mon Sep 17 00:00:00 2001 From: oleiade Date: Wed, 8 Dec 2021 16:57:17 +0100 Subject: [PATCH 003/124] fixup! Add import/export syntax support to the parser --- ast/node.go | 17 +++++++++++------ parser/statement.go | 8 +++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ast/node.go b/ast/node.go index 4bbc1047..f145a1b0 100644 --- a/ast/node.go +++ b/ast/node.go @@ -463,12 +463,13 @@ type ( } ExportDeclaration struct { - idx0 file.Idx - idx1 file.Idx - Variable *VariableStatement - NamedExports *NamedExports - ExportFromClause *ExportFromClause - FromClause *FromClause + idx0 file.Idx + idx1 file.Idx + Variable *VariableStatement + NamedExports *NamedExports + ExportFromClause *ExportFromClause + FromClause *FromClause + HoistableDeclaration *HoistableDeclaration } FromClause struct { @@ -488,6 +489,10 @@ type ( IdentifierName unistring.String Alias unistring.String } + + HoistableDeclaration struct { + FunctionDeclaration *FunctionLiteral + } ) // _statementNode diff --git a/parser/statement.go b/parser/statement.go index b748fa54..9701714c 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -840,7 +840,13 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.FUNCTION, token.LET, token.CONST: // FIXME: What about function* and async? break // TODO: implement me! case token.DEFAULT: // FIXME: current implementation of HoistableDeclaration only implements function? - return &ast.ExportDeclaration{} + self.next() + functionDeclaration := self.parseFunction(false) + return &ast.ExportDeclaration{ + HoistableDeclaration: &ast.HoistableDeclaration{ + FunctionDeclaration: functionDeclaration, + }, + } default: namedExports := self.parseNamedExports() self.semicolon() From 6c4b34aea847594dbed63e14440c8d511c94a0fa Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 8 Dec 2021 19:03:32 +0200 Subject: [PATCH 004/124] WIP ModuleRecords implementation --- ast/node.go | 3 + compiler_expr.go | 3 +- compiler_stmt.go | 3 +- module.go | 381 ++++++++++++++++++++++++++++++++++++++++++++ parser/scope.go | 2 + parser/statement.go | 18 +-- runtime.go | 6 +- vm.go | 1 - 8 files changed, 401 insertions(+), 16 deletions(-) create mode 100644 module.go diff --git a/ast/node.go b/ast/node.go index f145a1b0..8ee7fb3b 100644 --- a/ast/node.go +++ b/ast/node.go @@ -605,6 +605,8 @@ type Program struct { Body []Statement DeclarationList []*VariableDeclaration + ImportEntries []*ImportDeclaration + ExportEntries []*ExportDeclaration File *file.File } @@ -712,6 +714,7 @@ func (self *UnaryExpression) Idx1() file.Idx { } return self.Operand.Idx1() } + func (self *MetaProperty) Idx1() file.Idx { return self.Property.Idx1() } diff --git a/compiler_expr.go b/compiler_expr.go index dec80675..aa77fb44 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -2,6 +2,7 @@ package goja import ( "fmt" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" @@ -1737,7 +1738,6 @@ func (e *compiledBinaryExpr) emitGetter(putOnStack bool) { } func (c *compiler) compileBinaryExpression(v *ast.BinaryExpression) compiledExpr { - switch v.Operator { case token.LOGICAL_OR: return c.compileLogicalOr(v.Left, v.Right, v.Idx0()) @@ -2030,7 +2030,6 @@ func (c *compiler) compileSpreadCallArgument(spread *ast.SpreadElement) compiled } func (c *compiler) compileCallExpression(v *ast.CallExpression) compiledExpr { - args := make([]compiledExpr, len(v.ArgumentList)) isVariadic := false for i, argExpr := range v.ArgumentList { diff --git a/compiler_stmt.go b/compiler_stmt.go index 5ff05a88..823b9a5c 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -2,6 +2,7 @@ package goja import ( "fmt" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" @@ -205,7 +206,7 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) { } func (c *compiler) compileThrowStatement(v *ast.ThrowStatement) { - //c.p.srcMap = append(c.p.srcMap, srcMapItem{pc: len(c.p.code), srcPos: int(v.Throw) - 1}) + // c.p.srcMap = append(c.p.srcMap, srcMapItem{pc: len(c.p.code), srcPos: int(v.Throw) - 1}) c.compileExpression(v.Argument).emitGetter(true) c.emit(throw) } diff --git a/module.go b/module.go new file mode 100644 index 00000000..39ac56b1 --- /dev/null +++ b/module.go @@ -0,0 +1,381 @@ +package goja + +import ( + "errors" + "sort" + + "github.com/dop251/goja/ast" +) + +// TODO most things here probably should be unexported and names should be revised before merged in master +// Record should probably be dropped from everywhere + +// ModuleRecord is the common interface for module record as defined in the EcmaScript specification +type ModuleRecord interface { + GetExportedNames(resolveset ...*SourceTextModuleRecord) []string // TODO maybe this parameter is wrong + ResolveExport(exportName string, resolveset ...string) *Value // TODO this probably should not return Value directly + Link() error + Evaluate() error +} + +type CyclicModuleRecordStatus uint8 + +const ( + Unlinked CyclicModuleRecordStatus = iota + Linking + Linked + Evaluating + Evaluated +) + +type CyclicModuleRecord interface { + // TODO this probably shouldn't really be an interface ... or at least the current one is quite bad and big + ModuleRecord + Status() CyclicModuleRecordStatus + SetStatus(CyclicModuleRecordStatus) + EvaluationError() error + SetEvaluationError(error) + DFSIndex() uint + SetDFSIndex(uint) + DFSAncestorIndex() uint + SetDFSAncestorIndex(uint) + RequestedModules() []string + InitializeEnvorinment() error + ExecuteModule() error +} + +func (rt *Runtime) CyclicModuleRecordConcreteLink(c CyclicModuleRecord) error { + if c.Status() == Linking || c.Status() == Evaluating { + return errors.New("bad status on link") + } + + stack := []CyclicModuleRecord{} + if _, err := rt.innerModuleLinking(c, &stack, 0); err != nil { + for _, m := range stack { + if m.Status() != Linking { + return errors.New("bad status on link") + } + m.SetStatus(Unlinked) + + // TODO reset the rest + + } + c.SetStatus(Unlinked) + return err + + } + return nil +} + +func (rt *Runtime) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { + var c CyclicModuleRecord + var ok bool + if c, ok = m.(CyclicModuleRecord); !ok { + return index, m.Link() + } + if status := c.Status(); status == Linking || status == Linked || status == Evaluated { + return index, nil + } else if status != Unlinked { + return 0, errors.New("bad status on link") // TODO fix + } + c.SetStatus(Linking) + c.SetDFSIndex(index) + c.SetDFSAncestorIndex(index) + index++ + *stack = append(*stack, c) + var err error + for _, required := range c.RequestedModules() { + requiredModule := rt.hostResolveImportedModule(c, required) + index, err = rt.innerModuleLinking(requiredModule, stack, index) + if err != nil { + return 0, err + } + if requiredC, ok := requiredModule.(CyclicModuleRecord); ok { + // TODO some asserts + if requiredC.Status() == Linking { + if ancestorIndex := c.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { + requiredC.SetDFSAncestorIndex(ancestorIndex) + } + } + } + } + c.InitializeEnvorinment() // TODO implement + // TODO more asserts + + if c.DFSAncestorIndex() == c.DFSIndex() { + for { + requiredModule := (*stack)[len(*stack)-1] + // TODO assert + requiredC := requiredModule.(CyclicModuleRecord) + requiredC.SetStatus(Linked) + if requiredC == c { + break + } + } + } + return index, nil +} + +func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord) error { + // TODO asserts + stack := []CyclicModuleRecord{} + if _, err := rt.innerModuleEvaluation(c, &stack, 0); err != nil { + + for _, m := range stack { + // TODO asserts + m.SetStatus(Evaluated) + m.SetEvaluationError(err) + } + // TODO asserts + return err + } + + // TODO asserts + return nil +} + +func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { + var c CyclicModuleRecord + var ok bool + if c, ok = m.(CyclicModuleRecord); !ok { + return index, m.Evaluate() + } + if status := c.Status(); status == Evaluated { // TODO switch + return index, c.EvaluationError() + } else if status != Evaluating { + return index, nil + } else if status != Linked { + return 0, errors.New("module isn't linked when it's being evaluated") + } + c.SetStatus(Evaluating) + c.SetDFSIndex(index) + c.SetDFSAncestorIndex(index) + index++ + + *stack = append(*stack, c) + var err error + for _, required := range c.RequestedModules() { + requiredModule := rt.hostResolveImportedModule(c, required) + index, err = rt.innerModuleEvaluation(requiredModule, stack, index) + if err != nil { + return 0, err + } + if requiredC, ok := requiredModule.(CyclicModuleRecord); ok { + // TODO some asserts + if requiredC.Status() == Evaluating { + if ancestorIndex := c.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { + requiredC.SetDFSAncestorIndex(ancestorIndex) + } + } + } + } + c.ExecuteModule() + // TODO asserts + + if c.DFSAncestorIndex() == c.DFSIndex() { + for { + requiredModule := (*stack)[len(*stack)-1] + // TODO assert + requiredC := requiredModule.(CyclicModuleRecord) + requiredC.SetStatus(Evaluated) + if requiredC == c { + break + } + } + } + return index, nil +} + +var _ CyclicModuleRecord = &SourceTextModuleRecord{} + +type SourceTextModuleRecord struct { + cyclicModuleStub + rt *Runtime // TODO this is not great as it means the whole thing needs to be reparsed for each runtime + body *ast.Program + // context + // importmeta + // importEntries + localExportEntries []exportEntry + indirectExportEntries []exportEntry + starExportEntries []exportEntry +} + +type importEntry struct { + moduleRequest string + importName string + localName string +} + +type exportEntry struct { + exportName string + moduleRequest string + importName string + localName string +} + +func includes(slice []string, s string) bool { + i := sort.SearchStrings(slice, s) + return i < len(slice) && slice[i] == s +} + +// This should probably be part of Parse +// TODO arguments to this need fixing +func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, error) { + // TODO asserts + body, err := Parse("module", sourceText, rt.parserOptions...) + _ = body + if err != nil { + return nil, err + } + // Let body be ParseText(sourceText, Module). + // 3. If body is a List of errors, return body. + // 4. Let requestedModules be the ModuleRequests of body. + // 5. Let importEntries be ImportEntries of body. + // importEntries := body.ImportEntries TODO fix + // 6. Let importedBoundNames be ImportedLocalNames(importEntries). + var importedBoundNames []string // fix + // 7. Let indirectExportEntries be a new empty List. + // 8. Let localExportEntries be a new empty List. + var localExportEntries []exportEntry // fix + // 9. Let starExportEntries be a new empty List. + // 10. Let exportEntries be ExportEntries of body. + var exportEntries []exportEntry + for _, exportDeclarion := range body.ExportEntries { + for _, spec := range exportDeclarion.ExportFromClause.NamedExports.ExportsList { + exportEntries = append(exportEntries, exportEntry{ + localName: spec.IdentifierName.String(), + exportName: spec.Alias.String(), + }) + } + } + for _, ee := range exportEntries { + if ee.moduleRequest == "" { // technically nil + if !includes(importedBoundNames, ee.localName) { // TODO make it not true always + localExportEntries = append(localExportEntries, ee) + } else { + // TODO logic when we reexport something imported + } + } else { + // TODO implement this where we have export {s } from "somewhere"; and co. + } + } + return &SourceTextModuleRecord{ + // realm isn't implement + // environment is undefined + // namespace is undefined + cyclicModuleStub: cyclicModuleStub{ + status: Unlinked, + }, + // EvaluationError is undefined + // hostDefined TODO + body: body, + // Context empty + // importmenta empty + // RequestedModules + // ImportEntries + localExportEntries: localExportEntries, + // indirectExportEntries TODO + // starExportEntries TODO + // DFSIndex is empty + // DFSAncestorIndex is empty + }, nil // TODO fix +} + +func (s *SourceTextModuleRecord) ExecuteModule() error { + // TODO copy runtime.RunProgram here with some changes so that it doesn't touch the global ? + return nil +} + +func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceTextModuleRecord) []string { + for _, el := range exportStarSet { + if el == module { // better check + // TODO assert + return nil + } + } + exportStarSet = append(exportStarSet, module) + var exportedNames []string + for _, e := range module.localExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + for _, e := range module.indirectExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + for _, e := range module.starExportEntries { + requestedModule := module.rt.hostResolveImportedModule(module, e.moduleRequest) + starNames := requestedModule.GetExportedNames(exportStarSet...) + + for _, n := range starNames { + if n != "default" { + // TODO check if n i exportedNames and don't include it + exportedNames = append(exportedNames, n) + } + } + } + + return exportedNames +} + +func (s *SourceTextModuleRecord) InitializeEnvorinment() error { + return nil // TODO implement +} + +func (s *SourceTextModuleRecord) ResolveExport(exportname string, resolveset ...string) *Value { + return nil // TODO implement +} + +func (s *SourceTextModuleRecord) Evaluate() error { + return s.rt.CyclicModuleRecordEvaluate(s) +} + +func (s *SourceTextModuleRecord) Link() error { + return s.rt.CyclicModuleRecordConcreteLink(s) +} + +type cyclicModuleStub struct { + status CyclicModuleRecordStatus + dfsIndex uint + ancestorDfsIndex uint + evaluationError error + requestedModules []string +} + +func (c *cyclicModuleStub) SetStatus(status CyclicModuleRecordStatus) { + c.status = status +} + +func (c *cyclicModuleStub) Status() CyclicModuleRecordStatus { + return c.status +} + +func (c *cyclicModuleStub) SetDFSIndex(index uint) { + c.dfsIndex = index +} + +func (c *cyclicModuleStub) DFSIndex() uint { + return c.dfsIndex +} + +func (c *cyclicModuleStub) SetDFSAncestorIndex(index uint) { + c.ancestorDfsIndex = index +} + +func (c *cyclicModuleStub) DFSAncestorIndex() uint { + return c.ancestorDfsIndex +} + +func (c *cyclicModuleStub) SetEvaluationError(err error) { + c.evaluationError = err +} + +func (c *cyclicModuleStub) EvaluationError() error { + return c.evaluationError +} + +func (c *cyclicModuleStub) SetRequestedModules(modules []string) { + c.requestedModules = modules +} + +func (c *cyclicModuleStub) RequestedModules() []string { + return c.requestedModules +} diff --git a/parser/scope.go b/parser/scope.go index 321dd90e..59c3cdb9 100644 --- a/parser/scope.go +++ b/parser/scope.go @@ -13,6 +13,8 @@ type _scope struct { inSwitch bool inFunction bool declarationList []*ast.VariableDeclaration + importEntries []*ast.ImportDeclaration + exportEntries []*ast.ExportDeclaration labels []unistring.String } diff --git a/parser/statement.go b/parser/statement.go index 9701714c..1a9c64b8 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -37,7 +37,6 @@ func (self *_parser) parseStatementList() (list []ast.Statement) { } func (self *_parser) parseStatement() ast.Statement { - if self.token == token.EOF { self.errorUnexpectedToken(self.token) return &ast.BadStatement{From: self.idx, To: self.idx + 1} @@ -87,9 +86,13 @@ func (self *_parser) parseStatement() ast.Statement { case token.TRY: return self.parseTryStatement() case token.EXPORT: - return self.parseExportDeclaration() + exp := self.parseExportDeclaration() + self.scope.exportEntries = append(self.scope.exportEntries, exp) + return exp case token.IMPORT: - return self.parseImportDeclaration() + imp := self.parseImportDeclaration() + self.scope.importEntries = append(self.scope.importEntries, imp) + return imp } expression := self.parseExpression() @@ -123,7 +126,6 @@ func (self *_parser) parseStatement() ast.Statement { } func (self *_parser) parseTryStatement() ast.Statement { - node := &ast.TryStatement{ Try: self.expect(token.TRY), Body: self.parseBlockStatement(), @@ -184,7 +186,6 @@ func (self *_parser) parseFunctionParameterList() *ast.ParameterList { } func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral { - node := &ast.FunctionLiteral{ Function: self.expect(token.FUNCTION), } @@ -332,7 +333,6 @@ func (self *_parser) parseWithStatement() ast.Statement { } func (self *_parser) parseCaseStatement() *ast.CaseStatement { - node := &ast.CaseStatement{ Case: self.idx, } @@ -369,7 +369,6 @@ func (self *_parser) parseIterationStatement() ast.Statement { } func (self *_parser) parseForIn(idx file.Idx, into ast.ForInto) *ast.ForInStatement { - // Already have consumed " in" source := self.parseExpression() @@ -384,7 +383,6 @@ func (self *_parser) parseForIn(idx file.Idx, into ast.ForInto) *ast.ForInStatem } func (self *_parser) parseForOf(idx file.Idx, into ast.ForInto) *ast.ForOfStatement { - // Already have consumed " of" source := self.parseAssignmentExpression() @@ -399,7 +397,6 @@ func (self *_parser) parseForOf(idx file.Idx, into ast.ForInto) *ast.ForOfStatem } func (self *_parser) parseFor(idx file.Idx, initializer ast.ForLoopInitializer) *ast.ForStatement { - // Already have consumed " ;" var test, update ast.Expression @@ -550,7 +547,6 @@ func (self *_parser) ensurePatternInit(list []*ast.Binding) { } func (self *_parser) parseVariableStatement() *ast.VariableStatement { - idx := self.expect(token.VAR) list := self.parseVarDeclarationList(idx) @@ -658,6 +654,8 @@ func (self *_parser) parseProgram() *ast.Program { prg := &ast.Program{ Body: self.parseSourceElements(), DeclarationList: self.scope.declarationList, + ImportEntries: self.scope.importEntries, + ExportEntries: self.scope.exportEntries, File: self.file, } self.file.SetSourceMap(self.parseSourceMap()) diff --git a/runtime.go b/runtime.go index 01edbb7c..e317e385 100644 --- a/runtime.go +++ b/runtime.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "github.com/dop251/goja/file" "go/ast" "hash/maphash" "math" @@ -15,6 +14,8 @@ import ( "strconv" "time" + "github.com/dop251/goja/file" + "golang.org/x/text/collate" js_ast "github.com/dop251/goja/ast" @@ -185,6 +186,8 @@ type Runtime struct { jobQueue []func() promiseRejectionTracker PromiseRejectionTracker + // TODO add a type and a set method + hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) ModuleRecord } type StackFrame struct { @@ -1337,7 +1340,6 @@ func (r *Runtime) RunString(str string) (Value, error) { // RunScript executes the given string in the global context. func (r *Runtime) RunScript(name, src string) (Value, error) { p, err := r.compile(name, src, false, false, true) - if err != nil { return nil, err } diff --git a/vm.go b/vm.go index cc987b1d..8aa222bb 100644 --- a/vm.go +++ b/vm.go @@ -3399,7 +3399,6 @@ end: return valueTrue } return valueFalse - } type _op_lt struct{} From f58707a26bf65f36e14e43712d06c29f2cff47e4 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sat, 11 Dec 2021 13:08:48 +0200 Subject: [PATCH 005/124] WIP enabling of tests and fixing panics --- compiler.go | 82 +++++++++++- compiler_stmt.go | 71 ++++++++++- module.go | 296 ++++++++++++++++++++++++++++++++++++-------- parser/statement.go | 4 +- runtime.go | 2 +- tc39_test.go | 121 +++++++++++++++--- 6 files changed, 502 insertions(+), 74 deletions(-) diff --git a/compiler.go b/compiler.go index 12359ed8..c967ca93 100644 --- a/compiler.go +++ b/compiler.go @@ -2,9 +2,10 @@ package goja import ( "fmt" - "github.com/dop251/goja/token" "sort" + "github.com/dop251/goja/token" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/unistring" @@ -75,6 +76,8 @@ type compiler struct { block *block enumGetExpr compiledEnumGetExpr + // TODO add a type and a set method + hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) evalVM *vm } @@ -260,6 +263,8 @@ type scope struct { argsNeeded bool // 'this' is used and non-strict, so need to box it (functions only) thisNeeded bool + // is module + module bool } type block struct { @@ -670,6 +675,81 @@ found: s.bindings = s.bindings[:l] } +func (c *compiler) compileModule(module *SourceTextModuleRecord) { + in := module.body + c.p.src = in.File + c.newScope() + scope := c.scope + scope.dynamic = true + scope.strict = true + ownVarScope := true + ownLexScope := true + c.newBlockScope() + scope = c.scope + scope.module = true + funcs := c.extractFunctions(in.Body) + c.createFunctionBindings(funcs) + numFuncs := len(scope.bindings) + if false && !ownVarScope { + if numFuncs == len(funcs) { + c.compileFunctionsGlobalAllUnique(funcs) + } else { + c.compileFunctionsGlobal(funcs) + } + } + c.compileDeclList(in.DeclarationList, false) + numVars := len(scope.bindings) - numFuncs + vars := make([]unistring.String, len(scope.bindings)) + for i, b := range scope.bindings { + vars[i] = b.name + } + if len(vars) > 0 && !ownVarScope && ownLexScope { + c.emit(&bindVars{names: vars, deletable: false}) + } + var enter *enterBlock + if c.compileLexicalDeclarations(in.Body, ownVarScope || !ownLexScope) { + if ownLexScope { + c.block = &block{ + outer: c.block, + typ: blockScope, + needResult: true, + } + enter = &enterBlock{} + c.emit(enter) + } + } + c.CyclicModuleRecordConcreteLink(module) + + if len(scope.bindings) > 0 && !ownLexScope { + var lets, consts []unistring.String + for _, b := range c.scope.bindings[numFuncs+numVars:] { + if b.isConst { + consts = append(consts, b.name) + } else { + lets = append(lets, b.name) + } + } + c.emit(&bindGlobal{ + vars: vars[numFuncs:], + funcs: vars[:numFuncs], + lets: lets, + consts: consts, + }) + } + if ownVarScope { + c.compileFunctions(funcs) + } + c.compileStatements(in.Body, true) + if enter != nil { + c.leaveScopeBlock(enter) + c.popScope() + } + + c.p.code = append(c.p.code, halt) + + scope.finaliseVarAlloc(0) +} + func (c *compiler) compile(in *ast.Program, strict, eval, inGlobal bool) { c.p.src = in.File c.newScope() diff --git a/compiler_stmt.go b/compiler_stmt.go index 823b9a5c..30b447de 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -54,7 +54,9 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) { c.compileWithStatement(v, needResult) case *ast.DebuggerStatement: case *ast.ImportDeclaration: - // TODO + c.compileImportDeclaration(v) + case *ast.ExportDeclaration: + c.compileExportDeclaration(v) default: panic(fmt.Errorf("Unknown statement type: %T", v)) } @@ -759,6 +761,73 @@ func (c *compiler) emitVarAssign(name unistring.String, offset int, init compile } } +func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { + if !c.scope.module { + c.throwSyntaxError(int(expr.Idx0()), "import not allowed in non module code") + } + // TODO +} + +func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { + if !c.scope.module { + c.throwSyntaxError(int(expr.Idx0()), "import not allowed in non module code") + } + if expr.FromClause == nil { + return // TODO is this right? + } + // TODO fix, maybe cache this?!? have the current module as a field? + module, err := c.hostResolveImportedModule(nil, expr.FromClause.ModuleSpecifier.String()) + if err != nil { + // TODO this should in practice never happen + c.throwSyntaxError(int(expr.Idx0()), err.Error()) + } + if expr.ImportClause != nil { + if namespace := expr.ImportClause.NameSpaceImport; namespace != nil { + /* + r := &compiledLiteral{ + val: getModuleNamespace(module), + } + r.init(c, expr.Idx0()) + c.emitVarAssign(namespace.ImportedBinding, int(expr.Idx)-1, r) + */ + } + if named := expr.ImportClause.NamedImports; named != nil { + for _, name := range named.ImportsList { + value, ambiguous := module.ResolveExport(name.IdentifierName.String()) + + if ambiguous { // also ambiguous + c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of %s", name.IdentifierName.String()) + } + if value == nil { + c.throwSyntaxError(int(expr.Idx0()), "import of %s was not expoted from module %s", name.IdentifierName.String(), expr.FromClause.ModuleSpecifier.String()) + } + r := &compiledLiteral{ + // val: value, + } + r.init(c, expr.Idx0()) + // This should probably be the idx of the name? + c.emitVarAssign(name.Alias, int(expr.Idx1())-1, r) + } + } + + if def := expr.ImportClause.ImportedDefaultBinding; def != nil { + + value, ambiguous := module.ResolveExport("default") + if ambiguous { // also ambiguous + c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of %s", "default") + } + if value == nil { + c.throwSyntaxError(int(expr.Idx0()), "import of \"default\" was not exported from module %s", expr.FromClause.ModuleSpecifier.String()) + } + r := &compiledLiteral{ + // val: value, + } + r.init(c, def.Idx0()) + c.emitVarAssign(def.Name, int(def.Idx1())-1, r) + } + } +} + func (c *compiler) compileVarBinding(expr *ast.Binding) { switch target := expr.Target.(type) { case *ast.Identifier: diff --git a/module.go b/module.go index 39ac56b1..ab410f63 100644 --- a/module.go +++ b/module.go @@ -2,6 +2,7 @@ package goja import ( "errors" + "fmt" "sort" "github.com/dop251/goja/ast" @@ -13,7 +14,7 @@ import ( // ModuleRecord is the common interface for module record as defined in the EcmaScript specification type ModuleRecord interface { GetExportedNames(resolveset ...*SourceTextModuleRecord) []string // TODO maybe this parameter is wrong - ResolveExport(exportName string, resolveset ...string) *Value // TODO this probably should not return Value directly + ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error Evaluate() error } @@ -44,13 +45,13 @@ type CyclicModuleRecord interface { ExecuteModule() error } -func (rt *Runtime) CyclicModuleRecordConcreteLink(c CyclicModuleRecord) error { - if c.Status() == Linking || c.Status() == Evaluating { +func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) error { + if module.Status() == Linking || module.Status() == Evaluating { return errors.New("bad status on link") } stack := []CyclicModuleRecord{} - if _, err := rt.innerModuleLinking(c, &stack, 0); err != nil { + if _, err := c.innerModuleLinking(module, &stack, 0); err != nil { for _, m := range stack { if m.Status() != Linking { return errors.New("bad status on link") @@ -60,55 +61,62 @@ func (rt *Runtime) CyclicModuleRecordConcreteLink(c CyclicModuleRecord) error { // TODO reset the rest } - c.SetStatus(Unlinked) + module.SetStatus(Unlinked) return err } return nil } -func (rt *Runtime) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { - var c CyclicModuleRecord +func (co *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { + var module CyclicModuleRecord var ok bool - if c, ok = m.(CyclicModuleRecord); !ok { + if module, ok = m.(CyclicModuleRecord); !ok { return index, m.Link() } - if status := c.Status(); status == Linking || status == Linked || status == Evaluated { + if status := module.Status(); status == Linking || status == Linked || status == Evaluated { return index, nil } else if status != Unlinked { return 0, errors.New("bad status on link") // TODO fix } - c.SetStatus(Linking) - c.SetDFSIndex(index) - c.SetDFSAncestorIndex(index) + module.SetStatus(Linking) + module.SetDFSIndex(index) + module.SetDFSAncestorIndex(index) index++ - *stack = append(*stack, c) + *stack = append(*stack, module) var err error - for _, required := range c.RequestedModules() { - requiredModule := rt.hostResolveImportedModule(c, required) - index, err = rt.innerModuleLinking(requiredModule, stack, index) + var requiredModule ModuleRecord + for _, required := range module.RequestedModules() { + requiredModule, err = co.hostResolveImportedModule(module, required) + if err != nil { + return 0, err + } + index, err = co.innerModuleLinking(requiredModule, stack, index) if err != nil { return 0, err } if requiredC, ok := requiredModule.(CyclicModuleRecord); ok { // TODO some asserts if requiredC.Status() == Linking { - if ancestorIndex := c.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { + if ancestorIndex := module.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { requiredC.SetDFSAncestorIndex(ancestorIndex) } } } } - c.InitializeEnvorinment() // TODO implement + err = module.InitializeEnvorinment() // TODO implement + if err != nil { + return 0, err + } // TODO more asserts - if c.DFSAncestorIndex() == c.DFSIndex() { - for { - requiredModule := (*stack)[len(*stack)-1] + if module.DFSAncestorIndex() == module.DFSIndex() { + for i := len(*stack) - 1; i >= 0; i-- { + requiredModule := (*stack)[i] // TODO assert requiredC := requiredModule.(CyclicModuleRecord) requiredC.SetStatus(Linked) - if requiredC == c { + if requiredC == module { break } } @@ -142,7 +150,7 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe } if status := c.Status(); status == Evaluated { // TODO switch return index, c.EvaluationError() - } else if status != Evaluating { + } else if status == Evaluating { return index, nil } else if status != Linked { return 0, errors.New("module isn't linked when it's being evaluated") @@ -154,8 +162,12 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe *stack = append(*stack, c) var err error + var requiredModule ModuleRecord for _, required := range c.RequestedModules() { - requiredModule := rt.hostResolveImportedModule(c, required) + requiredModule, err = rt.hostResolveImportedModule(c, required) + if err != nil { + return 0, err + } index, err = rt.innerModuleEvaluation(requiredModule, stack, index) if err != nil { return 0, err @@ -169,12 +181,15 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe } } } - c.ExecuteModule() + err = c.ExecuteModule() + if err != nil { + return 0, err + } // TODO asserts if c.DFSAncestorIndex() == c.DFSIndex() { - for { - requiredModule := (*stack)[len(*stack)-1] + for i := len(*stack) - 1; i >= 0; i-- { + requiredModule := (*stack)[i] // TODO assert requiredC := requiredModule.(CyclicModuleRecord) requiredC.SetStatus(Evaluated) @@ -190,11 +205,12 @@ var _ CyclicModuleRecord = &SourceTextModuleRecord{} type SourceTextModuleRecord struct { cyclicModuleStub - rt *Runtime // TODO this is not great as it means the whole thing needs to be reparsed for each runtime - body *ast.Program + rt *Runtime // TODO this is not great as it means the whole thing needs to be reparsed for each runtime + compiler *compiler // TODO remove this + body *ast.Program // context // importmeta - // importEntries + importEntries []importEntry localExportEntries []exportEntry indirectExportEntries []exportEntry starExportEntries []exportEntry @@ -218,6 +234,77 @@ func includes(slice []string, s string) bool { return i < len(slice) && slice[i] == s } +func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { + var result []importEntry + for _, importDeclarion := range declarations { + importClause := importDeclarion.ImportClause + if importDeclarion.FromClause == nil { + continue // no entry in this case + } + moduleRequest := importDeclarion.FromClause.ModuleSpecifier.String() + if named := importClause.NamedImports; named != nil { + for _, el := range named.ImportsList { + result = append(result, importEntry{ + moduleRequest: moduleRequest, + importName: el.IdentifierName.String(), + localName: el.Alias.String(), + }) + } + } + if def := importClause.ImportedDefaultBinding; def != nil { + result = append(result, importEntry{ + moduleRequest: moduleRequest, + importName: "default", + localName: def.Name.String(), + }) + } + if namespace := importClause.NameSpaceImport; namespace != nil { + result = append(result, importEntry{ + moduleRequest: moduleRequest, + importName: "*", + localName: namespace.ImportedBinding.String(), + }) + } + } + return result +} + +func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { + var result []exportEntry + for _, exportDeclarion := range declarations { + if exportDeclarion.ExportFromClause != nil { + if exportDeclarion.ExportFromClause.NamedExports != nil { + for _, spec := range exportDeclarion.ExportFromClause.NamedExports.ExportsList { + result = append(result, exportEntry{ + localName: spec.IdentifierName.String(), + exportName: spec.Alias.String(), + }) + } + } else { + fmt.Println("unimplemented", exportDeclarion.ExportFromClause) + } + } + } + return result +} + +func requestedModulesFromAst(imports []*ast.ImportDeclaration, exports []*ast.ExportDeclaration) []string { + var result []string + for _, imp := range imports { + if imp.FromClause != nil { + result = append(result, imp.FromClause.ModuleSpecifier.String()) + } else { + result = append(result, imp.ModuleSpecifier.String()) + } + } + for _, exp := range exports { + if exp.FromClause != nil { + result = append(result, exp.FromClause.ModuleSpecifier.String()) + } + } + return result +} + // This should probably be part of Parse // TODO arguments to this need fixing func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, error) { @@ -229,7 +316,7 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro } // Let body be ParseText(sourceText, Module). // 3. If body is a List of errors, return body. - // 4. Let requestedModules be the ModuleRequests of body. + requestedModules := requestedModulesFromAst(body.ImportEntries, body.ExportEntries) // 5. Let importEntries be ImportEntries of body. // importEntries := body.ImportEntries TODO fix // 6. Let importedBoundNames be ImportedLocalNames(importEntries). @@ -239,15 +326,8 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro var localExportEntries []exportEntry // fix // 9. Let starExportEntries be a new empty List. // 10. Let exportEntries be ExportEntries of body. - var exportEntries []exportEntry - for _, exportDeclarion := range body.ExportEntries { - for _, spec := range exportDeclarion.ExportFromClause.NamedExports.ExportsList { - exportEntries = append(exportEntries, exportEntry{ - localName: spec.IdentifierName.String(), - exportName: spec.Alias.String(), - }) - } - } + importEntries := importEntriesFromAst(body.ImportEntries) + exportEntries := exportEntriesFromAst(body.ExportEntries) for _, ee := range exportEntries { if ee.moduleRequest == "" { // technically nil if !includes(importedBoundNames, ee.localName) { // TODO make it not true always @@ -264,15 +344,15 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro // environment is undefined // namespace is undefined cyclicModuleStub: cyclicModuleStub{ - status: Unlinked, + status: Unlinked, + requestedModules: requestedModules, }, // EvaluationError is undefined // hostDefined TODO body: body, // Context empty - // importmenta empty - // RequestedModules - // ImportEntries + // importMeta empty + importEntries: importEntries, localExportEntries: localExportEntries, // indirectExportEntries TODO // starExportEntries TODO @@ -281,9 +361,11 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro }, nil // TODO fix } -func (s *SourceTextModuleRecord) ExecuteModule() error { +func (module *SourceTextModuleRecord) ExecuteModule() error { // TODO copy runtime.RunProgram here with some changes so that it doesn't touch the global ? - return nil + + _, err := module.rt.RunProgram(module.compiler.p) + return err } func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceTextModuleRecord) []string { @@ -302,7 +384,10 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT exportedNames = append(exportedNames, e.exportName) } for _, e := range module.starExportEntries { - requestedModule := module.rt.hostResolveImportedModule(module, e.moduleRequest) + requestedModule, err := module.rt.hostResolveImportedModule(module, e.moduleRequest) + if err != nil { + panic(err) + } starNames := requestedModule.GetExportedNames(exportStarSet...) for _, n := range starNames { @@ -316,20 +401,123 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT return exportedNames } -func (s *SourceTextModuleRecord) InitializeEnvorinment() error { +func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { + defer func() { + if x := recover(); x != nil { + switch x1 := x.(type) { + case *CompilerSyntaxError: + err = x1 + default: + panic(x) + } + } + }() + + // TODO catch panics/exceptions + module.compiler.compileModule(module) + return + /* this is in the compiler + for _, e := range module.indirectExportEntries { + resolution := module.ResolveExport(e.exportName) + if resolution == nil { // TODO or ambiguous + panic(module.rt.newSyntaxError("bad resolution", -1)) // TODO fix + } + // TODO asserts + } + for _, in := range module.importEntries { + importedModule := module.rt.hostResolveImportedModule(module, in.moduleRequest) + if in.importName == "*" { + namespace := getModuleNamespace(importedModule) + b, exists := module.compiler.scope.bindName(in.localName) + if exists { + panic("this bad?") + } + b.emitInit() + } + + } + return nil // TODO implement + */ } -func (s *SourceTextModuleRecord) ResolveExport(exportname string, resolveset ...string) *Value { - return nil // TODO implement +type ResolveSetElement struct { + Module ModuleRecord + ExportName string +} + +type ResolvedBinding struct { + Module ModuleRecord + BindingName string +} + +func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) { + for _, r := range resolveset { + if r.Module == module && exportName == r.ExportName { // TODO better + return nil, false + } + } + resolveset = append(resolveset, ResolveSetElement{Module: module, ExportName: exportName}) + for _, e := range module.localExportEntries { + if exportName == e.exportName { + // ii. ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + return &ResolvedBinding{ + Module: module, + BindingName: e.localName, + }, false + } + } + + for _, e := range module.indirectExportEntries { + if exportName == e.exportName { + importedModule, err := module.rt.hostResolveImportedModule(module, e.moduleRequest) + if err != nil { + panic(err) // TODO return err + } + if e.importName == "*" { + // 2. 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: "*namespace*" }. + return &ResolvedBinding{ + Module: importedModule, + BindingName: "*namespace*", + }, false + } else { + return importedModule.ResolveExport(e.importName, resolveset...) + } + } + } + if exportName == "default" { + // This actually should've been caught above, but as it didn't it actually makes it s so the `default` export + // doesn't resolve anything that is `export * ...` + return nil, false + } + var starResolution *ResolvedBinding + + for _, e := range module.starExportEntries { + importedModule, err := module.rt.hostResolveImportedModule(module, e.moduleRequest) + if err != nil { + panic(err) // TODO return err + } + resolution, ambiguous := importedModule.ResolveExport(exportName, resolveset...) + if ambiguous { + return nil, true + } + if resolution != nil { + if starResolution == nil { + starResolution = resolution + } else if resolution.Module != starResolution.Module || resolution.BindingName != starResolution.BindingName { + return nil, true + } + } + } + return starResolution, false } -func (s *SourceTextModuleRecord) Evaluate() error { - return s.rt.CyclicModuleRecordEvaluate(s) +func (module *SourceTextModuleRecord) Evaluate() error { + return module.rt.CyclicModuleRecordEvaluate(module) } -func (s *SourceTextModuleRecord) Link() error { - return s.rt.CyclicModuleRecordConcreteLink(s) +func (module *SourceTextModuleRecord) Link() error { + return module.compiler.CyclicModuleRecordConcreteLink(module) } type cyclicModuleStub struct { diff --git a/parser/statement.go b/parser/statement.go index 1a9c64b8..cc36804c 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -87,7 +87,9 @@ func (self *_parser) parseStatement() ast.Statement { return self.parseTryStatement() case token.EXPORT: exp := self.parseExportDeclaration() - self.scope.exportEntries = append(self.scope.exportEntries, exp) + if exp != nil { + self.scope.exportEntries = append(self.scope.exportEntries, exp) + } return exp case token.IMPORT: imp := self.parseImportDeclaration() diff --git a/runtime.go b/runtime.go index e317e385..40fe1593 100644 --- a/runtime.go +++ b/runtime.go @@ -187,7 +187,7 @@ type Runtime struct { promiseRejectionTracker PromiseRejectionTracker // TODO add a type and a set method - hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) ModuleRecord + hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) } type StackFrame struct { diff --git a/tc39_test.go b/tc39_test.go index 9f973143..16c7e316 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -3,7 +3,6 @@ package goja import ( "errors" "fmt" - "gopkg.in/yaml.v2" "io/ioutil" "os" "path" @@ -12,6 +11,8 @@ import ( "sync" "testing" "time" + + "gopkg.in/yaml.v2" ) const ( @@ -264,11 +265,11 @@ var ( "ShadowRealm", "SharedArrayBuffer", "error-cause", + "top-level-await", } ) func init() { - skip := func(prefixes ...string) { for _, prefix := range prefixes { skipPrefixes.Add(prefix) @@ -306,7 +307,6 @@ func init() { "test/built-ins/TypedArrayConstructors/BigUint64Array/", "test/built-ins/TypedArrayConstructors/BigInt64Array/", ) - } type tc39Test struct { @@ -469,7 +469,13 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. vm.Set("print", t.Log) } - err, early := ctx.runTC39Script(name, src, meta.Includes, vm) + var err error + var early bool + if meta.hasFlag("module") { + err, early = ctx.runTC39Module(name, src, meta.Includes, vm) + } else { + err, early = ctx.runTC39Script(name, src, meta.Includes, vm) + } if err != nil { if meta.Negative.Type == "" { @@ -509,6 +515,7 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. } if errType != meta.Negative.Type { + fmt.Println(err) vm.vm.prg.dumpCode(t.Logf) t.Fatalf("%s: unexpected error type (%s), expected (%s)", name, errType, meta.Negative.Type) } @@ -555,13 +562,10 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { p := path.Join(ctx.base, name) meta, src, err := parseTC39File(p) if err != nil { - //t.Fatalf("Could not parse %s: %v", name, err) + // t.Fatalf("Could not parse %s: %v", name, err) t.Errorf("Could not parse %s: %v", name, err) return } - if meta.hasFlag("module") { - t.Skip("module") - } if meta.Es5id == "" { if meta.Es6id == "" && meta.Esid == "" { t.Skip("No ids") @@ -584,13 +588,13 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { hasRaw := meta.hasFlag("raw") if hasRaw || !meta.hasFlag("onlyStrict") { - //log.Printf("Running normal test: %s", name) + // log.Printf("Running normal test: %s", name) t.Logf("Running normal test: %s", name) ctx.runTC39Test(name, src, meta, t) } if !hasRaw && !meta.hasFlag("noStrict") { - //log.Printf("Running strict test: %s", name) + // log.Printf("Running strict test: %s", name) t.Logf("Running strict test: %s", name) ctx.runTC39Test(name, "'use strict';\n"+src, meta, t) } @@ -603,7 +607,6 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { }) ctx.benchLock.Unlock() } - } func (ctx *tc39TestCtx) init() { @@ -655,6 +658,90 @@ func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error { return err } +func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *Runtime) (err error, early bool) { + early = true + err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm) + if err != nil { + return + } + + err = ctx.runFile(ctx.base, path.Join("harness", "sta.js"), vm) + if err != nil { + return + } + + for _, include := range includes { + err = ctx.runFile(ctx.base, path.Join("harness", include), vm) + if err != nil { + return + } + } + type cacheElement struct { + m ModuleRecord + err error + } + cache := make(map[string]cacheElement) + + var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { + k, ok := cache[specifier] + if ok { + return k.m, k.err + } + fname := path.Join(ctx.base, path.Dir(name), specifier) + f, err := os.Open(fname) + if err != nil { + cache[specifier] = cacheElement{err: err} + return nil, err + } + defer f.Close() + + b, err := ioutil.ReadAll(f) + if err != nil { + cache[specifier] = cacheElement{err: err} + return nil, err + } + + str := string(b) + p, err := vm.ParseModule(str) + if err != nil { + cache[specifier] = cacheElement{err: err} + return nil, err + } + p.rt = vm + p.compiler = newCompiler() + p.compiler.hostResolveImportedModule = hostResolveImportedModule + cache[specifier] = cacheElement{m: p} + return p, nil + } + + vm.hostResolveImportedModule = hostResolveImportedModule + var p *SourceTextModuleRecord + p, err = vm.ParseModule(src) + if err != nil { + return + } + p.rt = vm + + compiler := newCompiler() + p.compiler = compiler + compiler.hostResolveImportedModule = hostResolveImportedModule + err = p.Link() + if err != nil { + return + } + + early = false + err = p.Evaluate() + if err != nil { + return + } + fmt.Println(p.compiler.p) + err = p.ExecuteModule() + + return +} + func (ctx *tc39TestCtx) runTC39Script(name, src string, includes []string, vm *Runtime) (err error, early bool) { early = true err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm) @@ -709,7 +796,6 @@ func (ctx *tc39TestCtx) runTC39Tests(name string) { } } } - } func TestTC39(t *testing.T) { @@ -725,12 +811,12 @@ func TestTC39(t *testing.T) { base: tc39BASE, } ctx.init() - //ctx.enableBench = true + // ctx.enableBench = true t.Run("tc39", func(t *testing.T) { ctx.t = t - //ctx.runTC39File("test/language/types/number/8.5.1.js", t) - //ctx.runTC39Tests("test/language") + // ctx.runTC39File("test/language/types/number/8.5.1.js", t) + // ctx.runTC39Tests("test/language") ctx.runTC39Tests("test/language/expressions") ctx.runTC39Tests("test/language/arguments-object") ctx.runTC39Tests("test/language/asi") @@ -740,7 +826,7 @@ func TestTC39(t *testing.T) { ctx.runTC39Tests("test/language/global-code") ctx.runTC39Tests("test/language/identifier-resolution") ctx.runTC39Tests("test/language/identifiers") - //ctx.runTC39Tests("test/language/literals") // legacy octal escape in strings in strict mode and regexp + // ctx.runTC39Tests("test/language/literals") // legacy octal escape in strings in strict mode and regexp ctx.runTC39Tests("test/language/literals/numeric") ctx.runTC39Tests("test/language/punctuators") ctx.runTC39Tests("test/language/reserved-words") @@ -748,6 +834,9 @@ func TestTC39(t *testing.T) { ctx.runTC39Tests("test/language/statements") ctx.runTC39Tests("test/language/types") ctx.runTC39Tests("test/language/white-space") + ctx.runTC39Tests("test/language/module-code") + ctx.runTC39Tests("test/language/export") + ctx.runTC39Tests("test/language/import") ctx.runTC39Tests("test/built-ins") ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr") ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimLeft") From 0d2f247605a5778244365b9166ee1f5c27fb5670 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sat, 11 Dec 2021 14:09:19 +0200 Subject: [PATCH 006/124] WIP more "fixes" This mostly gets some test failing in a different way - not certain it's correct --- compiler.go | 3 +-- parser/statement.go | 3 ++- tc39_test.go | 6 ------ 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/compiler.go b/compiler.go index c967ca93..1df32ef0 100644 --- a/compiler.go +++ b/compiler.go @@ -682,7 +682,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { scope := c.scope scope.dynamic = true scope.strict = true - ownVarScope := true + ownVarScope := false ownLexScope := true c.newBlockScope() scope = c.scope @@ -718,7 +718,6 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.emit(enter) } } - c.CyclicModuleRecordConcreteLink(module) if len(scope.bindings) > 0 && !ownLexScope { var lets, consts []unistring.String diff --git a/parser/statement.go b/parser/statement.go index cc36804c..6a8dd829 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -88,9 +88,10 @@ func (self *_parser) parseStatement() ast.Statement { case token.EXPORT: exp := self.parseExportDeclaration() if exp != nil { + // TODO this needs to be fixed self.scope.exportEntries = append(self.scope.exportEntries, exp) + return exp } - return exp case token.IMPORT: imp := self.parseImportDeclaration() self.scope.importEntries = append(self.scope.importEntries, imp) diff --git a/tc39_test.go b/tc39_test.go index 16c7e316..009e9932 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -733,12 +733,6 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R early = false err = p.Evaluate() - if err != nil { - return - } - fmt.Println(p.compiler.p) - err = p.ExecuteModule() - return } From 56ef325e581123f65b69dc1d4963e0ce70851ab5 Mon Sep 17 00:00:00 2001 From: oleiade Date: Wed, 15 Dec 2021 15:56:40 +0100 Subject: [PATCH 007/124] WIP treat import and export as identifiers in Object Property Key parsing --- parser/expression.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/parser/expression.go b/parser/expression.go index 9430c745..15c7bb44 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -308,6 +308,12 @@ func (self *_parser) parseObjectPropertyKey() (string, unistring.String, ast.Exp Literal: literal, Value: parsedLiteral, } + case token.EXPORT, token.IMPORT: + value = &ast.StringLiteral{ + Idx: idx, + Literal: literal, + Value: parsedLiteral, + } default: // null, false, class, etc. if isId(tkn) { From 656812082028cd622e17a254407d57553e7d6d3d Mon Sep 17 00:00:00 2001 From: oleiade Date: Mon, 20 Dec 2021 14:43:22 +0100 Subject: [PATCH 008/124] WIP address reserved-words/ident-name-keyword-memberexpr.js test --- parser/lexer.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/parser/lexer.go b/parser/lexer.go index e535048f..20fd959e 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -226,7 +226,10 @@ func isId(tkn token.Token) bool { token.CONTINUE, token.DEBUGGER, - token.INSTANCEOF: + token.INSTANCEOF, + + token.EXPORT, + token.IMPORT: return true } From 96d718b7c25a57a2c028dd87484f244c46324e21 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sun, 12 Dec 2021 12:11:55 +0200 Subject: [PATCH 009/124] finish implementing ParseModule --- module.go | 68 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/module.go b/module.go index ab410f63..f67ae9cb 100644 --- a/module.go +++ b/module.go @@ -3,7 +3,6 @@ package goja import ( "errors" "fmt" - "sort" "github.com/dop251/goja/ast" ) @@ -47,14 +46,14 @@ type CyclicModuleRecord interface { func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) error { if module.Status() == Linking || module.Status() == Evaluating { - return errors.New("bad status on link") + return fmt.Errorf("bad status %+v on link", module.Status()) } stack := []CyclicModuleRecord{} if _, err := c.innerModuleLinking(module, &stack, 0); err != nil { for _, m := range stack { if m.Status() != Linking { - return errors.New("bad status on link") + return fmt.Errorf("bad status %+v on link", m.Status()) } m.SetStatus(Unlinked) @@ -229,11 +228,6 @@ type exportEntry struct { localName string } -func includes(slice []string, s string) bool { - i := sort.SearchStrings(slice, s) - return i < len(slice) && slice[i] == s -} - func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { var result []importEntry for _, importDeclarion := range declarations { @@ -305,6 +299,16 @@ func requestedModulesFromAst(imports []*ast.ImportDeclaration, exports []*ast.Ex return result } +func findImportByLocalName(importEntries []importEntry, name string) (importEntry, bool) { + for _, i := range importEntries { + if i.localName == name { + return i, true + } + } + + return importEntry{}, false +} + // This should probably be part of Parse // TODO arguments to this need fixing func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, error) { @@ -314,29 +318,36 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro if err != nil { return nil, err } - // Let body be ParseText(sourceText, Module). - // 3. If body is a List of errors, return body. requestedModules := requestedModulesFromAst(body.ImportEntries, body.ExportEntries) - // 5. Let importEntries be ImportEntries of body. - // importEntries := body.ImportEntries TODO fix - // 6. Let importedBoundNames be ImportedLocalNames(importEntries). - var importedBoundNames []string // fix - // 7. Let indirectExportEntries be a new empty List. - // 8. Let localExportEntries be a new empty List. - var localExportEntries []exportEntry // fix - // 9. Let starExportEntries be a new empty List. - // 10. Let exportEntries be ExportEntries of body. importEntries := importEntriesFromAst(body.ImportEntries) + // 6. Let importedBoundNames be ImportedLocalNames(importEntries). + // ^ is skipped as we don't need it. + + var indirectExportEntries []exportEntry + var localExportEntries []exportEntry + var starExportEntries []exportEntry exportEntries := exportEntriesFromAst(body.ExportEntries) for _, ee := range exportEntries { if ee.moduleRequest == "" { // technically nil - if !includes(importedBoundNames, ee.localName) { // TODO make it not true always + if ie, ok := findImportByLocalName(importEntries, ee.localName); !ok { localExportEntries = append(localExportEntries, ee) } else { - // TODO logic when we reexport something imported + if ie.importName == "*" { + localExportEntries = append(localExportEntries, ee) + } else { + indirectExportEntries = append(indirectExportEntries, exportEntry{ + moduleRequest: ie.moduleRequest, + importName: ie.importName, + exportName: ee.exportName, + }) + } } } else { - // TODO implement this where we have export {s } from "somewhere"; and co. + if ee.importName == "*" && ee.exportName == "" { + starExportEntries = append(starExportEntries, ee) + } else { + indirectExportEntries = append(indirectExportEntries, ee) + } } } return &SourceTextModuleRecord{ @@ -347,18 +358,15 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro status: Unlinked, requestedModules: requestedModules, }, - // EvaluationError is undefined // hostDefined TODO body: body, // Context empty // importMeta empty - importEntries: importEntries, - localExportEntries: localExportEntries, - // indirectExportEntries TODO - // starExportEntries TODO - // DFSIndex is empty - // DFSAncestorIndex is empty - }, nil // TODO fix + importEntries: importEntries, + localExportEntries: localExportEntries, + indirectExportEntries: indirectExportEntries, + starExportEntries: starExportEntries, + }, nil } func (module *SourceTextModuleRecord) ExecuteModule() error { From d181d6493310c26a7c566f8376a82fc905b5d928 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 9 Feb 2022 16:01:17 +0200 Subject: [PATCH 010/124] Revert some changes to compileModule --- compiler.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/compiler.go b/compiler.go index 1df32ef0..249c8ff8 100644 --- a/compiler.go +++ b/compiler.go @@ -678,19 +678,29 @@ found: func (c *compiler) compileModule(module *SourceTextModuleRecord) { in := module.body c.p.src = in.File + strict := true + inGlobal := false + eval := false + c.newScope() scope := c.scope scope.dynamic = true - scope.strict = true - ownVarScope := false - ownLexScope := true - c.newBlockScope() - scope = c.scope - scope.module = true + scope.eval = eval + if !strict && len(in.Body) > 0 { + strict = c.isStrict(in.Body) != nil + } + scope.strict = strict + ownVarScope := eval && strict + ownLexScope := !inGlobal || eval + if ownVarScope { + c.newBlockScope() + scope = c.scope + scope.function = true + } funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) numFuncs := len(scope.bindings) - if false && !ownVarScope { + if inGlobal && !ownVarScope { if numFuncs == len(funcs) { c.compileFunctionsGlobalAllUnique(funcs) } else { @@ -704,7 +714,15 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { vars[i] = b.name } if len(vars) > 0 && !ownVarScope && ownLexScope { - c.emit(&bindVars{names: vars, deletable: false}) + if inGlobal { + c.emit(&bindGlobal{ + vars: vars[numFuncs:], + funcs: vars[:numFuncs], + deletable: eval, + }) + } else { + c.emit(&bindVars{names: vars, deletable: eval}) + } } var enter *enterBlock if c.compileLexicalDeclarations(in.Body, ownVarScope || !ownLexScope) { @@ -735,7 +753,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { consts: consts, }) } - if ownVarScope { + if !inGlobal || ownVarScope { c.compileFunctions(funcs) } c.compileStatements(in.Body, true) From ea0512e67768fe4c4e6c29f744af8eae5b9501ad Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 9 Feb 2022 17:30:39 +0200 Subject: [PATCH 011/124] Require import/export to be in the global scope --- parser/statement.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/parser/statement.go b/parser/statement.go index e3d5eb92..c4bfd487 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -86,6 +86,11 @@ func (self *_parser) parseStatement() ast.Statement { case token.TRY: return self.parseTryStatement() case token.EXPORT: + if self.scope.outer != nil { + self.next() + self.error(self.idx, "export only allowed in global scope") + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } exp := self.parseExportDeclaration() if exp != nil { // TODO this needs to be fixed @@ -93,6 +98,11 @@ func (self *_parser) parseStatement() ast.Statement { return exp } case token.IMPORT: + if self.scope.outer != nil { + self.next() + self.error(self.idx, "import only allowed in global scope") + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } imp := self.parseImportDeclaration() self.scope.importEntries = append(self.scope.importEntries, imp) return imp From 8fc9a3b39a8873998617be5a7bfd9344fff3e727 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 9 Feb 2022 19:39:33 +0200 Subject: [PATCH 012/124] Fix some export as syntax --- parser/statement.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/parser/statement.go b/parser/statement.go index c4bfd487..64f46311 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -844,8 +844,12 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { } case token.LEFT_BRACE: namedExports := self.parseNamedExports() + fromClause := self.parseFromClause() self.semicolon() - return &ast.ExportDeclaration{NamedExports: namedExports} + return &ast.ExportDeclaration{ + NamedExports: namedExports, + FromClause: fromClause, + } case token.VAR: self.next() return &ast.ExportDeclaration{Variable: self.parseVariableStatement()} @@ -986,6 +990,10 @@ func (self *_parser) parseNamedExports() *ast.NamedExports { // MOVE IN EXPRESSION func (self *_parser) parseExportsList() (exportsList []*ast.ExportSpecifier) { // FIXME: ast.Binding? + if self.token == token.RIGHT_BRACE { + return + } + for { exportsList = append(exportsList, self.parseExportSpecifier()) if self.token != token.COMMA { From d0e7456e3d346ae2f3a86fe7edef255185f9c6a0 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 9 Feb 2022 19:39:49 +0200 Subject: [PATCH 013/124] tc39: run module tests only as strict --- tc39_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tc39_test.go b/tc39_test.go index c9b90ae4..33da3fe2 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -596,13 +596,13 @@ func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { hasRaw := meta.hasFlag("raw") - if hasRaw || !meta.hasFlag("onlyStrict") { + if hasRaw || !meta.hasFlag("onlyStrict") && !meta.hasFlag("module") { // log.Printf("Running normal test: %s", name) t.Logf("Running normal test: %s", name) ctx.runTC39Test(name, src, meta, t) } - if !hasRaw && !meta.hasFlag("noStrict") { + if (!hasRaw && !meta.hasFlag("noStrict")) || meta.hasFlag("module") { // log.Printf("Running strict test: %s", name) t.Logf("Running strict test: %s", name) ctx.runTC39Test(name, "'use strict';\n"+src, meta, t) From 6a26d4e73f5a03d1495380996a26c794c5ac5ef3 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sun, 13 Feb 2022 18:06:36 +0200 Subject: [PATCH 014/124] Fix a bunch of parsing and compilation errors --- ast/node.go | 17 +++--- compiler.go | 45 +++++++++++++++ compiler_stmt.go | 3 +- module.go | 135 ++++++++++++++++++++++++++++++++++++++++++-- parser/statement.go | 30 +++++----- tc39_test.go | 5 +- 6 files changed, 207 insertions(+), 28 deletions(-) diff --git a/ast/node.go b/ast/node.go index 459d7eaf..d1645782 100644 --- a/ast/node.go +++ b/ast/node.go @@ -444,8 +444,7 @@ type ( } ImportDeclaration struct { - idx0 file.Idx - idx1 file.Idx + Idx file.Idx ImportClause *ImportClause FromClause *FromClause ModuleSpecifier unistring.String @@ -471,9 +470,11 @@ type ( } ExportDeclaration struct { - idx0 file.Idx - idx1 file.Idx - Variable *VariableStatement + idx0 file.Idx + idx1 file.Idx + Variable *VariableStatement + LexicalDeclaration *LexicalDeclaration + // ClassDeclaration NamedExports *NamedExports ExportFromClause *ExportFromClause FromClause *FromClause @@ -500,6 +501,8 @@ type ( HoistableDeclaration struct { FunctionDeclaration *FunctionLiteral + // GeneratorDeclaration + // AsyncFunc and AsyncGenerator } ) @@ -681,7 +684,7 @@ func (self *PropertyKeyed) Idx0() file.Idx { return self.Key.Idx func (self *ExpressionBody) Idx0() file.Idx { return self.Expression.Idx0() } func (self *ExportDeclaration) Idx0() file.Idx { return self.idx0 } -func (self *ImportDeclaration) Idx0() file.Idx { return self.idx0 } +func (self *ImportDeclaration) Idx0() file.Idx { return self.Idx } // ==== // // Idx1 // @@ -785,4 +788,4 @@ func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() } func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() } func (self *ExportDeclaration) Idx1() file.Idx { return self.idx1 } -func (self *ImportDeclaration) Idx1() file.Idx { return self.idx1 } +func (self *ImportDeclaration) Idx1() file.Idx { return self.Idx } // TODO fix diff --git a/compiler.go b/compiler.go index 24a38740..2f13dd3a 100644 --- a/compiler.go +++ b/compiler.go @@ -692,12 +692,45 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.newScope() scope := c.scope + scope.module = true + module.scope = scope scope.dynamic = true scope.eval = eval if !strict && len(in.Body) > 0 { strict = c.isStrict(in.Body) != nil } scope.strict = strict + // 15.2.1.17.4 step 9 start + for _, in := range module.importEntries { + importedModule, err := module.rt.hostResolveImportedModule(module, in.moduleRequest) + if err != nil { + panic(fmt.Errorf("previously resolved module returned error %w", err)) + } + if in.importName == "*" { + namespace := module.rt.getModuleNamespace(importedModule) + b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) + _ = namespace + _ = b + // TODO fix + } else { + resolution, ambiguous := importedModule.ResolveExport(in.importName) + if resolution == nil || ambiguous { + c.throwSyntaxError(in.offset, "ambiguous import of %s", in.importName) + } + if resolution.BindingName == "*namespace*" { + namespace := module.rt.getModuleNamespace(resolution.Module) + b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) + _ = namespace + _ = b + // TODO fix + } else { + c.createImportBinding(in.localName, resolution.Module, resolution.BindingName) + } + + } + + } + // 15.2.1.17.4 step 9 end ownVarScope := eval && strict ownLexScope := !inGlobal || eval if ownVarScope { @@ -999,6 +1032,18 @@ func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { } } +func (c *compiler) createImmutableBinding(name unistring.String, isStrict bool) *binding { + b, _ := c.scope.bindName(name) + b.isConst = true + b.isStrict = isStrict + return b +} + +func (c *compiler) createImportBinding(n string, m ModuleRecord, n2 string) *binding { + // TODO Do something :shrug: + return nil +} + func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding { if name == "let" { c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") diff --git a/compiler_stmt.go b/compiler_stmt.go index 995f7d08..a80dc0e4 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -807,7 +807,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "import of %s was not expoted from module %s", name.IdentifierName.String(), expr.FromClause.ModuleSpecifier.String()) } r := &compiledLiteral{ - // val: value, + val: Undefined(), // TODO FIX } r.init(c, expr.Idx0()) // This should probably be the idx of the name? @@ -825,6 +825,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "import of \"default\" was not exported from module %s", expr.FromClause.ModuleSpecifier.String()) } r := &compiledLiteral{ + val: Undefined(), // TODO FIX // val: value, } r.init(c, def.Idx0()) diff --git a/module.go b/module.go index f67ae9cb..a67f466a 100644 --- a/module.go +++ b/module.go @@ -3,6 +3,7 @@ package goja import ( "errors" "fmt" + "sort" "github.com/dop251/goja/ast" ) @@ -16,6 +17,8 @@ type ModuleRecord interface { ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error Evaluate() error + Namespace() *Namespace + SetNamespace(*Namespace) } type CyclicModuleRecordStatus uint8 @@ -51,6 +54,7 @@ func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) err stack := []CyclicModuleRecord{} if _, err := c.innerModuleLinking(module, &stack, 0); err != nil { + fmt.Println(err) for _, m := range stack { if m.Status() != Linking { return fmt.Errorf("bad status %+v on link", m.Status()) @@ -204,6 +208,7 @@ var _ CyclicModuleRecord = &SourceTextModuleRecord{} type SourceTextModuleRecord struct { cyclicModuleStub + scope *scope rt *Runtime // TODO this is not great as it means the whole thing needs to be reparsed for each runtime compiler *compiler // TODO remove this body *ast.Program @@ -219,6 +224,7 @@ type importEntry struct { moduleRequest string importName string localName string + offset int } type exportEntry struct { @@ -242,6 +248,7 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { moduleRequest: moduleRequest, importName: el.IdentifierName.String(), localName: el.Alias.String(), + offset: int(importDeclarion.Idx0()), }) } } @@ -250,6 +257,7 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { moduleRequest: moduleRequest, importName: "default", localName: def.Name.String(), + offset: int(importDeclarion.Idx0()), }) } if namespace := importClause.NameSpaceImport; namespace != nil { @@ -257,6 +265,7 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { moduleRequest: moduleRequest, importName: "*", localName: namespace.ImportedBinding.String(), + offset: int(importDeclarion.Idx0()), }) } } @@ -265,18 +274,91 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { var result []exportEntry - for _, exportDeclarion := range declarations { - if exportDeclarion.ExportFromClause != nil { - if exportDeclarion.ExportFromClause.NamedExports != nil { - for _, spec := range exportDeclarion.ExportFromClause.NamedExports.ExportsList { + for _, exportDeclaration := range declarations { + if exportDeclaration.ExportFromClause != nil { + exportFromClause := exportDeclaration.ExportFromClause + if namedExports := exportFromClause.NamedExports; namedExports != nil { + for _, spec := range namedExports.ExportsList { result = append(result, exportEntry{ localName: spec.IdentifierName.String(), exportName: spec.Alias.String(), }) } + } else if exportFromClause.IsWildcard { + if from := exportDeclaration.FromClause; from != nil { + result = append(result, exportEntry{ + exportName: exportFromClause.Alias.String(), + importName: "*", + moduleRequest: from.ModuleSpecifier.String(), + }) + } else { + result = append(result, exportEntry{ + exportName: exportFromClause.Alias.String(), + importName: "*", + }) + } } else { - fmt.Println("unimplemented", exportDeclarion.ExportFromClause) + fmt.Printf("unimplemented %+v\n", exportDeclaration.ExportFromClause) + panic("wat") } + } else if variableDeclaration := exportDeclaration.Variable; variableDeclaration != nil { + for _, l := range variableDeclaration.List { + id, ok := l.Target.(*ast.Identifier) + if !ok { + panic("target wasn;t identifier") + } + result = append(result, exportEntry{ + localName: id.Name.String(), + exportName: id.Name.String(), + }) + + } + } else if LexicalDeclaration := exportDeclaration.LexicalDeclaration; LexicalDeclaration != nil { + for _, l := range LexicalDeclaration.List { + + id, ok := l.Target.(*ast.Identifier) + if !ok { + panic("target wasn;t identifier") + } + result = append(result, exportEntry{ + localName: id.Name.String(), + exportName: id.Name.String(), + }) + + } + } else if hoistable := exportDeclaration.HoistableDeclaration; hoistable != nil { + localName := "default" + if hoistable.FunctionDeclaration.Name != nil { + localName = string(hoistable.FunctionDeclaration.Name.Name.String()) + } + result = append(result, exportEntry{ + localName: localName, + exportName: "default", + }) + } else if fromClause := exportDeclaration.FromClause; fromClause != nil { + if namedExports := exportDeclaration.NamedExports; namedExports != nil { + for _, spec := range namedExports.ExportsList { + result = append(result, exportEntry{ + localName: spec.IdentifierName.String(), + exportName: spec.Alias.String(), + moduleRequest: fromClause.ModuleSpecifier.String(), + }) + } + } else { + fmt.Printf("unimplemented %+v\n", exportDeclaration.ExportFromClause) + panic("wat") + } + } else if namedExports := exportDeclaration.NamedExports; namedExports != nil { + for _, spec := range namedExports.ExportsList { + result = append(result, exportEntry{ + localName: spec.IdentifierName.String(), + exportName: spec.Alias.String(), + }) + } + } else { + fmt.Printf("unimplemented %+v\n", exportDeclaration) + panic("wat") + } } return result @@ -449,6 +531,40 @@ func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { */ } +func (rt *Runtime) getModuleNamespace(module ModuleRecord) *Namespace { + if c, ok := module.(CyclicModuleRecord); ok && c.Status() == Unlinked { + panic("oops") // TODO beter oops + } + namespace := module.Namespace() + if namespace == nil { + exportedNames := module.GetExportedNames() + var unambiguousNames []string + for _, name := range exportedNames { + _, ok := module.ResolveExport(name) + if ok { + unambiguousNames = append(unambiguousNames, name) + } + } + namespace := rt.moduleNamespaceCreate(module, unambiguousNames) + module.SetNamespace(namespace) + } + return namespace +} + +// TODO this probably should really be goja.Object +type Namespace struct { + module ModuleRecord + exports []string +} + +func (rt *Runtime) moduleNamespaceCreate(module ModuleRecord, exports []string) *Namespace { + sort.Strings(exports) + return &Namespace{ + module: module, + exports: exports, + } +} + type ResolveSetElement struct { Module ModuleRecord ExportName string @@ -529,6 +645,7 @@ func (module *SourceTextModuleRecord) Link() error { } type cyclicModuleStub struct { + namespace *Namespace status CyclicModuleRecordStatus dfsIndex uint ancestorDfsIndex uint @@ -575,3 +692,11 @@ func (c *cyclicModuleStub) SetRequestedModules(modules []string) { func (c *cyclicModuleStub) RequestedModules() []string { return c.requestedModules } + +func (c *cyclicModuleStub) Namespace() *Namespace { + return c.namespace +} + +func (c *cyclicModuleStub) SetNamespace(namespace *Namespace) { + c.namespace = namespace +} diff --git a/parser/statement.go b/parser/statement.go index 64f46311..3fc96d23 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -837,7 +837,6 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.MULTIPLY: // FIXME: should also parse NamedExports if '{' exportFromClause := self.parseExportFromClause() fromClause := self.parseFromClause() - self.semicolon() return &ast.ExportDeclaration{ ExportFromClause: exportFromClause, FromClause: fromClause, @@ -845,16 +844,18 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.LEFT_BRACE: namedExports := self.parseNamedExports() fromClause := self.parseFromClause() - self.semicolon() return &ast.ExportDeclaration{ NamedExports: namedExports, FromClause: fromClause, } case token.VAR: - self.next() return &ast.ExportDeclaration{Variable: self.parseVariableStatement()} - case token.FUNCTION, token.LET, token.CONST: // FIXME: What about function* and async? - break // TODO: implement me! + case token.LET, token.CONST: + return &ast.ExportDeclaration{LexicalDeclaration: self.parseLexicalDeclaration(self.token)} + case token.FUNCTION: // FIXME: What about function* and async? + // TODO implement + self.next() + self.error(self.idx, "unsupported") case token.DEFAULT: // FIXME: current implementation of HoistableDeclaration only implements function? self.next() functionDeclaration := self.parseFunction(false) @@ -873,6 +874,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { } func (self *_parser) parseImportDeclaration() *ast.ImportDeclaration { + idx := self.idx self.next() // _, _, errString := self.scanString(0, false) // look at first character only @@ -885,11 +887,11 @@ func (self *_parser) parseImportDeclaration() *ast.ImportDeclaration { importClause := self.parseImportClause() fromClause := self.parseFromClause() - self.semicolon() return &ast.ImportDeclaration{ ImportClause: importClause, FromClause: fromClause, + Idx: idx, } } @@ -927,11 +929,15 @@ func (self *_parser) nextStatement() { } func (self *_parser) parseFromClause() *ast.FromClause { - identifier := self.parseIdentifier() - if identifier.Name != "from" { - self.error(self.idx, "expected 'from' keyword, found '%s' instead", identifier.Name) - return nil + if self.literal == "from" { + self.next() + return &ast.FromClause{ + ModuleSpecifier: self.parseModuleSpecifier(), + } + } else { + self.semicolon() } + return nil // expression := self.parsePrimaryExpression() // module specifier (string literal) // stringLiteral, ok := expression.(*ast.StringLiteral) @@ -939,10 +945,6 @@ func (self *_parser) parseFromClause() *ast.FromClause { // self.error(self.idx, "expected module specifier") // return nil // } - - return &ast.FromClause{ - ModuleSpecifier: self.parseModuleSpecifier(), - } } func (self *_parser) parseExportFromClause() *ast.ExportFromClause { diff --git a/tc39_test.go b/tc39_test.go index 33da3fe2..848f4a6b 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -205,6 +205,8 @@ var ( "test/language/expressions/optional-chaining/member-expression-async-literal.js": true, "test/language/expressions/optional-chaining/member-expression-async-identifier.js": true, "test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true, + "test/language/statements/labeled/value-await-module.js": true, + "test/language/statements/labeled/value-await-module-escaped.js": true, // legacy number literals "test/language/literals/numeric/non-octal-decimal-integer.js": true, @@ -531,6 +533,7 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. } } else { if meta.Negative.Type != "" { + t.Fatalf("%s: Expected error: %v", name, err) vm.vm.prg.dumpCode(t.Logf) t.Fatalf("%s: Expected error: %v", name, err) } @@ -732,6 +735,7 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } p.rt = vm + early = false compiler := newCompiler() p.compiler = compiler compiler.hostResolveImportedModule = hostResolveImportedModule @@ -740,7 +744,6 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R return } - early = false err = p.Evaluate() return } From 6a96898f73f2a8cbc61538f26221b882bdef1387 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 14 Feb 2022 12:31:49 +0200 Subject: [PATCH 015/124] Fix hostResolveImportedModule in tc39 --- tc39_test.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tc39_test.go b/tc39_test.go index 848f4a6b..2bde1104 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -696,49 +696,45 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { - k, ok := cache[specifier] + fname := path.Join(ctx.base, path.Dir(name), specifier) + k, ok := cache[fname] if ok { return k.m, k.err } - fname := path.Join(ctx.base, path.Dir(name), specifier) f, err := os.Open(fname) if err != nil { - cache[specifier] = cacheElement{err: err} + cache[fname] = cacheElement{err: err} return nil, err } defer f.Close() b, err := ioutil.ReadAll(f) if err != nil { - cache[specifier] = cacheElement{err: err} + cache[fname] = cacheElement{err: err} return nil, err } str := string(b) p, err := vm.ParseModule(str) if err != nil { - cache[specifier] = cacheElement{err: err} + cache[fname] = cacheElement{err: err} return nil, err } p.rt = vm p.compiler = newCompiler() p.compiler.hostResolveImportedModule = hostResolveImportedModule - cache[specifier] = cacheElement{m: p} + cache[fname] = cacheElement{m: p} return p, nil } vm.hostResolveImportedModule = hostResolveImportedModule - var p *SourceTextModuleRecord - p, err = vm.ParseModule(src) + m, err := vm.hostResolveImportedModule(nil, path.Base(name)) if err != nil { return } - p.rt = vm + p := m.(*SourceTextModuleRecord) early = false - compiler := newCompiler() - p.compiler = compiler - compiler.hostResolveImportedModule = hostResolveImportedModule err = p.Link() if err != nil { return From 4430b25f6411b189957eab52fb68acc5e918d820 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 14 Feb 2022 13:08:27 +0200 Subject: [PATCH 016/124] Fix some early errors --- compiler_stmt.go | 15 ++++++++++++++- module.go | 6 ++++-- parser/parser.go | 6 +++++- parser/statement.go | 11 +++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/compiler_stmt.go b/compiler_stmt.go index a80dc0e4..85e2366d 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -767,16 +767,29 @@ func (c *compiler) emitVarAssign(name unistring.String, offset int, init compile } func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { + /* TODO fix if !c.scope.module { - c.throwSyntaxError(int(expr.Idx0()), "import not allowed in non module code") + c.throwSyntaxError(int(expr.Idx0()), "export not allowed in non module code") + } + */ + + if expr.Variable != nil { + c.compileVariableStatement(expr.Variable) + } else if expr.LexicalDeclaration != nil { + c.compileLexicalDeclaration(expr.LexicalDeclaration) + } else if expr.HoistableDeclaration != nil { + // TODO this is not enough I think + c.compileFunctionLiteral(expr.HoistableDeclaration.FunctionDeclaration, false) } // TODO } func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { + /* TODO fix if !c.scope.module { c.throwSyntaxError(int(expr.Idx0()), "import not allowed in non module code") } + */ if expr.FromClause == nil { return // TODO is this right? } diff --git a/module.go b/module.go index a67f466a..bf9389e9 100644 --- a/module.go +++ b/module.go @@ -6,6 +6,7 @@ import ( "sort" "github.com/dop251/goja/ast" + "github.com/dop251/goja/parser" ) // TODO most things here probably should be unexported and names should be revised before merged in master @@ -107,7 +108,7 @@ func (co *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleReco } } } - err = module.InitializeEnvorinment() // TODO implement + err = module.InitializeEnvorinment() if err != nil { return 0, err } @@ -395,7 +396,8 @@ func findImportByLocalName(importEntries []importEntry, name string) (importEntr // TODO arguments to this need fixing func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, error) { // TODO asserts - body, err := Parse("module", sourceText, rt.parserOptions...) + opts := append(rt.parserOptions, parser.IsModule) + body, err := Parse("module", sourceText, opts...) _ = body if err != nil { return nil, err diff --git a/parser/parser.go b/parser/parser.go index b1bc74bb..d5d22454 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -53,6 +53,7 @@ const ( ) type options struct { + module bool disableSourceMaps bool sourceMapLoader func(path string) ([]byte, error) } @@ -67,6 +68,10 @@ func WithDisableSourceMaps(opts *options) { opts.disableSourceMaps = true } +func IsModule(opts *options) { + opts.module = true +} + // WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a // URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name // of the file being parsed. Any error returned by the loader will fail the parsing. @@ -189,7 +194,6 @@ func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mod // The parameter list, if any, should be a comma-separated list of identifiers. // func ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) { - src := "(function(" + parameterList + ") {\n" + body + "\n})" parser := _newParser("", src, 1, options...) diff --git a/parser/statement.go b/parser/statement.go index 3fc96d23..65a3734a 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -91,6 +91,11 @@ func (self *_parser) parseStatement() ast.Statement { self.error(self.idx, "export only allowed in global scope") return &ast.BadStatement{From: self.idx, To: self.idx + 1} } + if !self.opts.module { + self.next() + self.error(self.idx, "export not supported in script") + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } exp := self.parseExportDeclaration() if exp != nil { // TODO this needs to be fixed @@ -103,8 +108,14 @@ func (self *_parser) parseStatement() ast.Statement { self.error(self.idx, "import only allowed in global scope") return &ast.BadStatement{From: self.idx, To: self.idx + 1} } + if !self.opts.module { + self.next() + self.error(self.idx, "import not supported in script") + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } imp := self.parseImportDeclaration() self.scope.importEntries = append(self.scope.importEntries, imp) + return imp } From af5be259134665f6a9aa0f3492d73c83c393eb8f Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 4 Apr 2022 14:52:09 +0300 Subject: [PATCH 017/124] wip --- ast/node.go | 1 + compiler.go | 81 ++++++++++++++++++++++++++++++++++++--------- compiler_expr.go | 4 +++ compiler_stmt.go | 39 ++++++++++------------ go.mod | 1 + go.sum | 2 ++ module.go | 56 ++++++++++++++++++++++++++++--- parser/scope.go | 19 ++++++----- parser/statement.go | 51 +++++++++++++++++++--------- tc39_test.go | 6 ++-- 10 files changed, 191 insertions(+), 69 deletions(-) diff --git a/ast/node.go b/ast/node.go index d1645782..cdb307fd 100644 --- a/ast/node.go +++ b/ast/node.go @@ -473,6 +473,7 @@ type ( idx0 file.Idx idx1 file.Idx Variable *VariableStatement + AssignExpression Expression LexicalDeclaration *LexicalDeclaration // ClassDeclaration NamedExports *NamedExports diff --git a/compiler.go b/compiler.go index 2f13dd3a..18c1d9c9 100644 --- a/compiler.go +++ b/compiler.go @@ -353,7 +353,11 @@ func newCompiler() *compiler { } func (p *Program) defineLiteralValue(val Value) uint32 { + if val == nil { + panic("wat") + } for idx, v := range p.values { + fmt.Println(v) if v.SameAs(val) { return uint32(idx) } @@ -684,6 +688,7 @@ found: } func (c *compiler) compileModule(module *SourceTextModuleRecord) { + fmt.Println("start compileModule") in := module.body c.p.src = in.File strict := true @@ -700,6 +705,14 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { strict = c.isStrict(in.Body) != nil } scope.strict = strict + ownVarScope := eval && strict || true + ownLexScope := !inGlobal || eval + if ownVarScope { + c.newBlockScope() + scope = c.scope + scope.function = true + } + // fmt.Println(scope) // 15.2.1.17.4 step 9 start for _, in := range module.importEntries { importedModule, err := module.rt.hostResolveImportedModule(module, in.moduleRequest) @@ -708,36 +721,48 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { } if in.importName == "*" { namespace := module.rt.getModuleNamespace(importedModule) + fmt.Println(in) b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) _ = namespace _ = b // TODO fix + r := &compiledLiteral{ + val: stringValueFromRaw("pe"), // TODO FIX + // val: value, + } + r.init(c, 0) } else { + fmt.Println("name", in.importName) resolution, ambiguous := importedModule.ResolveExport(in.importName) if resolution == nil || ambiguous { c.throwSyntaxError(in.offset, "ambiguous import of %s", in.importName) } if resolution.BindingName == "*namespace*" { namespace := module.rt.getModuleNamespace(resolution.Module) + fmt.Println(in) b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) _ = namespace _ = b + r := &compiledIdentifierExpr{ + name: unistring.String(in.localName), + // val: value, + } + r.init(c, 0) // TODO fix } else { c.createImportBinding(in.localName, resolution.Module, resolution.BindingName) - } + r := &compiledIdentifierExpr{ + name: unistring.String(in.localName), + // val: value, + } + fmt.Println("r:", r) + r.init(c, 0) + } } - } // 15.2.1.17.4 step 9 end - ownVarScope := eval && strict - ownLexScope := !inGlobal || eval - if ownVarScope { - c.newBlockScope() - scope = c.scope - scope.function = true - } + // fmt.Println("end") funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) numFuncs := len(scope.bindings) @@ -777,6 +802,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.emit(enter) } } + // fmt.Println("somepoint", enter) if len(scope.bindings) > 0 && !ownLexScope { var lets, consts []unistring.String @@ -794,18 +820,23 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { consts: consts, }) } + // fmt.Println("somepoint 2") if !inGlobal || ownVarScope { c.compileFunctions(funcs) } + // fmt.Println("somepoint 3") c.compileStatements(in.Body, true) + // fmt.Println("somepoint 4") if enter != nil { c.leaveScopeBlock(enter) c.popScope() } + // fmt.Println("somepoint 5") c.p.code = append(c.p.code, halt) scope.finaliseVarAlloc(0) + // fmt.Println("end 2") } func (c *compiler) compile(in *ast.Program, strict, eval, inGlobal bool) { @@ -1033,7 +1064,9 @@ func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { } func (c *compiler) createImmutableBinding(name unistring.String, isStrict bool) *binding { + fmt.Println("immutable binding for ", name) b, _ := c.scope.bindName(name) + fmt.Println(b) b.isConst = true b.isStrict = isStrict return b @@ -1041,6 +1074,15 @@ func (c *compiler) createImmutableBinding(name unistring.String, isStrict bool) func (c *compiler) createImportBinding(n string, m ModuleRecord, n2 string) *binding { // TODO Do something :shrug: + // TODO fix this for not source text module records + + /* + binding, ok := s.scope.bindName(unistring.NewFromString(n2)) + fmt.Println(n, n2, ok) + */ + name := unistring.NewFromString(n) + localBinding, _ := c.scope.bindName(name) + _ = localBinding return nil } @@ -1095,13 +1137,22 @@ func (c *compiler) createLexicalBindings(lex *ast.LexicalDeclaration) { func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclared bool) bool { for _, st := range list { - if lex, ok := st.(*ast.LexicalDeclaration); ok { - if !scopeDeclared { - c.newBlockScope() - scopeDeclared = true - } - c.createLexicalBindings(lex) + var lex *ast.LexicalDeclaration + switch st := st.(type) { + case *ast.LexicalDeclaration: + lex = st + case *ast.ExportDeclaration: + lex = st.LexicalDeclaration + } + if lex == nil { + continue + } + if !scopeDeclared { + c.newBlockScope() + scopeDeclared = true } + c.createLexicalBindings(lex) + } return scopeDeclared } diff --git a/compiler_expr.go b/compiler_expr.go index afb5c12c..17f5115f 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -361,12 +361,15 @@ func (e *compiledIdentifierExpr) emitGetter(putOnStack bool) { func (e *compiledIdentifierExpr) emitGetterOrRef() { e.addSrcMap() if b, noDynamics := e.c.scope.lookupName(e.name); noDynamics { + fmt.Printf("dynamics %#v\n", b) if b != nil { b.emitGet() } else { panic("No dynamics and not found") } } else { + fmt.Println("no dynamics ", b) + fmt.Println(e.c.scope) if b != nil { b.emitGetVar(false) } else { @@ -1537,6 +1540,7 @@ func (e *compiledUnaryExpr) emitGetter(putOnStack bool) { goto end case token.TYPEOF: if o, ok := e.operand.(compiledExprOrRef); ok { + fmt.Println("o", o) o.emitGetterOrRef() } else { e.operand.emitGetter(true) diff --git a/compiler_stmt.go b/compiler_stmt.go index 85e2366d..4b12794c 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -767,12 +767,6 @@ func (c *compiler) emitVarAssign(name unistring.String, offset int, init compile } func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { - /* TODO fix - if !c.scope.module { - c.throwSyntaxError(int(expr.Idx0()), "export not allowed in non module code") - } - */ - if expr.Variable != nil { c.compileVariableStatement(expr.Variable) } else if expr.LexicalDeclaration != nil { @@ -785,29 +779,25 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { } func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { - /* TODO fix - if !c.scope.module { - c.throwSyntaxError(int(expr.Idx0()), "import not allowed in non module code") - } - */ if expr.FromClause == nil { return // TODO is this right? } // TODO fix, maybe cache this?!? have the current module as a field? module, err := c.hostResolveImportedModule(nil, expr.FromClause.ModuleSpecifier.String()) + _ = module // TODO fix if err != nil { // TODO this should in practice never happen c.throwSyntaxError(int(expr.Idx0()), err.Error()) } if expr.ImportClause != nil { if namespace := expr.ImportClause.NameSpaceImport; namespace != nil { - /* - r := &compiledLiteral{ - val: getModuleNamespace(module), - } - r.init(c, expr.Idx0()) - c.emitVarAssign(namespace.ImportedBinding, int(expr.Idx)-1, r) - */ + r := &compiledLiteral{ + // TODO export namespace + val: stringValueFromRaw("namespace thing"), + // val: module.Namespace()), + } + r.init(c, expr.Idx0()) + c.emitVarAssign(namespace.ImportedBinding, int(expr.Idx)-1, r) } if named := expr.ImportClause.NamedImports; named != nil { for _, name := range named.ImportsList { @@ -820,7 +810,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "import of %s was not expoted from module %s", name.IdentifierName.String(), expr.FromClause.ModuleSpecifier.String()) } r := &compiledLiteral{ - val: Undefined(), // TODO FIX + val: stringValueFromRaw("to do fix this"), // TODO FIX } r.init(c, expr.Idx0()) // This should probably be the idx of the name? @@ -829,7 +819,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { } if def := expr.ImportClause.ImportedDefaultBinding; def != nil { - + fmt.Println("heres", def) value, ambiguous := module.ResolveExport("default") if ambiguous { // also ambiguous c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of %s", "default") @@ -837,12 +827,17 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if value == nil { c.throwSyntaxError(int(expr.Idx0()), "import of \"default\" was not exported from module %s", expr.FromClause.ModuleSpecifier.String()) } + name := unistring.String(def.Name.String()) + fmt.Println("value", value, name) + c.scope.bindName(name) + a, b := c.scope.lookupName(name) + fmt.Println("lookup", a, b) r := &compiledLiteral{ - val: Undefined(), // TODO FIX + val: stringValueFromRaw("pe"), // TODO FIX // val: value, } r.init(c, def.Idx0()) - c.emitVarAssign(def.Name, int(def.Idx1())-1, r) + r.emitGetter(true) } } } diff --git a/go.mod b/go.mod index 64dae78b..ff507b35 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/dop251/goja go 1.14 require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 github.com/go-sourcemap/sourcemap v2.1.3+incompatible diff --git a/go.sum b/go.sum index 552aa985..b0723656 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 h1:tYwu/z8Y0NkkzGEh3z21mSWggMg4LwLRFucLS7TjARg= diff --git a/module.go b/module.go index bf9389e9..c3c7b887 100644 --- a/module.go +++ b/module.go @@ -275,6 +275,7 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { var result []exportEntry + // spew.Dump(declarations) for _, exportDeclaration := range declarations { if exportDeclaration.ExportFromClause != nil { exportFromClause := exportDeclaration.ExportFromClause @@ -339,9 +340,13 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { } else if fromClause := exportDeclaration.FromClause; fromClause != nil { if namedExports := exportDeclaration.NamedExports; namedExports != nil { for _, spec := range namedExports.ExportsList { + alias := spec.IdentifierName.String() + if spec.Alias.String() != "" { // TODO fix + alias = spec.Alias.String() + } result = append(result, exportEntry{ - localName: spec.IdentifierName.String(), - exportName: spec.Alias.String(), + importName: spec.IdentifierName.String(), + exportName: alias, moduleRequest: fromClause.ModuleSpecifier.String(), }) } @@ -351,17 +356,27 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { } } else if namedExports := exportDeclaration.NamedExports; namedExports != nil { for _, spec := range namedExports.ExportsList { + alias := spec.IdentifierName.String() + if spec.Alias.String() != "" { // TODO fix + alias = spec.Alias.String() + } result = append(result, exportEntry{ localName: spec.IdentifierName.String(), - exportName: spec.Alias.String(), + exportName: alias, }) } + } else if exportDeclaration.AssignExpression != nil { + result = append(result, exportEntry{ + exportName: "default", + localName: "default", + }) } else { fmt.Printf("unimplemented %+v\n", exportDeclaration) panic("wat") } } + // spew.Dump(result) return result } @@ -434,7 +449,8 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro } } } - return &SourceTextModuleRecord{ + + s := &SourceTextModuleRecord{ // realm isn't implement // environment is undefined // namespace is undefined @@ -450,7 +466,23 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro localExportEntries: localExportEntries, indirectExportEntries: indirectExportEntries, starExportEntries: starExportEntries, - }, nil + } + + s.rt = rt + names := s.getExportedNamesWithotStars() // we use this as the other one loops but wee need to early errors here + sort.Strings(names) + for i := 1; i < len(names); i++ { + if names[i] == names[i-1] { + return nil, &CompilerSyntaxError{ + CompilerError: CompilerError{ + Message: fmt.Sprintf("Duplicate export name %s", names[i]), + }, + } + } + // TODO other checks + } + + return s, nil } func (module *SourceTextModuleRecord) ExecuteModule() error { @@ -460,6 +492,17 @@ func (module *SourceTextModuleRecord) ExecuteModule() error { return err } +func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { + exportedNames := make([]string, 0, len(module.localExportEntries)+len(module.indirectExportEntries)) + for _, e := range module.localExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + for _, e := range module.indirectExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + return exportedNames +} + func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceTextModuleRecord) []string { for _, el := range exportStarSet { if el == module { // better check @@ -578,6 +621,9 @@ type ResolvedBinding struct { } func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) { + if exportName == "" { + panic("wat") + } for _, r := range resolveset { if r.Module == module && exportName == r.ExportName { // TODO better return nil, false diff --git a/parser/scope.go b/parser/scope.go index 59c3cdb9..ac3482f2 100644 --- a/parser/scope.go +++ b/parser/scope.go @@ -6,15 +6,16 @@ import ( ) type _scope struct { - outer *_scope - allowIn bool - allowLet bool - inIteration bool - inSwitch bool - inFunction bool - declarationList []*ast.VariableDeclaration - importEntries []*ast.ImportDeclaration - exportEntries []*ast.ExportDeclaration + outer *_scope + allowIn bool + allowLet bool + allowImportExport bool + inIteration bool + inSwitch bool + inFunction bool + declarationList []*ast.VariableDeclaration + importEntries []*ast.ImportDeclaration + exportEntries []*ast.ExportDeclaration labels []unistring.String } diff --git a/parser/statement.go b/parser/statement.go index 65a3734a..78f1c838 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -42,6 +42,8 @@ func (self *_parser) parseStatement() ast.Statement { return &ast.BadStatement{From: self.idx, To: self.idx + 1} } + allowImportExport := self.scope.allowImportExport + self.scope.allowImportExport = false switch self.token { case token.SEMICOLON: return self.parseEmptyStatement() @@ -86,7 +88,7 @@ func (self *_parser) parseStatement() ast.Statement { case token.TRY: return self.parseTryStatement() case token.EXPORT: - if self.scope.outer != nil { + if !allowImportExport { self.next() self.error(self.idx, "export only allowed in global scope") return &ast.BadStatement{From: self.idx, To: self.idx + 1} @@ -103,7 +105,7 @@ func (self *_parser) parseStatement() ast.Statement { return exp } case token.IMPORT: - if self.scope.outer != nil { + if !allowImportExport { self.next() self.error(self.idx, "import only allowed in global scope") return &ast.BadStatement{From: self.idx, To: self.idx + 1} @@ -667,6 +669,7 @@ func (self *_parser) parseIfStatement() ast.Statement { func (self *_parser) parseSourceElements() (body []ast.Statement) { for self.token != token.EOF { self.scope.allowLet = true + self.scope.allowImportExport = true body = append(body, self.parseStatement()) } @@ -848,6 +851,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.MULTIPLY: // FIXME: should also parse NamedExports if '{' exportFromClause := self.parseExportFromClause() fromClause := self.parseFromClause() + self.semicolon() return &ast.ExportDeclaration{ ExportFromClause: exportFromClause, FromClause: fromClause, @@ -855,6 +859,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.LEFT_BRACE: namedExports := self.parseNamedExports() fromClause := self.parseFromClause() + self.semicolon() return &ast.ExportDeclaration{ NamedExports: namedExports, FromClause: fromClause, @@ -865,16 +870,29 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { return &ast.ExportDeclaration{LexicalDeclaration: self.parseLexicalDeclaration(self.token)} case token.FUNCTION: // FIXME: What about function* and async? // TODO implement - self.next() - self.error(self.idx, "unsupported") - case token.DEFAULT: // FIXME: current implementation of HoistableDeclaration only implements function? - self.next() functionDeclaration := self.parseFunction(false) + self.semicolon() return &ast.ExportDeclaration{ HoistableDeclaration: &ast.HoistableDeclaration{ FunctionDeclaration: functionDeclaration, }, } + case token.DEFAULT: // FIXME: current implementation of HoistableDeclaration only implements function? + self.next() + switch self.token { + case token.FUNCTION: + functionDeclaration := self.parseFunction(false) + return &ast.ExportDeclaration{ + HoistableDeclaration: &ast.HoistableDeclaration{ + FunctionDeclaration: functionDeclaration, + }, + } + // TODO classes + default: + return &ast.ExportDeclaration{ + AssignExpression: self.parseAssignmentExpression(), + } + } default: namedExports := self.parseNamedExports() self.semicolon() @@ -1007,7 +1025,7 @@ func (self *_parser) parseExportsList() (exportsList []*ast.ExportSpecifier) { / return } - for { + for self.token != token.RIGHT_BRACE { exportsList = append(exportsList, self.parseExportSpecifier()) if self.token != token.COMMA { break @@ -1027,13 +1045,11 @@ func (self *_parser) parseExportSpecifier() *ast.ExportSpecifier { return &ast.ExportSpecifier{IdentifierName: identifier.Name} } - as := self.parseIdentifier() - // We reach this point only if the next token is an identifier. - // Let's now verify if it's "as". - if as.Name != "as" { // FIXME: The specification says 'as' is a keyword in this context + if self.literal != "as" { // FAIL - self.error(as.Idx, "Expected 'as' keyword, found '%s' instead", as.Name) + self.error(self.idx, "Expected 'as' keyword, found '%s' instead", self.literal) } + self.next() alias := self.parseIdentifier() @@ -1064,6 +1080,9 @@ func (self *_parser) parseImportClause() *ast.ImportClause { // FIXME: return ty if self.token == token.MULTIPLY { importClause.NameSpaceImport = self.parseNameSpaceImport() + } else if self.token == token.RIGHT_BRACE { + self.next() + return &importClause } else { importClause.NamedImports = self.parseNamedImports() } @@ -1093,11 +1112,11 @@ func (self *_parser) parseImportedDefaultBinding() *ast.Identifier { // FIXME: return type? func (self *_parser) parseNameSpaceImport() *ast.NameSpaceImport { self.expect(token.MULTIPLY) // * - identifier := self.parseIdentifier() - if identifier.Name != "as" { - self.error(self.idx, "expected 'as' identifier, found '%s' instead", identifier.Name) - return nil + if self.literal != "as" { + // FAIL + self.error(self.idx, "Expected 'as' keyword, found '%s' instead", self.literal) } + self.next() return &ast.NameSpaceImport{ImportedBinding: self.parseImportedBinding().Name} } diff --git a/tc39_test.go b/tc39_test.go index 2bde1104..9e5e59ba 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -316,6 +316,8 @@ func init() { // BigInt "test/built-ins/TypedArrayConstructors/BigUint64Array/", "test/built-ins/TypedArrayConstructors/BigInt64Array/", + // module namespace TODO remove + "test/language/module-code/namespace/", ) } @@ -720,7 +722,6 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R cache[fname] = cacheElement{err: err} return nil, err } - p.rt = vm p.compiler = newCompiler() p.compiler.hostResolveImportedModule = hostResolveImportedModule cache[fname] = cacheElement{m: p} @@ -734,12 +735,12 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } p := m.(*SourceTextModuleRecord) - early = false err = p.Link() if err != nil { return } + early = false err = p.Evaluate() return } @@ -822,6 +823,7 @@ func TestTC39(t *testing.T) { ctx.runTC39Tests("test/language/expressions") ctx.runTC39Tests("test/language/arguments-object") ctx.runTC39Tests("test/language/asi") + ctx.runTC39Tests("test/language/block-scope") ctx.runTC39Tests("test/language/directive-prologue") ctx.runTC39Tests("test/language/function-code") ctx.runTC39Tests("test/language/eval-code") From 85f55770e40bef27c5bb09a52ce4235eeeb66b75 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 16 May 2022 12:59:00 +0300 Subject: [PATCH 018/124] Add simple failing test --- modules_test.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 modules_test.go diff --git a/modules_test.go b/modules_test.go new file mode 100644 index 00000000..3a452c5f --- /dev/null +++ b/modules_test.go @@ -0,0 +1,73 @@ +package goja + +import ( + "fmt" + "testing" +) + +func TestSimpleModule(t *testing.T) { + vm := New() + type cacheElement struct { + m ModuleRecord + err error + } + a := ` + + import { b } from "dep.js"; + + globalThis.s = b() + ` + b := `export let b= function() { return 5 }; +` + cache := make(map[string]cacheElement) + var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { + k, ok := cache[specifier] + if ok { + return k.m, k.err + } + var src string + switch specifier { + case "a.js": + src = a + case "dep.js": + src = b + default: + panic(specifier) + } + p, err := vm.ParseModule(src) + if err != nil { + cache[specifier] = cacheElement{err: err} + return nil, err + } + p.compiler = newCompiler() + p.compiler.hostResolveImportedModule = hostResolveImportedModule + cache[specifier] = cacheElement{m: p} + return p, nil + } + + vm.hostResolveImportedModule = hostResolveImportedModule + vm.Set("l", func() { + fmt.Println("l called") + fmt.Printf("iter stack ; %+v", vm.vm.iterStack) + }) + m, err := vm.hostResolveImportedModule(nil, "a.js") + if err != nil { + t.Fatalf("got error %s", err) + } + p := m.(*SourceTextModuleRecord) + + err = p.Link() + if err != nil { + t.Fatalf("got error %s", err) + } + + err = p.Evaluate() + if err != nil { + t.Fatalf("got error %s", err) + } + v := vm.Get("s") + if v == nil || v.ToNumber().ToInteger() != 5 { + t.Fatalf("expected 5 got %s", v) + } +} From c1786b3c6f99d75bd568049a9d4248076ab951ad Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 2 Jun 2022 17:35:04 +0300 Subject: [PATCH 019/124] Basic import/export for let/const/functions works + cleanup Also includes tons of parsing and other small fixes --- ast/node.go | 3 +- compiler.go | 108 +++++++++++++++---------------- compiler_expr.go | 8 +-- compiler_stmt.go | 59 +++++++---------- module.go | 42 +++++++++---- modules_test.go | 150 ++++++++++++++++++++++++++++---------------- parser/statement.go | 25 ++++---- tc39_test.go | 8 ++- vm.go | 34 ++++++++-- 9 files changed, 258 insertions(+), 179 deletions(-) diff --git a/ast/node.go b/ast/node.go index cdb307fd..ae10bcce 100644 --- a/ast/node.go +++ b/ast/node.go @@ -480,6 +480,7 @@ type ( ExportFromClause *ExportFromClause FromClause *FromClause HoistableDeclaration *HoistableDeclaration + IsDefault bool } FromClause struct { @@ -501,7 +502,7 @@ type ( } HoistableDeclaration struct { - FunctionDeclaration *FunctionLiteral + FunctionDeclaration *FunctionDeclaration // GeneratorDeclaration // AsyncFunc and AsyncGenerator } diff --git a/compiler.go b/compiler.go index 18c1d9c9..2dba2572 100644 --- a/compiler.go +++ b/compiler.go @@ -79,6 +79,7 @@ type compiler struct { enumGetExpr compiledEnumGetExpr // TODO add a type and a set method hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + module *SourceTextModuleRecord evalVM *vm } @@ -87,6 +88,7 @@ type binding struct { scope *scope name unistring.String accessPoints map[*scope]*[]int + getIndirect func() Value isConst bool isStrict bool isArg bool @@ -126,7 +128,9 @@ func (b *binding) markAccessPoint() { func (b *binding) emitGet() { b.markAccessPoint() - if b.isVar && !b.isArg { + if b.getIndirect != nil { + b.scope.c.emit(loadIndirect(b.getIndirect)) + } else if b.isVar && !b.isArg { b.scope.c.emit(loadStash(0)) } else { b.scope.c.emit(loadStashLex(0)) @@ -265,7 +269,7 @@ type scope struct { // 'this' is used and non-strict, so need to box it (functions only) thisNeeded bool // is module - module bool + // module *SourceTextModuleRecord } type block struct { @@ -357,7 +361,6 @@ func (p *Program) defineLiteralValue(val Value) uint32 { panic("wat") } for idx, v := range p.values { - fmt.Println(v) if v.SameAs(val) { return uint32(idx) } @@ -503,6 +506,7 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { stackIdx, stashIdx := 0, 0 allInStash := s.isDynamic() for i, b := range s.bindings { + // fmt.Printf("Binding %+v\n", b) if allInStash || b.inStash { for scope, aps := range b.accessPoints { var level uint32 @@ -522,6 +526,11 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { switch i := (*ap).(type) { case loadStash: *ap = loadStash(idx) + case export: + *ap = export{ + idx: idx, + callback: i.callback, + } case storeStash: *ap = storeStash(idx) case storeStashP: @@ -688,7 +697,11 @@ found: } func (c *compiler) compileModule(module *SourceTextModuleRecord) { - fmt.Println("start compileModule") + oldModule := c.module + c.module = module + defer func() { + c.module = oldModule + }() in := module.body c.p.src = in.File strict := true @@ -697,8 +710,6 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.newScope() scope := c.scope - scope.module = true - module.scope = scope scope.dynamic = true scope.eval = eval if !strict && len(in.Body) > 0 { @@ -712,7 +723,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { scope = c.scope scope.function = true } - // fmt.Println(scope) + // scope.module = module + module.scope = scope // 15.2.1.17.4 step 9 start for _, in := range module.importEntries { importedModule, err := module.rt.hostResolveImportedModule(module, in.moduleRequest) @@ -720,49 +732,19 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { panic(fmt.Errorf("previously resolved module returned error %w", err)) } if in.importName == "*" { - namespace := module.rt.getModuleNamespace(importedModule) - fmt.Println(in) - b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) - _ = namespace - _ = b - // TODO fix - r := &compiledLiteral{ - val: stringValueFromRaw("pe"), // TODO FIX - // val: value, - } - r.init(c, 0) } else { - fmt.Println("name", in.importName) resolution, ambiguous := importedModule.ResolveExport(in.importName) if resolution == nil || ambiguous { c.throwSyntaxError(in.offset, "ambiguous import of %s", in.importName) } if resolution.BindingName == "*namespace*" { - namespace := module.rt.getModuleNamespace(resolution.Module) - fmt.Println(in) - b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) - _ = namespace - _ = b - r := &compiledIdentifierExpr{ - name: unistring.String(in.localName), - // val: value, - } - r.init(c, 0) - // TODO fix } else { - c.createImportBinding(in.localName, resolution.Module, resolution.BindingName) - - r := &compiledIdentifierExpr{ - name: unistring.String(in.localName), - // val: value, - } - fmt.Println("r:", r) - r.init(c, 0) + // c.createImportBinding(in.localName, resolution.Module, resolution.BindingName) + c.createImmutableBinding(unistring.String(in.localName), true) } } } // 15.2.1.17.4 step 9 end - // fmt.Println("end") funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) numFuncs := len(scope.bindings) @@ -802,8 +784,19 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.emit(enter) } } - // fmt.Println("somepoint", enter) + for _, entry := range module.localExportEntries { + name := unistring.String(entry.localName) + b, ok := scope.boundNames[name] + if !ok { + c.throwSyntaxError(0, "this very bad - can't find exported binding for "+entry.localName) + } + b.inStash = true + b.markAccessPoint() + c.emit(export{callback: func(getter func() Value) { + module.exportGetters[name] = getter + }}) + } if len(scope.bindings) > 0 && !ownLexScope { var lets, consts []unistring.String for _, b := range c.scope.bindings[numFuncs+numVars:] { @@ -820,23 +813,18 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { consts: consts, }) } - // fmt.Println("somepoint 2") if !inGlobal || ownVarScope { c.compileFunctions(funcs) } - // fmt.Println("somepoint 3") c.compileStatements(in.Body, true) - // fmt.Println("somepoint 4") if enter != nil { c.leaveScopeBlock(enter) c.popScope() } - // fmt.Println("somepoint 5") c.p.code = append(c.p.code, halt) scope.finaliseVarAlloc(0) - // fmt.Println("end 2") } func (c *compiler) compile(in *ast.Program, strict, eval, inGlobal bool) { @@ -950,6 +938,11 @@ func (c *compiler) extractFunctions(list []ast.Statement) (funcs []*ast.Function } else { continue } + case *ast.ExportDeclaration: + if st.HoistableDeclaration == nil || st.HoistableDeclaration.FunctionDeclaration == nil { + continue + } + decl = st.HoistableDeclaration.FunctionDeclaration default: continue } @@ -1064,10 +1057,9 @@ func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { } func (c *compiler) createImmutableBinding(name unistring.String, isStrict bool) *binding { - fmt.Println("immutable binding for ", name) b, _ := c.scope.bindName(name) - fmt.Println(b) b.isConst = true + b.isVar = true b.isStrict = isStrict return b } @@ -1076,14 +1068,20 @@ func (c *compiler) createImportBinding(n string, m ModuleRecord, n2 string) *bin // TODO Do something :shrug: // TODO fix this for not source text module records - /* - binding, ok := s.scope.bindName(unistring.NewFromString(n2)) - fmt.Println(n, n2, ok) - */ name := unistring.NewFromString(n) - localBinding, _ := c.scope.bindName(name) - _ = localBinding - return nil + + s := m.(*SourceTextModuleRecord) + exportedBinding, _ := s.scope.lookupName(unistring.NewFromString(n2)) + _ = exportedBinding + // TODO use exportedBinding + // localBinding.emitInit() + // TODO this is likely wrong + c.scope.bindings = append(c.scope.bindings, exportedBinding) + if c.scope.boundNames == nil { + c.scope.boundNames = make(map[unistring.String]*binding) + } + c.scope.boundNames[name] = exportedBinding + return exportedBinding } func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding { @@ -1193,6 +1191,8 @@ func (c *compiler) compileStandaloneFunctionDecl(v *ast.FunctionDeclaration) { } func (c *compiler) emit(instructions ...instruction) { + // fmt.Printf("emit instructions %s\n", spew.Sdump(instructions)) + // fmt.Printf("on instructions %s\n", spew.Sdump(c.p.code)) c.p.code = append(c.p.code, instructions...) } diff --git a/compiler_expr.go b/compiler_expr.go index 17f5115f..6076983a 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -361,15 +361,12 @@ func (e *compiledIdentifierExpr) emitGetter(putOnStack bool) { func (e *compiledIdentifierExpr) emitGetterOrRef() { e.addSrcMap() if b, noDynamics := e.c.scope.lookupName(e.name); noDynamics { - fmt.Printf("dynamics %#v\n", b) if b != nil { b.emitGet() } else { panic("No dynamics and not found") } } else { - fmt.Println("no dynamics ", b) - fmt.Println(e.c.scope) if b != nil { b.emitGetVar(false) } else { @@ -1274,7 +1271,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { strict := s.strict p := e.c.p - // e.c.p.dumpCode() + // e.c.p.dumpCode(log.Default().Printf) if enterFunc2Mark != -1 { e.c.popScope() } @@ -1540,7 +1537,6 @@ func (e *compiledUnaryExpr) emitGetter(putOnStack bool) { goto end case token.TYPEOF: if o, ok := e.operand.(compiledExprOrRef); ok { - fmt.Println("o", o) o.emitGetterOrRef() } else { e.operand.emitGetter(true) @@ -2108,7 +2104,7 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { case float64: val = floatToValue(num) default: - panic(fmt.Errorf("Unsupported number literal type: %T", v.Value)) + panic(fmt.Errorf("unsupported number literal type: %T", v.Value)) } r := &compiledLiteral{ val: val, diff --git a/compiler_stmt.go b/compiler_stmt.go index 4b12794c..cb3b47d5 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -10,8 +10,6 @@ import ( ) func (c *compiler) compileStatement(v ast.Statement, needResult bool) { - // log.Printf("compileStatement(): %T", v) - switch v := v.(type) { case *ast.BlockStatement: c.compileBlockStatement(v, needResult) @@ -767,13 +765,22 @@ func (c *compiler) emitVarAssign(name unistring.String, offset int, init compile } func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { + // module := c.module // the compiler.module might be different at execution of this if expr.Variable != nil { c.compileVariableStatement(expr.Variable) } else if expr.LexicalDeclaration != nil { c.compileLexicalDeclaration(expr.LexicalDeclaration) } else if expr.HoistableDeclaration != nil { - // TODO this is not enough I think - c.compileFunctionLiteral(expr.HoistableDeclaration.FunctionDeclaration, false) + h := expr.HoistableDeclaration + if h.FunctionDeclaration != nil { + /* + b, _ := c.scope.lookupName(h.FunctionDeclaration.Function.Name.Name) + b.markAccessPoint() + c.emit(exportPrevious{callback: func(getter func() Value) { + module.exportGetters[h.FunctionDeclaration.Function.Name.Name] = getter + }}) + */ + } } // TODO } @@ -791,13 +798,6 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { } if expr.ImportClause != nil { if namespace := expr.ImportClause.NameSpaceImport; namespace != nil { - r := &compiledLiteral{ - // TODO export namespace - val: stringValueFromRaw("namespace thing"), - // val: module.Namespace()), - } - r.init(c, expr.Idx0()) - c.emitVarAssign(namespace.ImportedBinding, int(expr.Idx)-1, r) } if named := expr.ImportClause.NamedImports; named != nil { for _, name := range named.ImportsList { @@ -809,35 +809,24 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if value == nil { c.throwSyntaxError(int(expr.Idx0()), "import of %s was not expoted from module %s", name.IdentifierName.String(), expr.FromClause.ModuleSpecifier.String()) } - r := &compiledLiteral{ - val: stringValueFromRaw("to do fix this"), // TODO FIX + + n := name.Alias + if n.String() == "" { + n = name.IdentifierName + } + localB, _ := c.scope.lookupName(n) + if localB == nil { + c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } - r.init(c, expr.Idx0()) - // This should probably be the idx of the name? - c.emitVarAssign(name.Alias, int(expr.Idx1())-1, r) + identifier := name.IdentifierName // this + localB.getIndirect = func() Value { + return module.GetBindingValue(identifier, true) + } + } } if def := expr.ImportClause.ImportedDefaultBinding; def != nil { - fmt.Println("heres", def) - value, ambiguous := module.ResolveExport("default") - if ambiguous { // also ambiguous - c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of %s", "default") - } - if value == nil { - c.throwSyntaxError(int(expr.Idx0()), "import of \"default\" was not exported from module %s", expr.FromClause.ModuleSpecifier.String()) - } - name := unistring.String(def.Name.String()) - fmt.Println("value", value, name) - c.scope.bindName(name) - a, b := c.scope.lookupName(name) - fmt.Println("lookup", a, b) - r := &compiledLiteral{ - val: stringValueFromRaw("pe"), // TODO FIX - // val: value, - } - r.init(c, def.Idx0()) - r.emitGetter(true) } } } diff --git a/module.go b/module.go index c3c7b887..8db7c0ee 100644 --- a/module.go +++ b/module.go @@ -7,6 +7,7 @@ import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/parser" + "github.com/dop251/goja/unistring" ) // TODO most things here probably should be unexported and names should be revised before merged in master @@ -20,6 +21,7 @@ type ModuleRecord interface { Evaluate() error Namespace() *Namespace SetNamespace(*Namespace) + GetBindingValue(unistring.String, bool) Value } type CyclicModuleRecordStatus uint8 @@ -219,6 +221,9 @@ type SourceTextModuleRecord struct { localExportEntries []exportEntry indirectExportEntries []exportEntry starExportEntries []exportEntry + + // TODO figure out something less idiotic + exportGetters map[unistring.String]func() Value } type importEntry struct { @@ -245,10 +250,14 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { moduleRequest := importDeclarion.FromClause.ModuleSpecifier.String() if named := importClause.NamedImports; named != nil { for _, el := range named.ImportsList { + localName := el.Alias.String() + if localName == "" { + localName = el.IdentifierName.String() + } result = append(result, importEntry{ moduleRequest: moduleRequest, importName: el.IdentifierName.String(), - localName: el.Alias.String(), + localName: localName, offset: int(importDeclarion.Idx0()), }) } @@ -275,7 +284,6 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { var result []exportEntry - // spew.Dump(declarations) for _, exportDeclaration := range declarations { if exportDeclaration.ExportFromClause != nil { exportFromClause := exportDeclaration.ExportFromClause @@ -300,7 +308,6 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { }) } } else { - fmt.Printf("unimplemented %+v\n", exportDeclaration.ExportFromClause) panic("wat") } } else if variableDeclaration := exportDeclaration.Variable; variableDeclaration != nil { @@ -330,12 +337,16 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { } } else if hoistable := exportDeclaration.HoistableDeclaration; hoistable != nil { localName := "default" - if hoistable.FunctionDeclaration.Name != nil { - localName = string(hoistable.FunctionDeclaration.Name.Name.String()) + exportName := "default" + if hoistable.FunctionDeclaration.Function.Name != nil { + localName = string(hoistable.FunctionDeclaration.Function.Name.Name.String()) + } + if !exportDeclaration.IsDefault { + exportName = localName } result = append(result, exportEntry{ localName: localName, - exportName: "default", + exportName: exportName, }) } else if fromClause := exportDeclaration.FromClause; fromClause != nil { if namedExports := exportDeclaration.NamedExports; namedExports != nil { @@ -351,7 +362,6 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { }) } } else { - fmt.Printf("unimplemented %+v\n", exportDeclaration.ExportFromClause) panic("wat") } } else if namedExports := exportDeclaration.NamedExports; namedExports != nil { @@ -371,12 +381,9 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { localName: "default", }) } else { - fmt.Printf("unimplemented %+v\n", exportDeclaration) panic("wat") - } } - // spew.Dump(result) return result } @@ -409,10 +416,10 @@ func findImportByLocalName(importEntries []importEntry, name string) (importEntr // This should probably be part of Parse // TODO arguments to this need fixing -func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, error) { +func (rt *Runtime) ParseModule(name, sourceText string) (*SourceTextModuleRecord, error) { // TODO asserts opts := append(rt.parserOptions, parser.IsModule) - body, err := Parse("module", sourceText, opts...) + body, err := Parse(name, sourceText, opts...) _ = body if err != nil { return nil, err @@ -466,6 +473,8 @@ func (rt *Runtime) ParseModule(sourceText string) (*SourceTextModuleRecord, erro localExportEntries: localExportEntries, indirectExportEntries: indirectExportEntries, starExportEntries: starExportEntries, + + exportGetters: make(map[unistring.String]func() Value), } s.rt = rt @@ -536,6 +545,15 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT return exportedNames } +func (module *SourceTextModuleRecord) GetBindingValue(name unistring.String, _ bool) Value { + getter, ok := module.exportGetters[name] + if !ok { + panic(module.rt.newError(module.rt.global.ReferenceError, + "%s is not defined, this shoukldn't be possible due to how ESM works", name)) + } + return getter() +} + func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { defer func() { if x := recover(); x != nil { diff --git a/modules_test.go b/modules_test.go index 3a452c5f..83d95dd6 100644 --- a/modules_test.go +++ b/modules_test.go @@ -6,68 +6,110 @@ import ( ) func TestSimpleModule(t *testing.T) { - vm := New() type cacheElement struct { m ModuleRecord err error } - a := ` - - import { b } from "dep.js"; - - globalThis.s = b() - ` - b := `export let b= function() { return 5 }; -` - cache := make(map[string]cacheElement) - var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) - hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { - k, ok := cache[specifier] - if ok { - return k.m, k.err - } - var src string - switch specifier { - case "a.js": - src = a - case "dep.js": - src = b - default: - panic(specifier) - } - p, err := vm.ParseModule(src) - if err != nil { - cache[specifier] = cacheElement{err: err} - return nil, err - } - p.compiler = newCompiler() - p.compiler.hostResolveImportedModule = hostResolveImportedModule - cache[specifier] = cacheElement{m: p} - return p, nil + type testCase struct { + a string + b string } - vm.hostResolveImportedModule = hostResolveImportedModule - vm.Set("l", func() { - fmt.Println("l called") - fmt.Printf("iter stack ; %+v", vm.vm.iterStack) - }) - m, err := vm.hostResolveImportedModule(nil, "a.js") - if err != nil { - t.Fatalf("got error %s", err) + testCases := map[string]testCase{ + "function export": { + a: `import { b } from "dep.js"; +globalThis.s = b() +`, + b: `export function b() {globalThis.p(); return 5 };`, + }, + "let export": { + a: `import { b } from "dep.js"; +globalThis.s = b() +`, + b: `export let b = function() {globalThis.p(); return 5 };`, + }, + "const export": { + a: `import { b } from "dep.js"; +globalThis.s = b() +`, + b: `export const b = function() {globalThis.p(); return 5 };`, + }, + "let export with update": { + a: `import { s , b} from "dep.js"; + s() +globalThis.s = b() +`, + b: `export let b = "something"; + export function s (){ + globalThis.p() + b = function() {globalThis.p(); return 5 }; + }`, + }, + "default export": { + a: `import b from "dep.js"; +globalThis.s = b() +`, + b: `export default function() {globalThis.p(); return 5 };`, + }, } - p := m.(*SourceTextModuleRecord) + for name, cases := range testCases { + a, b := cases.a, cases.b + t.Run(name, func(t *testing.T) { + vm := New() + vm.Set("p", vm.ToValue(func() { + // fmt.Println("p called") + })) + cache := make(map[string]cacheElement) + var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { + k, ok := cache[specifier] + if ok { + return k.m, k.err + } + var src string + switch specifier { + case "a.js": + src = a + case "dep.js": + src = b + default: + panic(specifier) + } + p, err := vm.ParseModule(specifier, src) + if err != nil { + cache[specifier] = cacheElement{err: err} + return nil, err + } + p.compiler = newCompiler() + p.compiler.hostResolveImportedModule = hostResolveImportedModule + cache[specifier] = cacheElement{m: p} + return p, nil + } - err = p.Link() - if err != nil { - t.Fatalf("got error %s", err) - } + vm.hostResolveImportedModule = hostResolveImportedModule + vm.Set("l", func() { + fmt.Println("l called") + fmt.Printf("iter stack ; %+v", vm.vm.iterStack) + }) + m, err := vm.hostResolveImportedModule(nil, "a.js") + if err != nil { + t.Fatalf("got error %s", err) + } + p := m.(*SourceTextModuleRecord) - err = p.Evaluate() - if err != nil { - t.Fatalf("got error %s", err) - } - v := vm.Get("s") - if v == nil || v.ToNumber().ToInteger() != 5 { - t.Fatalf("expected 5 got %s", v) + err = p.Link() + if err != nil { + t.Fatalf("got error %s", err) + } + + err = p.Evaluate() + if err != nil { + t.Fatalf("got error %s", err) + } + v := vm.Get("s") + if v == nil || v.ToNumber().ToInteger() != 5 { + t.Fatalf("expected 5 got %s", v) + } + }) } } diff --git a/parser/statement.go b/parser/statement.go index 78f1c838..10023c2a 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -868,31 +868,34 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { return &ast.ExportDeclaration{Variable: self.parseVariableStatement()} case token.LET, token.CONST: return &ast.ExportDeclaration{LexicalDeclaration: self.parseLexicalDeclaration(self.token)} - case token.FUNCTION: // FIXME: What about function* and async? - // TODO implement - functionDeclaration := self.parseFunction(false) - self.semicolon() + case token.FUNCTION: return &ast.ExportDeclaration{ HoistableDeclaration: &ast.HoistableDeclaration{ - FunctionDeclaration: functionDeclaration, + FunctionDeclaration: &ast.FunctionDeclaration{ + Function: self.parseFunction(true), + }, }, } - case token.DEFAULT: // FIXME: current implementation of HoistableDeclaration only implements function? + case token.DEFAULT: self.next() + var exp *ast.ExportDeclaration switch self.token { case token.FUNCTION: - functionDeclaration := self.parseFunction(false) - return &ast.ExportDeclaration{ + exp = &ast.ExportDeclaration{ HoistableDeclaration: &ast.HoistableDeclaration{ - FunctionDeclaration: functionDeclaration, + FunctionDeclaration: &ast.FunctionDeclaration{ + Function: self.parseFunction(true), + }, }, } - // TODO classes default: - return &ast.ExportDeclaration{ + exp = &ast.ExportDeclaration{ AssignExpression: self.parseAssignmentExpression(), + IsDefault: true, } } + self.semicolon() + return exp default: namedExports := self.parseNamedExports() self.semicolon() diff --git a/tc39_test.go b/tc39_test.go index 9e5e59ba..96414a0a 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -29,7 +29,6 @@ var ( skipPrefixes prefixList skipList = map[string]bool{ - // timezone "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js": true, "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js": true, @@ -232,6 +231,11 @@ var ( // Left-hand side as a CoverParenthesizedExpression "test/language/expressions/assignment/fn-name-lhs-cover.js": true, + + // eval this panics in modules + "test/language/module-code/eval-this.js": true, + // star and default leads to panic + "test/language/module-code/export-star-as-dflt.js": true, } featuresBlackList = []string{ @@ -717,7 +721,7 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } str := string(b) - p, err := vm.ParseModule(str) + p, err := vm.ParseModule(fname, str) if err != nil { cache[fname] = cacheElement{err: err} return nil, err diff --git a/vm.go b/vm.go index dcb026ad..6d2ff7cb 100644 --- a/vm.go +++ b/vm.go @@ -407,6 +407,7 @@ func (vm *vm) run() { if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { break } + // fmt.Printf("Executing %#v\n", vm.prg.code[vm.pc]) vm.prg.code[vm.pc].exec(vm) ticks++ if ticks > 10000 { @@ -568,8 +569,7 @@ func (vm *vm) peek() Value { } func (vm *vm) saveCtx(ctx *context) { - ctx.prg, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args, ctx.funcName = - vm.prg, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args, vm.funcName + ctx.prg, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args, ctx.funcName = vm.prg, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args, vm.funcName } func (vm *vm) pushCtx() { @@ -586,8 +586,7 @@ func (vm *vm) pushCtx() { } func (vm *vm) restoreCtx(ctx *context) { - vm.prg, vm.funcName, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = - ctx.prg, ctx.funcName, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args + vm.prg, vm.funcName, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = ctx.prg, ctx.funcName, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args } func (vm *vm) popCtx() { @@ -855,6 +854,25 @@ func (s initStack1) exec(vm *vm) { vm.sp-- } +type export struct { + idx uint32 + callback func(func() Value) +} + +func (e export) exec(vm *vm) { + // from loadStash + level := int(e.idx >> 24) + idx := uint32(e.idx & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + e.callback(func() Value { + return stash.getByIdx(idx) + }) + vm.pc++ +} + type storeStackP int func (s storeStackP) exec(vm *vm) { @@ -2054,6 +2072,14 @@ func (s setGlobalStrict) exec(vm *vm) { vm.pc++ } +// Load a var indirectly from another module +type loadIndirect func() Value + +func (g loadIndirect) exec(vm *vm) { + vm.push(nilSafe(g())) + vm.pc++ +} + // Load a var from stash type loadStash uint32 From 9bafbfb3e8f67babd67bb320b674b28cf227e642 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 3 Jun 2022 10:36:11 +0300 Subject: [PATCH 020/124] Implement basic default export/import --- ast/node.go | 1 + compiler.go | 8 ++++++-- compiler_stmt.go | 41 +++++++++++++++++++++++++++++++++++++++-- module.go | 8 +++++--- parser/statement.go | 7 +++---- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/ast/node.go b/ast/node.go index ae10bcce..c5296ad2 100644 --- a/ast/node.go +++ b/ast/node.go @@ -503,6 +503,7 @@ type ( HoistableDeclaration struct { FunctionDeclaration *FunctionDeclaration + FunctionLiteral *FunctionLiteral // GeneratorDeclaration // AsyncFunc and AsyncGenerator } diff --git a/compiler.go b/compiler.go index ff503fba..cd36be61 100644 --- a/compiler.go +++ b/compiler.go @@ -792,12 +792,16 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { name := unistring.String(entry.localName) b, ok := scope.boundNames[name] if !ok { - c.throwSyntaxError(0, "this very bad - can't find exported binding for "+entry.localName) + // TODO fix this somehow - this is as *default* doesn't get bound before it's used + b, _ = scope.bindName(name) } + b.inStash = true b.markAccessPoint() + + exportName := unistring.String(entry.exportName) c.emit(export{callback: func(getter func() Value) { - module.exportGetters[name] = getter + module.exportGetters[exportName] = getter }}) } if len(scope.bindings) > 0 && !ownLexScope { diff --git a/compiler_stmt.go b/compiler_stmt.go index 60d31978..89f38d16 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -773,13 +773,34 @@ func (c *compiler) emitVarAssign(name unistring.String, offset int, init compile func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { // module := c.module // the compiler.module might be different at execution of this + // fmt.Printf("Export %#v\n", expr) if expr.Variable != nil { c.compileVariableStatement(expr.Variable) } else if expr.LexicalDeclaration != nil { c.compileLexicalDeclaration(expr.LexicalDeclaration) } else if expr.HoistableDeclaration != nil { h := expr.HoistableDeclaration - if h.FunctionDeclaration != nil { + if h.FunctionLiteral != nil { + if !expr.IsDefault { + panic("non default function literal export") + } + // TODO fix this - this was the easiest way to bound the default to something + c.compileLexicalDeclaration(&ast.LexicalDeclaration{ + Idx: h.FunctionLiteral.Idx0(), + Token: token.CONST, + List: []*ast.Binding{ + { + Target: &ast.Identifier{ + Name: unistring.String("*default*"), + Idx: h.FunctionLiteral.Idx0(), + }, + Initializer: h.FunctionLiteral, + }, + }, + }) + // r.emitGetter(true) + // r.markAccessPoint() + /* b, _ := c.scope.lookupName(h.FunctionDeclaration.Function.Name.Name) b.markAccessPoint() @@ -829,11 +850,27 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { localB.getIndirect = func() Value { return module.GetBindingValue(identifier, true) } - } } if def := expr.ImportClause.ImportedDefaultBinding; def != nil { + value, ambiguous := module.ResolveExport("default") + + if ambiguous { // also ambiguous + c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of default") + } + if value == nil { + c.throwSyntaxError(int(expr.Idx0()), "import of default was not exported from module %s", expr.FromClause.ModuleSpecifier.String()) + } + + localB, _ := c.scope.lookupName(def.Name) + if localB == nil { + c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) + } + localB.getIndirect = func() Value { + // TODO this should be just "default", this also likely doesn't work for export aliasing + return module.GetBindingValue(unistring.String("default"), true) + } } } } diff --git a/module.go b/module.go index 8db7c0ee..f86a2aa1 100644 --- a/module.go +++ b/module.go @@ -336,10 +336,12 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { } } else if hoistable := exportDeclaration.HoistableDeclaration; hoistable != nil { - localName := "default" + localName := "*default*" exportName := "default" - if hoistable.FunctionDeclaration.Function.Name != nil { - localName = string(hoistable.FunctionDeclaration.Function.Name.Name.String()) + if hoistable.FunctionDeclaration != nil { + if hoistable.FunctionDeclaration.Function.Name != nil { + localName = string(hoistable.FunctionDeclaration.Function.Name.Name.String()) + } } if !exportDeclaration.IsDefault { exportName = localName diff --git a/parser/statement.go b/parser/statement.go index 10023c2a..204c510f 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -879,21 +879,20 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.DEFAULT: self.next() var exp *ast.ExportDeclaration + switch self.token { case token.FUNCTION: exp = &ast.ExportDeclaration{ HoistableDeclaration: &ast.HoistableDeclaration{ - FunctionDeclaration: &ast.FunctionDeclaration{ - Function: self.parseFunction(true), - }, + FunctionLiteral: self.parseFunction(false), }, } default: exp = &ast.ExportDeclaration{ AssignExpression: self.parseAssignmentExpression(), - IsDefault: true, } } + exp.IsDefault = true self.semicolon() return exp default: From 28d747ccde7880daa6577e62a46013d797de0dca Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 3 Jun 2022 10:36:20 +0300 Subject: [PATCH 021/124] Disable a bunch of class tests --- tc39_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tc39_test.go b/tc39_test.go index 546e2293..55c6b096 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -127,6 +127,23 @@ var ( "test/language/expressions/optional-chaining/member-expression.js": true, "test/language/expressions/optional-chaining/call-expression.js": true, + "test/language/module-code/eval-export-dflt-cls-anon.js": true, + "test/language/module-code/eval-export-dflt-cls-anon-semi.js": true, + "test/language/module-code/eval-export-dflt-cls-named.js": true, + "test/language/module-code/eval-export-dflt-cls-named-semi.js": true, + "test/language/module-code/eval-export-dflt-cls-named-semi-meth.js": true, + "test/language/module-code/eval-export-dflt-expr-cls-named.js": true, + "test/language/module-code/eval-export-dflt-expr-cls-name-meth.js": true, + "test/language/module-code/instn-named-bndng-cls.js": true, + "test/language/module-code/instn-local-bndng-export-cls.js": true, + "test/language/module-code/instn-local-bndng-cls.js": true, + "test/language/module-code/eval-gtbndng-local-bndng-cls.js": true, + "test/language/module-code/instn-iee-bndng-cls.js": true, + "test/language/module-code/eval-export-dflt-expr-cls-anon.js": true, + "test/language/module-code/eval-export-dflt-cls-name-meth.js": true, + "test/language/module-code/eval-export-cls-semi.js": true, + "test/language/module-code/instn-named-bndng-dflt-cls.js": true, + // restricted unicode regexp syntax "test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true, "test/built-ins/RegExp/unicode_restricted_octal_escape.js": true, From 27ae1e1ceaf8e856702ccd010b2bca1655041ad3 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 3 Jun 2022 13:21:53 +0300 Subject: [PATCH 022/124] Default loop test --- modules_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules_test.go b/modules_test.go index 83d95dd6..7fb495bd 100644 --- a/modules_test.go +++ b/modules_test.go @@ -51,6 +51,13 @@ globalThis.s = b() `, b: `export default function() {globalThis.p(); return 5 };`, }, + "default loop": { + a: `import b from "a.js"; +export default function() {return 5;}; +globalThis.s = b() +`, + b: ``, + }, } for name, cases := range testCases { a, b := cases.a, cases.b From c9d37136eac30cb83f08b5b06ca5900818bf3e6d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 3 Jun 2022 15:08:03 +0300 Subject: [PATCH 023/124] WIP refactor --- compiler.go | 2 +- module.go | 40 +++++++++++++++++++++------------------- modules_test.go | 4 ++-- tc39_test.go | 4 ++-- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/compiler.go b/compiler.go index cd36be61..23048b20 100644 --- a/compiler.go +++ b/compiler.go @@ -730,7 +730,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { module.scope = scope // 15.2.1.17.4 step 9 start for _, in := range module.importEntries { - importedModule, err := module.rt.hostResolveImportedModule(module, in.moduleRequest) + importedModule, err := module.hostResolveImportedModule(module, in.moduleRequest) if err != nil { panic(fmt.Errorf("previously resolved module returned error %w", err)) } diff --git a/module.go b/module.go index f86a2aa1..d6931043 100644 --- a/module.go +++ b/module.go @@ -10,6 +10,8 @@ import ( "github.com/dop251/goja/unistring" ) +type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + // TODO most things here probably should be unexported and names should be revised before merged in master // Record should probably be dropped from everywhere @@ -18,7 +20,7 @@ type ModuleRecord interface { GetExportedNames(resolveset ...*SourceTextModuleRecord) []string // TODO maybe this parameter is wrong ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error - Evaluate() error + Evaluate(rt *Runtime) error Namespace() *Namespace SetNamespace(*Namespace) GetBindingValue(unistring.String, bool) Value @@ -47,7 +49,7 @@ type CyclicModuleRecord interface { SetDFSAncestorIndex(uint) RequestedModules() []string InitializeEnvorinment() error - ExecuteModule() error + ExecuteModule(*Runtime) error } func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) error { @@ -152,7 +154,7 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe var c CyclicModuleRecord var ok bool if c, ok = m.(CyclicModuleRecord); !ok { - return index, m.Evaluate() + return index, m.Evaluate(rt) } if status := c.Status(); status == Evaluated { // TODO switch return index, c.EvaluationError() @@ -187,7 +189,7 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe } } } - err = c.ExecuteModule() + err = c.ExecuteModule(rt) if err != nil { return 0, err } @@ -212,7 +214,6 @@ var _ CyclicModuleRecord = &SourceTextModuleRecord{} type SourceTextModuleRecord struct { cyclicModuleStub scope *scope - rt *Runtime // TODO this is not great as it means the whole thing needs to be reparsed for each runtime compiler *compiler // TODO remove this body *ast.Program // context @@ -223,7 +224,8 @@ type SourceTextModuleRecord struct { starExportEntries []exportEntry // TODO figure out something less idiotic - exportGetters map[unistring.String]func() Value + exportGetters map[unistring.String]func() Value + hostResolveImportedModule HostResolveImportedModuleFunc } type importEntry struct { @@ -418,9 +420,9 @@ func findImportByLocalName(importEntries []importEntry, name string) (importEntr // This should probably be part of Parse // TODO arguments to this need fixing -func (rt *Runtime) ParseModule(name, sourceText string) (*SourceTextModuleRecord, error) { +func ParseModule(name, sourceText string, resolveModule HostResolveImportedModuleFunc, opts ...parser.Option) (*SourceTextModuleRecord, error) { // TODO asserts - opts := append(rt.parserOptions, parser.IsModule) + opts = append(opts, parser.IsModule) body, err := Parse(name, sourceText, opts...) _ = body if err != nil { @@ -476,10 +478,10 @@ func (rt *Runtime) ParseModule(name, sourceText string) (*SourceTextModuleRecord indirectExportEntries: indirectExportEntries, starExportEntries: starExportEntries, - exportGetters: make(map[unistring.String]func() Value), + exportGetters: make(map[unistring.String]func() Value), + hostResolveImportedModule: resolveModule, } - s.rt = rt names := s.getExportedNamesWithotStars() // we use this as the other one loops but wee need to early errors here sort.Strings(names) for i := 1; i < len(names); i++ { @@ -496,10 +498,10 @@ func (rt *Runtime) ParseModule(name, sourceText string) (*SourceTextModuleRecord return s, nil } -func (module *SourceTextModuleRecord) ExecuteModule() error { +func (module *SourceTextModuleRecord) ExecuteModule(rt *Runtime) error { // TODO copy runtime.RunProgram here with some changes so that it doesn't touch the global ? - _, err := module.rt.RunProgram(module.compiler.p) + _, err := rt.RunProgram(module.compiler.p) return err } @@ -530,7 +532,7 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT exportedNames = append(exportedNames, e.exportName) } for _, e := range module.starExportEntries { - requestedModule, err := module.rt.hostResolveImportedModule(module, e.moduleRequest) + requestedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) if err != nil { panic(err) } @@ -550,8 +552,8 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT func (module *SourceTextModuleRecord) GetBindingValue(name unistring.String, _ bool) Value { getter, ok := module.exportGetters[name] if !ok { - panic(module.rt.newError(module.rt.global.ReferenceError, - "%s is not defined, this shoukldn't be possible due to how ESM works", name)) + return nil + // panic(name + " is not defined, this shoukldn't be possible due to how ESM works") } return getter() } @@ -662,7 +664,7 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese for _, e := range module.indirectExportEntries { if exportName == e.exportName { - importedModule, err := module.rt.hostResolveImportedModule(module, e.moduleRequest) + importedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) if err != nil { panic(err) // TODO return err } @@ -685,7 +687,7 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese var starResolution *ResolvedBinding for _, e := range module.starExportEntries { - importedModule, err := module.rt.hostResolveImportedModule(module, e.moduleRequest) + importedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) if err != nil { panic(err) // TODO return err } @@ -704,8 +706,8 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese return starResolution, false } -func (module *SourceTextModuleRecord) Evaluate() error { - return module.rt.CyclicModuleRecordEvaluate(module) +func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) error { + return rt.CyclicModuleRecordEvaluate(module) } func (module *SourceTextModuleRecord) Link() error { diff --git a/modules_test.go b/modules_test.go index 7fb495bd..2e4027a5 100644 --- a/modules_test.go +++ b/modules_test.go @@ -82,7 +82,7 @@ globalThis.s = b() default: panic(specifier) } - p, err := vm.ParseModule(specifier, src) + p, err := ParseModule(specifier, src, hostResolveImportedModule) if err != nil { cache[specifier] = cacheElement{err: err} return nil, err @@ -109,7 +109,7 @@ globalThis.s = b() t.Fatalf("got error %s", err) } - err = p.Evaluate() + err = p.Evaluate(vm) if err != nil { t.Fatalf("got error %s", err) } diff --git a/tc39_test.go b/tc39_test.go index 55c6b096..22233c72 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -706,7 +706,7 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } str := string(b) - p, err := vm.ParseModule(fname, str) + p, err := ParseModule(fname, str, hostResolveImportedModule) if err != nil { cache[fname] = cacheElement{err: err} return nil, err @@ -730,7 +730,7 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } early = false - err = p.Evaluate() + err = p.Evaluate(vm) return } From 1e617f0848cb7eb236e4eb0962bfcbf049f8dfd1 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 6 Jun 2022 12:41:29 +0300 Subject: [PATCH 024/124] Make possible to Run the same compiled modules in parallel --- compiler.go | 14 ++-- compiler_stmt.go | 12 ++- module.go | 194 ++++++++++++++++++++++++++++------------------- modules_test.go | 53 ++++++++----- runtime.go | 2 + tc39_test.go | 3 +- vm.go | 8 +- 7 files changed, 174 insertions(+), 112 deletions(-) diff --git a/compiler.go b/compiler.go index 23048b20..d0fc0ff9 100644 --- a/compiler.go +++ b/compiler.go @@ -88,7 +88,7 @@ type binding struct { scope *scope name unistring.String accessPoints map[*scope]*[]int - getIndirect func() Value + getIndirect func(vm *vm) Value isConst bool isStrict bool isArg bool @@ -702,8 +702,11 @@ found: func (c *compiler) compileModule(module *SourceTextModuleRecord) { oldModule := c.module c.module = module + oldResolve := c.hostResolveImportedModule + c.hostResolveImportedModule = module.hostResolveImportedModule defer func() { c.module = oldModule + c.hostResolveImportedModule = oldResolve }() in := module.body c.p.src = in.File @@ -727,10 +730,10 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { scope.function = true } // scope.module = module - module.scope = scope + // module.scope = scope // 15.2.1.17.4 step 9 start for _, in := range module.importEntries { - importedModule, err := module.hostResolveImportedModule(module, in.moduleRequest) + importedModule, err := c.hostResolveImportedModule(module, in.moduleRequest) if err != nil { panic(fmt.Errorf("previously resolved module returned error %w", err)) } @@ -800,8 +803,9 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { b.markAccessPoint() exportName := unistring.String(entry.exportName) - c.emit(export{callback: func(getter func() Value) { - module.exportGetters[exportName] = getter + c.emit(export{callback: func(vm *vm, getter func() Value) { + m := vm.r.modules[module.name].(*SourceTextModuleInstance) + m.exportGetters[exportName] = getter }}) } if len(scope.bindings) > 0 && !ownLexScope { diff --git a/compiler_stmt.go b/compiler_stmt.go index 89f38d16..38d2488d 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -847,8 +847,10 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } identifier := name.IdentifierName // this - localB.getIndirect = func() Value { - return module.GetBindingValue(identifier, true) + moduleName := expr.FromClause.ModuleSpecifier.String() + localB.getIndirect = func(vm *vm) Value { + m := vm.r.modules[moduleName] + return m.GetBindingValue(identifier, true) } } } @@ -867,9 +869,11 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) } - localB.getIndirect = func() Value { + moduleName := expr.FromClause.ModuleSpecifier.String() + localB.getIndirect = func(vm *vm) Value { // TODO this should be just "default", this also likely doesn't work for export aliasing - return module.GetBindingValue(unistring.String("default"), true) + m := vm.r.modules[moduleName] + return m.GetBindingValue(unistring.String("default"), true) } } } diff --git a/module.go b/module.go index d6931043..3ffb49a6 100644 --- a/module.go +++ b/module.go @@ -20,10 +20,9 @@ type ModuleRecord interface { GetExportedNames(resolveset ...*SourceTextModuleRecord) []string // TODO maybe this parameter is wrong ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error - Evaluate(rt *Runtime) error + Evaluate(rt *Runtime) (ModuleInstance, error) Namespace() *Namespace SetNamespace(*Namespace) - GetBindingValue(unistring.String, bool) Value } type CyclicModuleRecordStatus uint8 @@ -49,9 +48,12 @@ type CyclicModuleRecord interface { SetDFSAncestorIndex(uint) RequestedModules() []string InitializeEnvorinment() error - ExecuteModule(*Runtime) error + ExecuteModule(*Runtime) (Value, error) + Instanciate() CyclicModuleInstance } +type LinkedSourceModuleRecord struct{} + func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) error { if module.Status() == Linking || module.Status() == Evaluating { return fmt.Errorf("bad status %+v on link", module.Status()) @@ -76,11 +78,12 @@ func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) err return nil } -func (co *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { +func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { var module CyclicModuleRecord var ok bool if module, ok = m.(CyclicModuleRecord); !ok { - return index, m.Link() + err := m.Link() // TODO fix + return index, err } if status := module.Status(); status == Linking || status == Linked || status == Evaluated { return index, nil @@ -95,11 +98,11 @@ func (co *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleReco var err error var requiredModule ModuleRecord for _, required := range module.RequestedModules() { - requiredModule, err = co.hostResolveImportedModule(module, required) + requiredModule, err = c.hostResolveImportedModule(module, required) if err != nil { return 0, err } - index, err = co.innerModuleLinking(requiredModule, stack, index) + index, err = c.innerModuleLinking(requiredModule, stack, index) if err != nil { return 0, err } @@ -132,36 +135,53 @@ func (co *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleReco return index, nil } -func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord) error { +func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) (mi ModuleInstance, err error) { // TODO asserts - stack := []CyclicModuleRecord{} - if _, err := rt.innerModuleEvaluation(c, &stack, 0); err != nil { - - for _, m := range stack { - // TODO asserts - m.SetStatus(Evaluated) - m.SetEvaluationError(err) - } + if rt.modules == nil { + rt.modules = make(map[string]ModuleInstance) + } + stackInstance := []CyclicModuleInstance{} + if mi, _, err = rt.innerModuleEvaluation(c, &stackInstance, 0, name, make(map[ModuleRecord]CyclicModuleInstance)); err != nil { + /* + for _, m := range stack { + // TODO asserts + m.SetStatus(Evaluated) + m.SetEvaluationError(err) + } + */ // TODO asserts - return err + return nil, err } // TODO asserts - return nil + return mi, nil } -func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { - var c CyclicModuleRecord +func (rt *Runtime) innerModuleEvaluation( + m ModuleRecord, stack *[]CyclicModuleInstance, index uint, + name string, instances map[ModuleRecord]CyclicModuleInstance, // TODO remove thsi?!? +) (mi ModuleInstance, idx uint, err error) { + if len(*stack) > 100000 { + panic("too deep dependancy stack of 100000") + } + var cr CyclicModuleRecord var ok bool - if c, ok = m.(CyclicModuleRecord); !ok { - return index, m.Evaluate(rt) + if cr, ok = m.(CyclicModuleRecord); !ok { + mi, err = m.Evaluate(rt) + return mi, index, err } + c, ok := instances[cr] + if !ok { + c = cr.Instanciate() + instances[cr] = c + } + rt.modules[name] = c if status := c.Status(); status == Evaluated { // TODO switch - return index, c.EvaluationError() + return nil, index, c.EvaluationError() } else if status == Evaluating { - return index, nil + return nil, index, nil } else if status != Linked { - return 0, errors.New("module isn't linked when it's being evaluated") + return nil, 0, errors.New("module isn't linked when it's being evaluated") } c.SetStatus(Evaluating) c.SetDFSIndex(index) @@ -169,18 +189,17 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe index++ *stack = append(*stack, c) - var err error var requiredModule ModuleRecord for _, required := range c.RequestedModules() { requiredModule, err = rt.hostResolveImportedModule(c, required) if err != nil { - return 0, err + return nil, 0, err } - index, err = rt.innerModuleEvaluation(requiredModule, stack, index) + mi, index, err = rt.innerModuleEvaluation(requiredModule, stack, index, required, instances) if err != nil { - return 0, err + return nil, 0, err } - if requiredC, ok := requiredModule.(CyclicModuleRecord); ok { + if requiredC, ok := mi.(CyclicModuleInstance); ok { // TODO some asserts if requiredC.Status() == Evaluating { if ancestorIndex := c.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { @@ -189,9 +208,9 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe } } } - err = c.ExecuteModule(rt) + _, err = c.ExecuteModule(rt) if err != nil { - return 0, err + return nil, 0, err } // TODO asserts @@ -199,20 +218,64 @@ func (rt *Runtime) innerModuleEvaluation(m ModuleRecord, stack *[]CyclicModuleRe for i := len(*stack) - 1; i >= 0; i-- { requiredModule := (*stack)[i] // TODO assert - requiredC := requiredModule.(CyclicModuleRecord) + requiredC := requiredModule.(CyclicModuleInstance) requiredC.SetStatus(Evaluated) if requiredC == c { break } } } - return index, nil + return mi, index, nil } +type ( + ModuleInstance interface { + // Evaluate(rt *Runtime) (ModuleInstance, error) + GetBindingValue(unistring.String, bool) Value + } + CyclicModuleInstance interface { + ModuleInstance + Status() CyclicModuleRecordStatus + SetStatus(CyclicModuleRecordStatus) + EvaluationError() error + SetEvaluationError(error) + DFSIndex() uint + SetDFSIndex(uint) + DFSAncestorIndex() uint + SetDFSAncestorIndex(uint) + RequestedModules() []string + ExecuteModule(*Runtime) (ModuleInstance, error) + } +) + var _ CyclicModuleRecord = &SourceTextModuleRecord{} +var _ CyclicModuleInstance = &SourceTextModuleInstance{} + +type SourceTextModuleInstance struct { + cyclicModuleStub + moduleRecord *SourceTextModuleRecord + // TODO figure out omething less idiotic + exportGetters map[unistring.String]func() Value +} + +func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, error) { + _, err := rt.RunProgram(s.moduleRecord.compiler.p) + return s, err +} + +func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool) Value { + getter, ok := s.exportGetters[name] + if !ok { + // return nil + // panic(name + " is not defined, this shoukldn't be possible due to how ESM works") + } + return getter() +} + type SourceTextModuleRecord struct { cyclicModuleStub + name string // TODO remove this :crossed_fingers: scope *scope compiler *compiler // TODO remove this body *ast.Program @@ -223,8 +286,6 @@ type SourceTextModuleRecord struct { indirectExportEntries []exportEntry starExportEntries []exportEntry - // TODO figure out something less idiotic - exportGetters map[unistring.String]func() Value hostResolveImportedModule HostResolveImportedModuleFunc } @@ -462,6 +523,7 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul } s := &SourceTextModuleRecord{ + name: name, // realm isn't implement // environment is undefined // namespace is undefined @@ -478,7 +540,6 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul indirectExportEntries: indirectExportEntries, starExportEntries: starExportEntries, - exportGetters: make(map[unistring.String]func() Value), hostResolveImportedModule: resolveModule, } @@ -498,11 +559,10 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul return s, nil } -func (module *SourceTextModuleRecord) ExecuteModule(rt *Runtime) error { +func (module *SourceTextModuleRecord) ExecuteModule(rt *Runtime) (Value, error) { // TODO copy runtime.RunProgram here with some changes so that it doesn't touch the global ? - _, err := rt.RunProgram(module.compiler.p) - return err + return rt.RunProgram(module.compiler.p) } func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { @@ -549,16 +609,8 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT return exportedNames } -func (module *SourceTextModuleRecord) GetBindingValue(name unistring.String, _ bool) Value { - getter, ok := module.exportGetters[name] - if !ok { - return nil - // panic(name + " is not defined, this shoukldn't be possible due to how ESM works") - } - return getter() -} - func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { + // c := newCompiler() defer func() { if x := recover(); x != nil { switch x1 := x.(type) { @@ -570,32 +622,9 @@ func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { } }() - // TODO catch panics/exceptions module.compiler.compileModule(module) + // p = c.p return - /* this is in the compiler - for _, e := range module.indirectExportEntries { - resolution := module.ResolveExport(e.exportName) - if resolution == nil { // TODO or ambiguous - panic(module.rt.newSyntaxError("bad resolution", -1)) // TODO fix - } - // TODO asserts - } - for _, in := range module.importEntries { - importedModule := module.rt.hostResolveImportedModule(module, in.moduleRequest) - if in.importName == "*" { - namespace := getModuleNamespace(importedModule) - b, exists := module.compiler.scope.bindName(in.localName) - if exists { - panic("this bad?") - } - b.emitInit() - } - - } - - return nil // TODO implement - */ } func (rt *Runtime) getModuleNamespace(module ModuleRecord) *Namespace { @@ -706,12 +735,25 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese return starResolution, false } -func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) error { - return rt.CyclicModuleRecordEvaluate(module) +func (module *SourceTextModuleRecord) Instanciate() CyclicModuleInstance { + return &SourceTextModuleInstance{ + cyclicModuleStub: cyclicModuleStub{ + status: module.status, + requestedModules: module.requestedModules, + }, + moduleRecord: module, + exportGetters: make(map[unistring.String]func() Value), + } +} + +func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { + return rt.CyclicModuleRecordEvaluate(module, module.name) } func (module *SourceTextModuleRecord) Link() error { - return module.compiler.CyclicModuleRecordConcreteLink(module) + c := newCompiler() + c.hostResolveImportedModule = module.hostResolveImportedModule + return c.CyclicModuleRecordConcreteLink(module) } type cyclicModuleStub struct { diff --git a/modules_test.go b/modules_test.go index 2e4027a5..d82d0fbe 100644 --- a/modules_test.go +++ b/modules_test.go @@ -2,10 +2,12 @@ package goja import ( "fmt" + "sync" "testing" ) func TestSimpleModule(t *testing.T) { + t.Parallel() type cacheElement struct { m ModuleRecord err error @@ -62,10 +64,7 @@ globalThis.s = b() for name, cases := range testCases { a, b := cases.a, cases.b t.Run(name, func(t *testing.T) { - vm := New() - vm.Set("p", vm.ToValue(func() { - // fmt.Println("p called") - })) + t.Parallel() cache := make(map[string]cacheElement) var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { @@ -88,20 +87,11 @@ globalThis.s = b() return nil, err } p.compiler = newCompiler() - p.compiler.hostResolveImportedModule = hostResolveImportedModule cache[specifier] = cacheElement{m: p} return p, nil } - vm.hostResolveImportedModule = hostResolveImportedModule - vm.Set("l", func() { - fmt.Println("l called") - fmt.Printf("iter stack ; %+v", vm.vm.iterStack) - }) - m, err := vm.hostResolveImportedModule(nil, "a.js") - if err != nil { - t.Fatalf("got error %s", err) - } + m, err := hostResolveImportedModule(nil, "a.js") p := m.(*SourceTextModuleRecord) err = p.Link() @@ -109,14 +99,35 @@ globalThis.s = b() t.Fatalf("got error %s", err) } - err = p.Evaluate(vm) - if err != nil { - t.Fatalf("got error %s", err) - } - v := vm.Get("s") - if v == nil || v.ToNumber().ToInteger() != 5 { - t.Fatalf("expected 5 got %s", v) + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + var err error + vm := New() + vm.Set("p", vm.ToValue(func() { + // fmt.Println("p called") + })) + vm.hostResolveImportedModule = hostResolveImportedModule + vm.Set("l", func() { + fmt.Println("l called") + fmt.Printf("iter stack ; %+v", vm.vm.iterStack) + }) + if err != nil { + t.Fatalf("got error %s", err) + } + _, err = m.Evaluate(vm) + if err != nil { + t.Fatalf("got error %s", err) + } + v := vm.Get("s") + if v == nil || v.ToNumber().ToInteger() != 5 { + t.Fatalf("expected 5 got %s", v) + } + }() } + wg.Wait() }) } } diff --git a/runtime.go b/runtime.go index c12ec313..1b0a7b8f 100644 --- a/runtime.go +++ b/runtime.go @@ -183,6 +183,8 @@ type Runtime struct { hash *maphash.Hash idSeq uint64 + modules map[string]ModuleInstance + jobQueue []func() promiseRejectionTracker PromiseRejectionTracker diff --git a/tc39_test.go b/tc39_test.go index 22233c72..f5e2fea7 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -712,7 +712,6 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R return nil, err } p.compiler = newCompiler() - p.compiler.hostResolveImportedModule = hostResolveImportedModule cache[fname] = cacheElement{m: p} return p, nil } @@ -730,7 +729,7 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } early = false - err = p.Evaluate(vm) + _, err = m.Evaluate(vm) return } diff --git a/vm.go b/vm.go index f8e3f1b8..afd26937 100644 --- a/vm.go +++ b/vm.go @@ -854,7 +854,7 @@ func (s initStack1) exec(vm *vm) { type export struct { idx uint32 - callback func(func() Value) + callback func(*vm, func() Value) } func (e export) exec(vm *vm) { @@ -865,7 +865,7 @@ func (e export) exec(vm *vm) { for i := 0; i < level; i++ { stash = stash.outer } - e.callback(func() Value { + e.callback(vm, func() Value { return stash.getByIdx(idx) }) vm.pc++ @@ -2085,10 +2085,10 @@ func (s setGlobalStrict) exec(vm *vm) { } // Load a var indirectly from another module -type loadIndirect func() Value +type loadIndirect func(vm *vm) Value func (g loadIndirect) exec(vm *vm) { - vm.push(nilSafe(g())) + vm.push(nilSafe(g(vm))) vm.pc++ } From 238dccdf37f2c759c63045bb70d7888acb595979 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 6 Jun 2022 16:19:39 +0300 Subject: [PATCH 025/124] Split ParseModule into ParseModule And ModuleFromAst --- module.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/module.go b/module.go index 3ffb49a6..166cb0cf 100644 --- a/module.go +++ b/module.go @@ -489,6 +489,10 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul if err != nil { return nil, err } + return ModuleFromAST(name, body, resolveModule) +} + +func ModuleFromAST(name string, body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { requestedModules := requestedModulesFromAst(body.ImportEntries, body.ExportEntries) importEntries := importEntriesFromAst(body.ImportEntries) // 6. Let importedBoundNames be ImportedLocalNames(importEntries). From 26a0311a4bfc436658608d6521f6536861d5533d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 6 Jun 2022 16:28:06 +0300 Subject: [PATCH 026/124] Fix go vet --- modules_test.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules_test.go b/modules_test.go index d82d0fbe..69c97937 100644 --- a/modules_test.go +++ b/modules_test.go @@ -2,7 +2,6 @@ package goja import ( "fmt" - "sync" "testing" ) @@ -99,11 +98,10 @@ globalThis.s = b() t.Fatalf("got error %s", err) } - wg := sync.WaitGroup{} for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() + i := i + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + t.Parallel() var err error vm := New() vm.Set("p", vm.ToValue(func() { @@ -125,9 +123,8 @@ globalThis.s = b() if v == nil || v.ToNumber().ToInteger() != 5 { t.Fatalf("expected 5 got %s", v) } - }() + }) } - wg.Wait() }) } } From 2bfb5754056da15c63356a35ed97130e0100dad1 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 6 Jun 2022 16:56:13 +0300 Subject: [PATCH 027/124] Fix some test panics --- compiler.go | 10 ++++++++-- compiler_stmt.go | 8 ++++---- module.go | 23 +++++++++++++---------- runtime.go | 2 +- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/compiler.go b/compiler.go index d0fc0ff9..1c8d31ea 100644 --- a/compiler.go +++ b/compiler.go @@ -804,8 +804,14 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { exportName := unistring.String(entry.exportName) c.emit(export{callback: func(vm *vm, getter func() Value) { - m := vm.r.modules[module.name].(*SourceTextModuleInstance) - m.exportGetters[exportName] = getter + m := vm.r.modules[module] + + if s, ok := m.(*SourceTextModuleInstance); !ok { + fmt.Println(vm.r.modules, module.name) + vm.r.throwReferenceError(exportName) // TODO fix + } else { + s.exportGetters[exportName] = getter + } }}) } if len(scope.bindings) > 0 && !ownLexScope { diff --git a/compiler_stmt.go b/compiler_stmt.go index 38d2488d..f1618b88 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -847,9 +847,9 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } identifier := name.IdentifierName // this - moduleName := expr.FromClause.ModuleSpecifier.String() + // moduleName := expr.FromClause.ModuleSpecifier.String() localB.getIndirect = func(vm *vm) Value { - m := vm.r.modules[moduleName] + m := vm.r.modules[module] return m.GetBindingValue(identifier, true) } } @@ -869,10 +869,10 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) } - moduleName := expr.FromClause.ModuleSpecifier.String() + // moduleName := expr.FromClause.ModuleSpecifier.String() localB.getIndirect = func(vm *vm) Value { // TODO this should be just "default", this also likely doesn't work for export aliasing - m := vm.r.modules[moduleName] + m := vm.r.modules[module] return m.GetBindingValue(unistring.String("default"), true) } } diff --git a/module.go b/module.go index 166cb0cf..d94db479 100644 --- a/module.go +++ b/module.go @@ -138,10 +138,10 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) (mi ModuleInstance, err error) { // TODO asserts if rt.modules == nil { - rt.modules = make(map[string]ModuleInstance) + rt.modules = make(map[ModuleRecord]ModuleInstance) } stackInstance := []CyclicModuleInstance{} - if mi, _, err = rt.innerModuleEvaluation(c, &stackInstance, 0, name, make(map[ModuleRecord]CyclicModuleInstance)); err != nil { + if mi, _, err = rt.innerModuleEvaluation(c, &stackInstance, 0, name); err != nil { /* for _, m := range stack { // TODO asserts @@ -159,23 +159,26 @@ func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) func (rt *Runtime) innerModuleEvaluation( m ModuleRecord, stack *[]CyclicModuleInstance, index uint, - name string, instances map[ModuleRecord]CyclicModuleInstance, // TODO remove thsi?!? + name string, ) (mi ModuleInstance, idx uint, err error) { if len(*stack) > 100000 { panic("too deep dependancy stack of 100000") } var cr CyclicModuleRecord var ok bool + var c CyclicModuleInstance if cr, ok = m.(CyclicModuleRecord); !ok { mi, err = m.Evaluate(rt) + rt.modules[m] = mi return mi, index, err - } - c, ok := instances[cr] - if !ok { + } else { + mi, ok = rt.modules[m] + if ok { + return mi, index, nil + } c = cr.Instanciate() - instances[cr] = c + rt.modules[m] = c } - rt.modules[name] = c if status := c.Status(); status == Evaluated { // TODO switch return nil, index, c.EvaluationError() } else if status == Evaluating { @@ -195,7 +198,7 @@ func (rt *Runtime) innerModuleEvaluation( if err != nil { return nil, 0, err } - mi, index, err = rt.innerModuleEvaluation(requiredModule, stack, index, required, instances) + mi, index, err = rt.innerModuleEvaluation(requiredModule, stack, index, required) if err != nil { return nil, 0, err } @@ -267,7 +270,7 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, e func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool) Value { getter, ok := s.exportGetters[name] if !ok { - // return nil + return nil // panic(name + " is not defined, this shoukldn't be possible due to how ESM works") } return getter() diff --git a/runtime.go b/runtime.go index 1b0a7b8f..a34cb586 100644 --- a/runtime.go +++ b/runtime.go @@ -183,7 +183,7 @@ type Runtime struct { hash *maphash.Hash idSeq uint64 - modules map[string]ModuleInstance + modules map[ModuleRecord]ModuleInstance jobQueue []func() From 439b7eca5e2b62ddb5025bf0fb45bc6967ff2ca6 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 6 Jun 2022 17:16:07 +0300 Subject: [PATCH 028/124] More vet fixes --- parser/statement.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index 204c510f..424d53c0 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -900,8 +900,6 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { self.semicolon() return &ast.ExportDeclaration{NamedExports: namedExports} } - - return nil } func (self *_parser) parseImportDeclaration() *ast.ImportDeclaration { From 258d0cf338d7328907e1c1fdfa6e596bfc110907 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 6 Jun 2022 17:44:14 +0300 Subject: [PATCH 029/124] fixes for staticcheck --- compiler.go | 20 ------------------- compiler_stmt.go | 1 + module.go | 51 +++++++++++++++++++++++++----------------------- modules_test.go | 3 +++ 4 files changed, 31 insertions(+), 44 deletions(-) diff --git a/compiler.go b/compiler.go index 1c8d31ea..d2297cfd 100644 --- a/compiler.go +++ b/compiler.go @@ -1081,26 +1081,6 @@ func (c *compiler) createImmutableBinding(name unistring.String, isStrict bool) return b } -func (c *compiler) createImportBinding(n string, m ModuleRecord, n2 string) *binding { - // TODO Do something :shrug: - // TODO fix this for not source text module records - - name := unistring.NewFromString(n) - - s := m.(*SourceTextModuleRecord) - exportedBinding, _ := s.scope.lookupName(unistring.NewFromString(n2)) - _ = exportedBinding - // TODO use exportedBinding - // localBinding.emitInit() - // TODO this is likely wrong - c.scope.bindings = append(c.scope.bindings, exportedBinding) - if c.scope.boundNames == nil { - c.scope.boundNames = make(map[unistring.String]*binding) - } - c.scope.boundNames[name] = exportedBinding - return exportedBinding -} - func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding { if name == "let" { c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") diff --git a/compiler_stmt.go b/compiler_stmt.go index f1618b88..0255ed03 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -826,6 +826,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { } if expr.ImportClause != nil { if namespace := expr.ImportClause.NameSpaceImport; namespace != nil { + c.throwSyntaxError(int(expr.Idx0()), "namespace imports not implemented") } if named := expr.ImportClause.NamedImports; named != nil { for _, name := range named.ImportsList { diff --git a/module.go b/module.go index d94db479..20d2c51b 100644 --- a/module.go +++ b/module.go @@ -20,9 +20,11 @@ type ModuleRecord interface { GetExportedNames(resolveset ...*SourceTextModuleRecord) []string // TODO maybe this parameter is wrong ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error - Evaluate(rt *Runtime) (ModuleInstance, error) - Namespace() *Namespace - SetNamespace(*Namespace) + Evaluate(*Runtime) (ModuleInstance, error) + /* + Namespace() *Namespace + SetNamespace(*Namespace) + */ } type CyclicModuleRecordStatus uint8 @@ -125,9 +127,8 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor for i := len(*stack) - 1; i >= 0; i-- { requiredModule := (*stack)[i] // TODO assert - requiredC := requiredModule.(CyclicModuleRecord) - requiredC.SetStatus(Linked) - if requiredC == module { + requiredModule.SetStatus(Linked) + if requiredModule == module { break } } @@ -135,13 +136,13 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor return index, nil } -func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) (mi ModuleInstance, err error) { +func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) (mi ModuleInstance, err error) { // TODO asserts - if rt.modules == nil { - rt.modules = make(map[ModuleRecord]ModuleInstance) + if r.modules == nil { + r.modules = make(map[ModuleRecord]ModuleInstance) } stackInstance := []CyclicModuleInstance{} - if mi, _, err = rt.innerModuleEvaluation(c, &stackInstance, 0, name); err != nil { + if mi, _, err = r.innerModuleEvaluation(c, &stackInstance, 0, name); err != nil { /* for _, m := range stack { // TODO asserts @@ -157,7 +158,7 @@ func (rt *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) return mi, nil } -func (rt *Runtime) innerModuleEvaluation( +func (r *Runtime) innerModuleEvaluation( m ModuleRecord, stack *[]CyclicModuleInstance, index uint, name string, ) (mi ModuleInstance, idx uint, err error) { @@ -168,16 +169,16 @@ func (rt *Runtime) innerModuleEvaluation( var ok bool var c CyclicModuleInstance if cr, ok = m.(CyclicModuleRecord); !ok { - mi, err = m.Evaluate(rt) - rt.modules[m] = mi + mi, err = m.Evaluate(r) + r.modules[m] = mi return mi, index, err } else { - mi, ok = rt.modules[m] + mi, ok = r.modules[m] if ok { return mi, index, nil } c = cr.Instanciate() - rt.modules[m] = c + r.modules[m] = c } if status := c.Status(); status == Evaluated { // TODO switch return nil, index, c.EvaluationError() @@ -194,11 +195,11 @@ func (rt *Runtime) innerModuleEvaluation( *stack = append(*stack, c) var requiredModule ModuleRecord for _, required := range c.RequestedModules() { - requiredModule, err = rt.hostResolveImportedModule(c, required) + requiredModule, err = r.hostResolveImportedModule(c, required) if err != nil { return nil, 0, err } - mi, index, err = rt.innerModuleEvaluation(requiredModule, stack, index, required) + mi, index, err = r.innerModuleEvaluation(requiredModule, stack, index, required) if err != nil { return nil, 0, err } @@ -211,7 +212,7 @@ func (rt *Runtime) innerModuleEvaluation( } } } - _, err = c.ExecuteModule(rt) + _, err = c.ExecuteModule(r) if err != nil { return nil, 0, err } @@ -221,9 +222,8 @@ func (rt *Runtime) innerModuleEvaluation( for i := len(*stack) - 1; i >= 0; i-- { requiredModule := (*stack)[i] // TODO assert - requiredC := requiredModule.(CyclicModuleInstance) - requiredC.SetStatus(Evaluated) - if requiredC == c { + requiredModule.SetStatus(Evaluated) + if requiredModule == c { break } } @@ -278,8 +278,7 @@ func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool type SourceTextModuleRecord struct { cyclicModuleStub - name string // TODO remove this :crossed_fingers: - scope *scope + name string // TODO remove this :crossed_fingers: compiler *compiler // TODO remove this body *ast.Program // context @@ -634,6 +633,7 @@ func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { return } +/* func (rt *Runtime) getModuleNamespace(module ModuleRecord) *Namespace { if c, ok := module.(CyclicModuleRecord); ok && c.Status() == Unlinked { panic("oops") // TODO beter oops @@ -668,6 +668,7 @@ func (rt *Runtime) moduleNamespaceCreate(module ModuleRecord, exports []string) } } +*/ type ResolveSetElement struct { Module ModuleRecord ExportName string @@ -764,7 +765,7 @@ func (module *SourceTextModuleRecord) Link() error { } type cyclicModuleStub struct { - namespace *Namespace + // namespace *Namespace status CyclicModuleRecordStatus dfsIndex uint ancestorDfsIndex uint @@ -812,6 +813,7 @@ func (c *cyclicModuleStub) RequestedModules() []string { return c.requestedModules } +/* func (c *cyclicModuleStub) Namespace() *Namespace { return c.namespace } @@ -819,3 +821,4 @@ func (c *cyclicModuleStub) Namespace() *Namespace { func (c *cyclicModuleStub) SetNamespace(namespace *Namespace) { c.namespace = namespace } +*/ diff --git a/modules_test.go b/modules_test.go index 69c97937..d4083a72 100644 --- a/modules_test.go +++ b/modules_test.go @@ -91,6 +91,9 @@ globalThis.s = b() } m, err := hostResolveImportedModule(nil, "a.js") + if err != nil { + t.Fatalf("got error %s", err) + } p := m.(*SourceTextModuleRecord) err = p.Link() From 6acaadbc7554b50d4ab17c507659227cddaecf0d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 7 Jun 2022 15:57:41 +0300 Subject: [PATCH 030/124] Make compiler when needed for modules --- module.go | 16 ++++++++-------- modules_test.go | 1 - tc39_test.go | 1 - 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/module.go b/module.go index 20d2c51b..5a3879d0 100644 --- a/module.go +++ b/module.go @@ -263,7 +263,7 @@ type SourceTextModuleInstance struct { } func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, error) { - _, err := rt.RunProgram(s.moduleRecord.compiler.p) + _, err := rt.RunProgram(s.moduleRecord.p) return s, err } @@ -278,9 +278,9 @@ func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool type SourceTextModuleRecord struct { cyclicModuleStub - name string // TODO remove this :crossed_fingers: - compiler *compiler // TODO remove this - body *ast.Program + name string // TODO remove this :crossed_fingers: + body *ast.Program + p *Program // context // importmeta importEntries []importEntry @@ -568,7 +568,7 @@ func ModuleFromAST(name string, body *ast.Program, resolveModule HostResolveImpo func (module *SourceTextModuleRecord) ExecuteModule(rt *Runtime) (Value, error) { // TODO copy runtime.RunProgram here with some changes so that it doesn't touch the global ? - return rt.RunProgram(module.compiler.p) + return rt.RunProgram(module.p) } func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { @@ -616,7 +616,7 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceT } func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { - // c := newCompiler() + c := newCompiler() defer func() { if x := recover(); x != nil { switch x1 := x.(type) { @@ -628,8 +628,8 @@ func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { } }() - module.compiler.compileModule(module) - // p = c.p + c.compileModule(module) + module.p = c.p return } diff --git a/modules_test.go b/modules_test.go index d4083a72..5c60ed95 100644 --- a/modules_test.go +++ b/modules_test.go @@ -85,7 +85,6 @@ globalThis.s = b() cache[specifier] = cacheElement{err: err} return nil, err } - p.compiler = newCompiler() cache[specifier] = cacheElement{m: p} return p, nil } diff --git a/tc39_test.go b/tc39_test.go index f5e2fea7..acaa025f 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -711,7 +711,6 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R cache[fname] = cacheElement{err: err} return nil, err } - p.compiler = newCompiler() cache[fname] = cacheElement{m: p} return p, nil } From b314f9b4a2123544077a945fd931fc53a262ffed Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 7 Jun 2022 16:07:21 +0300 Subject: [PATCH 031/124] Remove hostResolveImportedModule from runtime --- module.go | 13 +++++++------ modules_test.go | 1 - runtime.go | 2 -- tc39_test.go | 3 +-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/module.go b/module.go index 5a3879d0..ef0f7a82 100644 --- a/module.go +++ b/module.go @@ -136,13 +136,14 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor return index, nil } -func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) (mi ModuleInstance, err error) { +func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string, resolve HostResolveImportedModuleFunc, +) (mi ModuleInstance, err error) { // TODO asserts if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) } stackInstance := []CyclicModuleInstance{} - if mi, _, err = r.innerModuleEvaluation(c, &stackInstance, 0, name); err != nil { + if mi, _, err = r.innerModuleEvaluation(c, &stackInstance, 0, name, resolve); err != nil { /* for _, m := range stack { // TODO asserts @@ -160,7 +161,7 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string) func (r *Runtime) innerModuleEvaluation( m ModuleRecord, stack *[]CyclicModuleInstance, index uint, - name string, + name string, resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, idx uint, err error) { if len(*stack) > 100000 { panic("too deep dependancy stack of 100000") @@ -195,11 +196,11 @@ func (r *Runtime) innerModuleEvaluation( *stack = append(*stack, c) var requiredModule ModuleRecord for _, required := range c.RequestedModules() { - requiredModule, err = r.hostResolveImportedModule(c, required) + requiredModule, err = resolve(c, required) if err != nil { return nil, 0, err } - mi, index, err = r.innerModuleEvaluation(requiredModule, stack, index, required) + mi, index, err = r.innerModuleEvaluation(requiredModule, stack, index, required, resolve) if err != nil { return nil, 0, err } @@ -755,7 +756,7 @@ func (module *SourceTextModuleRecord) Instanciate() CyclicModuleInstance { } func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { - return rt.CyclicModuleRecordEvaluate(module, module.name) + return rt.CyclicModuleRecordEvaluate(module, module.name, module.hostResolveImportedModule) } func (module *SourceTextModuleRecord) Link() error { diff --git a/modules_test.go b/modules_test.go index 5c60ed95..851a27c1 100644 --- a/modules_test.go +++ b/modules_test.go @@ -109,7 +109,6 @@ globalThis.s = b() vm.Set("p", vm.ToValue(func() { // fmt.Println("p called") })) - vm.hostResolveImportedModule = hostResolveImportedModule vm.Set("l", func() { fmt.Println("l called") fmt.Printf("iter stack ; %+v", vm.vm.iterStack) diff --git a/runtime.go b/runtime.go index a34cb586..4add2d89 100644 --- a/runtime.go +++ b/runtime.go @@ -188,8 +188,6 @@ type Runtime struct { jobQueue []func() promiseRejectionTracker PromiseRejectionTracker - // TODO add a type and a set method - hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) } type StackFrame struct { diff --git a/tc39_test.go b/tc39_test.go index acaa025f..e376cfe7 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -715,8 +715,7 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R return p, nil } - vm.hostResolveImportedModule = hostResolveImportedModule - m, err := vm.hostResolveImportedModule(nil, path.Base(name)) + m, err := hostResolveImportedModule(nil, path.Base(name)) if err != nil { return } From b1eeef49966d348e70fe89a2b95f72f7b526a0b7 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 7 Jun 2022 17:26:31 +0300 Subject: [PATCH 032/124] fix returning ModuleInstance on Evaluation --- module.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/module.go b/module.go index ef0f7a82..ec8d8a46 100644 --- a/module.go +++ b/module.go @@ -178,6 +178,7 @@ func (r *Runtime) innerModuleEvaluation( if ok { return mi, index, nil } + mi = c c = cr.Instanciate() r.modules[m] = c } @@ -200,11 +201,12 @@ func (r *Runtime) innerModuleEvaluation( if err != nil { return nil, 0, err } - mi, index, err = r.innerModuleEvaluation(requiredModule, stack, index, required, resolve) + var requiredInstance ModuleInstance + requiredInstance, index, err = r.innerModuleEvaluation(requiredModule, stack, index, required, resolve) if err != nil { return nil, 0, err } - if requiredC, ok := mi.(CyclicModuleInstance); ok { + if requiredC, ok := requiredInstance.(CyclicModuleInstance); ok { // TODO some asserts if requiredC.Status() == Evaluating { if ancestorIndex := c.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { @@ -213,7 +215,7 @@ func (r *Runtime) innerModuleEvaluation( } } } - _, err = c.ExecuteModule(r) + mi, err = c.ExecuteModule(r) if err != nil { return nil, 0, err } From c1f7626fcf7d7864f61c3d2ebfd5805d9cee48fc Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 9 Jun 2022 11:09:34 +0300 Subject: [PATCH 033/124] Fixes for imports in various cases --- compiler.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler.go b/compiler.go index d2297cfd..a82ce4e4 100644 --- a/compiler.go +++ b/compiler.go @@ -139,7 +139,9 @@ func (b *binding) emitGet() { func (b *binding) emitGetAt(pos int) { b.markAccessPointAt(pos) - if b.isVar && !b.isArg { + if b.getIndirect != nil { + b.scope.c.p.code[pos] = loadIndirect(b.getIndirect) + } else if b.isVar && !b.isArg { b.scope.c.p.code[pos] = loadStash(0) } else { b.scope.c.p.code[pos] = loadStashLex(0) @@ -193,7 +195,9 @@ func (b *binding) emitInit() { func (b *binding) emitGetVar(callee bool) { b.markAccessPoint() - if b.isVar && !b.isArg { + if b.getIndirect != nil { + b.scope.c.emit(loadIndirect(b.getIndirect)) + } else if b.isVar && !b.isArg { b.scope.c.emit(&loadMixed{name: b.name, callee: callee}) } else { b.scope.c.emit(&loadMixedLex{name: b.name, callee: callee}) @@ -750,6 +754,11 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { } } } + for _, exp := range in.Body { + if imp, ok := exp.(*ast.ImportDeclaration); ok { + c.compileImportDeclaration(imp) + } + } // 15.2.1.17.4 step 9 end funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) From 788e4c0a64d1f093b9f0df8bbcb43d027bf6db94 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Jun 2022 16:15:29 +0300 Subject: [PATCH 034/124] Use ModuleRecord not Instance when resolving during evaluation --- module.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.go b/module.go index ec8d8a46..5e31e802 100644 --- a/module.go +++ b/module.go @@ -197,7 +197,7 @@ func (r *Runtime) innerModuleEvaluation( *stack = append(*stack, c) var requiredModule ModuleRecord for _, required := range c.RequestedModules() { - requiredModule, err = resolve(c, required) + requiredModule, err = resolve(m, required) if err != nil { return nil, 0, err } From 0f987b8e828c239d69c92413ec5f6da647d4ab53 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Jun 2022 19:34:00 +0300 Subject: [PATCH 035/124] Use ModuleRecord when resolving in compileImportDeclaration --- compiler_stmt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler_stmt.go b/compiler_stmt.go index 0255ed03..7ffc8a83 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -818,7 +818,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { return // TODO is this right? } // TODO fix, maybe cache this?!? have the current module as a field? - module, err := c.hostResolveImportedModule(nil, expr.FromClause.ModuleSpecifier.String()) + module, err := c.hostResolveImportedModule(c.module, expr.FromClause.ModuleSpecifier.String()) _ = module // TODO fix if err != nil { // TODO this should in practice never happen From eaf169de45cd5064a0473d2fcaf0d4f3aa3f3952 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Jun 2022 19:44:26 +0300 Subject: [PATCH 036/124] I think I got this wrong from the spec --- compiler.go | 1 - compiler_stmt.go | 2 +- module.go | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler.go b/compiler.go index a82ce4e4..465e3036 100644 --- a/compiler.go +++ b/compiler.go @@ -804,7 +804,6 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { name := unistring.String(entry.localName) b, ok := scope.boundNames[name] if !ok { - // TODO fix this somehow - this is as *default* doesn't get bound before it's used b, _ = scope.bindName(name) } diff --git a/compiler_stmt.go b/compiler_stmt.go index 7ffc8a83..61357fb2 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -791,7 +791,7 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { List: []*ast.Binding{ { Target: &ast.Identifier{ - Name: unistring.String("*default*"), + Name: unistring.String("default"), Idx: h.FunctionLiteral.Idx0(), }, Initializer: h.FunctionLiteral, diff --git a/module.go b/module.go index 5e31e802..5e36da63 100644 --- a/module.go +++ b/module.go @@ -404,7 +404,7 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { } } else if hoistable := exportDeclaration.HoistableDeclaration; hoistable != nil { - localName := "*default*" + localName := "default" exportName := "default" if hoistable.FunctionDeclaration != nil { if hoistable.FunctionDeclaration.Function.Name != nil { From 4e6b82597b0a0f6a807a6d737cc1b98a5fcc8678 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Jun 2022 20:06:21 +0300 Subject: [PATCH 037/124] Support arrow functions (among others) as default export --- compiler_stmt.go | 14 ++++++++++++++ modules_test.go | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/compiler_stmt.go b/compiler_stmt.go index 61357fb2..1793ad4c 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -809,6 +809,20 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { }}) */ } + } else if assign := expr.AssignExpression; assign != nil { + c.compileLexicalDeclaration(&ast.LexicalDeclaration{ + Idx: assign.Idx0(), + Token: token.CONST, + List: []*ast.Binding{ + { + Target: &ast.Identifier{ + Name: unistring.String("default"), + Idx: assign.Idx0(), + }, + Initializer: assign, + }, + }, + }) } // TODO } diff --git a/modules_test.go b/modules_test.go index 851a27c1..f836df72 100644 --- a/modules_test.go +++ b/modules_test.go @@ -59,6 +59,12 @@ globalThis.s = b() `, b: ``, }, + "default export arrow": { + a: `import b from "dep.js"; +globalThis.s = b() +`, + b: `export default () => {globalThis.p(); return 5 };`, + }, } for name, cases := range testCases { a, b := cases.a, cases.b From a73983a76e97816f8b28ca69c4011f121e85a6f2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sat, 11 Jun 2022 13:51:10 +0300 Subject: [PATCH 038/124] More parsing fies --- modules_test.go | 7 +++++++ parser/statement.go | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules_test.go b/modules_test.go index f836df72..63fe77e1 100644 --- a/modules_test.go +++ b/modules_test.go @@ -65,6 +65,13 @@ globalThis.s = b() `, b: `export default () => {globalThis.p(); return 5 };`, }, + "default export with as": { + a: `import b from "dep.js"; +globalThis.s = b() +`, + b: `function f() {return 5;}; + export { f as default };`, + }, } for name, cases := range testCases { a, b := cases.a, cases.b diff --git a/parser/statement.go b/parser/statement.go index 424d53c0..0a2f8770 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -851,7 +851,6 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.MULTIPLY: // FIXME: should also parse NamedExports if '{' exportFromClause := self.parseExportFromClause() fromClause := self.parseFromClause() - self.semicolon() return &ast.ExportDeclaration{ ExportFromClause: exportFromClause, FromClause: fromClause, @@ -859,7 +858,6 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.LEFT_BRACE: namedExports := self.parseNamedExports() fromClause := self.parseFromClause() - self.semicolon() return &ast.ExportDeclaration{ NamedExports: namedExports, FromClause: fromClause, From 82cf429f57e418c7b56db53ae04573dfcbfdc21d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 14 Jun 2022 18:00:10 +0300 Subject: [PATCH 039/124] Throw ReferenceError before imports are initialized --- compiler_stmt.go | 13 +++++++++++-- module.go | 8 ++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler_stmt.go b/compiler_stmt.go index 1793ad4c..43536493 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -865,7 +865,11 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { // moduleName := expr.FromClause.ModuleSpecifier.String() localB.getIndirect = func(vm *vm) Value { m := vm.r.modules[module] - return m.GetBindingValue(identifier, true) + v, ok := m.GetBindingValue(identifier, true) + if !ok { + vm.r.throwReferenceError(identifier) + } + return v } } } @@ -885,10 +889,15 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) } // moduleName := expr.FromClause.ModuleSpecifier.String() + identifier := unistring.String("default") localB.getIndirect = func(vm *vm) Value { // TODO this should be just "default", this also likely doesn't work for export aliasing m := vm.r.modules[module] - return m.GetBindingValue(unistring.String("default"), true) + v, ok := m.GetBindingValue(identifier, true) + if !ok { + vm.r.throwReferenceError(identifier) + } + return v } } } diff --git a/module.go b/module.go index 5e36da63..73fd897d 100644 --- a/module.go +++ b/module.go @@ -237,7 +237,7 @@ func (r *Runtime) innerModuleEvaluation( type ( ModuleInstance interface { // Evaluate(rt *Runtime) (ModuleInstance, error) - GetBindingValue(unistring.String, bool) Value + GetBindingValue(unistring.String, bool) (Value, bool) } CyclicModuleInstance interface { ModuleInstance @@ -270,13 +270,13 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, e return s, err } -func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool) Value { +func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool) (Value, bool) { getter, ok := s.exportGetters[name] if !ok { - return nil + return nil, false // panic(name + " is not defined, this shoukldn't be possible due to how ESM works") } - return getter() + return getter(), true } type SourceTextModuleRecord struct { From 04e0b13cf436e101d7f177d5ee5c579f92f6ce13 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 11:38:35 +0300 Subject: [PATCH 040/124] Fix trailing commas in import lists --- parser/statement.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index 0a2f8770..a102afb1 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -1132,11 +1132,7 @@ func (self *_parser) parseNamedImports() *ast.NamedImports { } func (self *_parser) parseImportsList() (importsList []*ast.ImportSpecifier) { - if self.token == token.RIGHT_BRACE { - return - } - - for { + for self.token != token.RIGHT_BRACE { importsList = append(importsList, self.parseImportSpecifier()) if self.token != token.COMMA { break From 90a123b2563bf0f9d0478d5327c4e65a8da799f2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 12:16:01 +0300 Subject: [PATCH 041/124] Top level function definitions in modules should be lexical --- compiler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler.go b/compiler.go index 465e3036..8e31d638 100644 --- a/compiler.go +++ b/compiler.go @@ -979,7 +979,7 @@ func (c *compiler) extractFunctions(list []ast.Statement) (funcs []*ast.Function func (c *compiler) createFunctionBindings(funcs []*ast.FunctionDeclaration) { s := c.scope if s.outer != nil { - unique := !s.function && !s.variable && s.strict + unique := (!s.function && !s.variable && s.strict) || (c.module != nil && s.outer.outer == nil) for _, decl := range funcs { s.bindNameLexical(decl.Function.Name.Name, unique, int(decl.Function.Name.Idx1())-1) } From ecbdc4cb300caf071ac0c706840f9a8ab86ac35a Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 12:25:42 +0300 Subject: [PATCH 042/124] Throw syntax errors on exporting unknown bindings --- compiler.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler.go b/compiler.go index 8e31d638..88369771 100644 --- a/compiler.go +++ b/compiler.go @@ -804,6 +804,10 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { name := unistring.String(entry.localName) b, ok := scope.boundNames[name] if !ok { + if entry.localName != "default" { + // TODO fix index + c.throwSyntaxError(0, "exporting unknown binding: %q", name) + } b, _ = scope.bindName(name) } From b50623e4dcbd7c23177961fc7f3144e5c31d95ec Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 12:38:20 +0300 Subject: [PATCH 043/124] Don't require a semicolon after default export of a function --- parser/statement.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index a102afb1..92d6e2e8 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -884,14 +884,15 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { HoistableDeclaration: &ast.HoistableDeclaration{ FunctionLiteral: self.parseFunction(false), }, + IsDefault: true, } default: exp = &ast.ExportDeclaration{ AssignExpression: self.parseAssignmentExpression(), + IsDefault: true, } + self.semicolon() } - exp.IsDefault = true - self.semicolon() return exp default: namedExports := self.parseNamedExports() From a8d14d090cf0e476e20f0f0f0009e12883288bfd Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 13:43:14 +0300 Subject: [PATCH 044/124] Report ambigiuous impotts later --- compiler.go | 7 ++++++- compiler_stmt.go | 7 ++----- vm.go | 6 ++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler.go b/compiler.go index 88369771..0e67c6ee 100644 --- a/compiler.go +++ b/compiler.go @@ -745,7 +745,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { } else { resolution, ambiguous := importedModule.ResolveExport(in.importName) if resolution == nil || ambiguous { - c.throwSyntaxError(in.offset, "ambiguous import of %s", in.importName) + c.compileAmbiguousImport(unistring.String(in.importName)) + continue } if resolution.BindingName == "*namespace*" { } else { @@ -942,6 +943,10 @@ func (c *compiler) compile(in *ast.Program, strict, eval, inGlobal bool) { scope.finaliseVarAlloc(0) } +func (c *compiler) compileAmbiguousImport(name unistring.String) { + c.emit(ambiguousImport(name)) +} + func (c *compiler) compileDeclList(v []*ast.VariableDeclaration, inFunc bool) { for _, value := range v { c.createVarBindings(value, inFunc) diff --git a/compiler_stmt.go b/compiler_stmt.go index 43536493..c61ef615 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -846,11 +846,8 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { for _, name := range named.ImportsList { value, ambiguous := module.ResolveExport(name.IdentifierName.String()) - if ambiguous { // also ambiguous - c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of %s", name.IdentifierName.String()) - } - if value == nil { - c.throwSyntaxError(int(expr.Idx0()), "import of %s was not expoted from module %s", name.IdentifierName.String(), expr.FromClause.ModuleSpecifier.String()) + if ambiguous || value == nil { // also ambiguous + continue // ambiguous import already reports } n := name.Alias diff --git a/vm.go b/vm.go index afd26937..e7727f0f 100644 --- a/vm.go +++ b/vm.go @@ -3115,6 +3115,12 @@ func (n *newArrowFunc) exec(vm *vm) { vm.pc++ } +type ambiguousImport unistring.String + +func (a ambiguousImport) exec(vm *vm) { + panic(vm.r.newError(vm.r.global.SyntaxError, "Ambiguous import for name %s", a)) +} + func (vm *vm) alreadyDeclared(name unistring.String) Value { return vm.r.newError(vm.r.global.SyntaxError, "Identifier '%s' has already been declared", name) } From f3e29a89070d0fcea27067fb66ba45551428432b Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 14:07:52 +0300 Subject: [PATCH 045/124] Fix a bunch of ambigious import/export tests --- compiler.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler.go b/compiler.go index 0e67c6ee..de5400be 100644 --- a/compiler.go +++ b/compiler.go @@ -733,6 +733,17 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { scope = c.scope scope.function = true } + for _, in := range module.indirectExportEntries { + importedModule, err := c.hostResolveImportedModule(module, in.moduleRequest) + if err != nil { + panic(fmt.Errorf("previously resolved module returned error %w", err)) + } + resolution, ambiguous := importedModule.ResolveExport(in.importName) + if resolution == nil || ambiguous { + c.compileAmbiguousImport(unistring.String(in.importName)) + } + + } // scope.module = module // module.scope = scope // 15.2.1.17.4 step 9 start From 55b2c88b67d9a908066aead44e031e541475939f Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 14:59:00 +0300 Subject: [PATCH 046/124] Fix lexical and indirect exports --- compiler.go | 38 +++++++++++++++++++++++++++++++++++--- compiler_stmt.go | 38 ++++++++++++++++++++++++++++++++++++-- module.go | 8 +++++++- vm.go | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/compiler.go b/compiler.go index de5400be..6cbe66d3 100644 --- a/compiler.go +++ b/compiler.go @@ -513,7 +513,6 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { stackIdx, stashIdx := 0, 0 allInStash := s.isDynamic() for i, b := range s.bindings { - // fmt.Printf("Binding %+v\n", b) if allInStash || b.inStash { for scope, aps := range b.accessPoints { var level uint32 @@ -538,6 +537,11 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { idx: idx, callback: i.callback, } + case exportLex: + *ap = exportLex{ + idx: idx, + callback: i.callback, + } case storeStash: *ap = storeStash(idx) case storeStashP: @@ -827,15 +831,43 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { b.markAccessPoint() exportName := unistring.String(entry.exportName) - c.emit(export{callback: func(vm *vm, getter func() Value) { + callback := func(vm *vm, getter func() Value) { m := vm.r.modules[module] if s, ok := m.(*SourceTextModuleInstance); !ok { - fmt.Println(vm.r.modules, module.name) vm.r.throwReferenceError(exportName) // TODO fix } else { s.exportGetters[exportName] = getter } + } + if entry.lex { + c.emit(exportLex{callback: callback}) + } else { + c.emit(export{callback: callback}) + } + } + for _, entry := range module.indirectExportEntries { + otherModule, err := c.hostResolveImportedModule(c.module, entry.moduleRequest) + if err != nil { + panic(err) // this should not be possible + } + exportName := unistring.String(entry.exportName) + importName := unistring.String(entry.importName) + c.emit(exportIndirect{callback: func(vm *vm) { + m := vm.r.modules[module] + m2 := vm.r.modules[otherModule] + + if s, ok := m.(*SourceTextModuleInstance); !ok { + vm.r.throwReferenceError(exportName) // TODO fix + } else { + s.exportGetters[exportName] = func() Value { + v, ok := m2.GetBindingValue(importName, true) + if !ok { + vm.r.throwReferenceError(exportName) // TODO fix + } + return v + } + } }}) } if len(scope.bindings) > 0 && !ownLexScope { diff --git a/compiler_stmt.go b/compiler_stmt.go index c61ef615..51526586 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -823,6 +823,42 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { }, }, }) + } else if expr.ExportFromClause != nil { + from := expr.ExportFromClause + module, err := c.hostResolveImportedModule(c.module, expr.FromClause.ModuleSpecifier.String()) + if err != nil { + // TODO this should in practice never happen + c.throwSyntaxError(int(expr.Idx0()), err.Error()) + } + if from.NamedExports == nil { // star export -nothing to do + return + } + for _, name := range from.NamedExports.ExportsList { + value, ambiguous := module.ResolveExport(name.IdentifierName.String()) + + if ambiguous || value == nil { // also ambiguous + continue // ambiguous import already reports + } + + n := name.Alias + if n.String() == "" { + n = name.IdentifierName + } + localB, _ := c.scope.lookupName(n) + if localB == nil { + c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) + } + identifier := name.IdentifierName // this + // moduleName := expr.FromClause.ModuleSpecifier.String() + localB.getIndirect = func(vm *vm) Value { + m := vm.r.modules[module] + v, ok := m.GetBindingValue(identifier, true) + if !ok { + vm.r.throwReferenceError(identifier) + } + return v + } + } } // TODO } @@ -831,9 +867,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if expr.FromClause == nil { return // TODO is this right? } - // TODO fix, maybe cache this?!? have the current module as a field? module, err := c.hostResolveImportedModule(c.module, expr.FromClause.ModuleSpecifier.String()) - _ = module // TODO fix if err != nil { // TODO this should in practice never happen c.throwSyntaxError(int(expr.Idx0()), err.Error()) diff --git a/module.go b/module.go index 73fd897d..0114e0ca 100644 --- a/module.go +++ b/module.go @@ -274,7 +274,6 @@ func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool getter, ok := s.exportGetters[name] if !ok { return nil, false - // panic(name + " is not defined, this shoukldn't be possible due to how ESM works") } return getter(), true } @@ -306,6 +305,9 @@ type exportEntry struct { moduleRequest string importName string localName string + + // no standard + lex bool } func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { @@ -387,6 +389,7 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { result = append(result, exportEntry{ localName: id.Name.String(), exportName: id.Name.String(), + lex: false, }) } @@ -400,6 +403,7 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { result = append(result, exportEntry{ localName: id.Name.String(), exportName: id.Name.String(), + lex: true, }) } @@ -417,6 +421,7 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { result = append(result, exportEntry{ localName: localName, exportName: exportName, + lex: true, }) } else if fromClause := exportDeclaration.FromClause; fromClause != nil { if namedExports := exportDeclaration.NamedExports; namedExports != nil { @@ -449,6 +454,7 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { result = append(result, exportEntry{ exportName: "default", localName: "default", + lex: true, }) } else { panic("wat") diff --git a/vm.go b/vm.go index e7727f0f..d736bf7a 100644 --- a/vm.go +++ b/vm.go @@ -871,6 +871,38 @@ func (e export) exec(vm *vm) { vm.pc++ } +type exportLex struct { + idx uint32 + callback func(*vm, func() Value) +} + +func (e exportLex) exec(vm *vm) { + // from loadStashLex + level := int(e.idx >> 24) + idx := uint32(e.idx & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + e.callback(vm, func() Value { + v := stash.getByIdx(idx) + if v == nil { + panic(errAccessBeforeInit) + } + return v + }) + vm.pc++ +} + +type exportIndirect struct { + callback func(*vm) +} + +func (e exportIndirect) exec(vm *vm) { + e.callback(vm) + vm.pc++ +} + type storeStackP int func (s storeStackP) exec(vm *vm) { From 95d8b1fc49e7bcaa4a694d0d89d1efd2564f717f Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 15:06:41 +0300 Subject: [PATCH 047/124] Fix parsing errors with semicolons --- parser/statement.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index 92d6e2e8..0cfa3020 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -851,6 +851,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.MULTIPLY: // FIXME: should also parse NamedExports if '{' exportFromClause := self.parseExportFromClause() fromClause := self.parseFromClause() + self.semicolon() return &ast.ExportDeclaration{ ExportFromClause: exportFromClause, FromClause: fromClause, @@ -858,6 +859,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { case token.LEFT_BRACE: namedExports := self.parseNamedExports() fromClause := self.parseFromClause() + self.semicolon() return &ast.ExportDeclaration{ NamedExports: namedExports, FromClause: fromClause, @@ -962,8 +964,6 @@ func (self *_parser) parseFromClause() *ast.FromClause { return &ast.FromClause{ ModuleSpecifier: self.parseModuleSpecifier(), } - } else { - self.semicolon() } return nil From 9c2eeb0b53c6879690c919c7bfc2f06ee53c6cf2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 16:50:35 +0300 Subject: [PATCH 048/124] check identifiers on import --- compiler_stmt.go | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler_stmt.go b/compiler_stmt.go index 51526586..75f22667 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -888,6 +888,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if n.String() == "" { n = name.IdentifierName } + c.checkIdentifierLName(n, int(expr.Idx)) localB, _ := c.scope.lookupName(n) if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) From 9438c844a356ab12d1fe4b89952a518e777bf5f8 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Jun 2022 17:36:58 +0300 Subject: [PATCH 049/124] relay error up when it happens during compilation of module --- module.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/module.go b/module.go index 0114e0ca..9ab6e6fa 100644 --- a/module.go +++ b/module.go @@ -65,13 +65,14 @@ func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) err if _, err := c.innerModuleLinking(module, &stack, 0); err != nil { fmt.Println(err) for _, m := range stack { - if m.Status() != Linking { - return fmt.Errorf("bad status %+v on link", m.Status()) - } + /* + if m.Status() != Linking { + return fmt.Errorf("bad status %+v on link", m.Status()) + } + */ m.SetStatus(Unlinked) // TODO reset the rest - } module.SetStatus(Unlinked) return err From a0905d857996bc5ee68f655c883fa5db5cb711ef Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 17 Jun 2022 18:42:17 +0300 Subject: [PATCH 050/124] Report default not being exported during runtime --- compiler_stmt.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler_stmt.go b/compiler_stmt.go index 75f22667..597f06fb 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -909,11 +909,8 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if def := expr.ImportClause.ImportedDefaultBinding; def != nil { value, ambiguous := module.ResolveExport("default") - if ambiguous { // also ambiguous - c.throwSyntaxError(int(expr.Idx0()), "ambiguous import of default") - } - if value == nil { - c.throwSyntaxError(int(expr.Idx0()), "import of default was not exported from module %s", expr.FromClause.ModuleSpecifier.String()) + if ambiguous || value == nil { // also ambiguous + return // already handled } localB, _ := c.scope.lookupName(def.Name) From 764969cebf3ee1565d85d25b612b5fafba054f36 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 17 Jun 2022 19:17:31 +0300 Subject: [PATCH 051/124] Hacks around default exporting function and it being imported --- ast/node.go | 4 ++-- compiler.go | 9 +++++++-- compiler_stmt.go | 31 +------------------------------ parser/statement.go | 9 ++++++++- 4 files changed, 18 insertions(+), 35 deletions(-) diff --git a/ast/node.go b/ast/node.go index c5296ad2..dee48e54 100644 --- a/ast/node.go +++ b/ast/node.go @@ -440,7 +440,8 @@ type ( } FunctionDeclaration struct { - Function *FunctionLiteral + Function *FunctionLiteral + IsDefault bool // TODO figure out how to not have to that } ImportDeclaration struct { @@ -503,7 +504,6 @@ type ( HoistableDeclaration struct { FunctionDeclaration *FunctionDeclaration - FunctionLiteral *FunctionLiteral // GeneratorDeclaration // AsyncFunc and AsyncGenerator } diff --git a/compiler.go b/compiler.go index 6cbe66d3..2e406902 100644 --- a/compiler.go +++ b/compiler.go @@ -1016,10 +1016,12 @@ func (c *compiler) extractFunctions(list []ast.Statement) (funcs []*ast.Function continue } case *ast.ExportDeclaration: - if st.HoistableDeclaration == nil || st.HoistableDeclaration.FunctionDeclaration == nil { + if st.HoistableDeclaration == nil { continue } - decl = st.HoistableDeclaration.FunctionDeclaration + if st.HoistableDeclaration.FunctionDeclaration != nil { + decl = st.HoistableDeclaration.FunctionDeclaration + } default: continue } @@ -1034,6 +1036,9 @@ func (c *compiler) createFunctionBindings(funcs []*ast.FunctionDeclaration) { unique := (!s.function && !s.variable && s.strict) || (c.module != nil && s.outer.outer == nil) for _, decl := range funcs { s.bindNameLexical(decl.Function.Name.Name, unique, int(decl.Function.Name.Idx1())-1) + if decl.IsDefault && decl.Function.Name.Name != "default" { + s.bindNameLexical("default", unique, int(decl.Function.Name.Idx1())-1) + } } } else { for _, decl := range funcs { diff --git a/compiler_stmt.go b/compiler_stmt.go index 597f06fb..cb244a9e 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -779,36 +779,7 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { } else if expr.LexicalDeclaration != nil { c.compileLexicalDeclaration(expr.LexicalDeclaration) } else if expr.HoistableDeclaration != nil { - h := expr.HoistableDeclaration - if h.FunctionLiteral != nil { - if !expr.IsDefault { - panic("non default function literal export") - } - // TODO fix this - this was the easiest way to bound the default to something - c.compileLexicalDeclaration(&ast.LexicalDeclaration{ - Idx: h.FunctionLiteral.Idx0(), - Token: token.CONST, - List: []*ast.Binding{ - { - Target: &ast.Identifier{ - Name: unistring.String("default"), - Idx: h.FunctionLiteral.Idx0(), - }, - Initializer: h.FunctionLiteral, - }, - }, - }) - // r.emitGetter(true) - // r.markAccessPoint() - - /* - b, _ := c.scope.lookupName(h.FunctionDeclaration.Function.Name.Name) - b.markAccessPoint() - c.emit(exportPrevious{callback: func(getter func() Value) { - module.exportGetters[h.FunctionDeclaration.Function.Name.Name] = getter - }}) - */ - } + // already done } else if assign := expr.AssignExpression; assign != nil { c.compileLexicalDeclaration(&ast.LexicalDeclaration{ Idx: assign.Idx0(), diff --git a/parser/statement.go b/parser/statement.go index 0cfa3020..65fba829 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -882,9 +882,16 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { switch self.token { case token.FUNCTION: + f := self.parseFunction(false) + if f.Name == nil { + f.Name = &ast.Identifier{Name: unistring.String("default"), Idx: f.Idx0()} + } exp = &ast.ExportDeclaration{ HoistableDeclaration: &ast.HoistableDeclaration{ - FunctionLiteral: self.parseFunction(false), + FunctionDeclaration: &ast.FunctionDeclaration{ + Function: f, + IsDefault: true, + }, }, IsDefault: true, } From 9eee9ed9575bc9b26289b581a8fca4dc0301e405 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 17 Jun 2022 19:22:26 +0300 Subject: [PATCH 052/124] disable tests with generators --- tc39_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tc39_test.go b/tc39_test.go index e376cfe7..95c67bca 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -173,6 +173,8 @@ var ( "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-generator-function-declaration.js": true, "test/built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js": true, "test/built-ins/Object/seal/seal-generatorfunction.js": true, + "test/language/module-code/instn-local-bndng-gen.js": true, + "test/language/module-code/instn-local-bndng-export-gen.js": true, // async "test/language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": true, From 69f3638075a6d0b050df47e41d8b974205547906 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sat, 18 Jun 2022 10:04:40 +0300 Subject: [PATCH 053/124] Report duplicate import names as syntax error --- module.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/module.go b/module.go index 9ab6e6fa..ff90489c 100644 --- a/module.go +++ b/module.go @@ -311,8 +311,9 @@ type exportEntry struct { lex bool } -func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { +func importEntriesFromAst(declarations []*ast.ImportDeclaration) ([]importEntry, error) { var result []importEntry + names := make(map[string]struct{}, len(declarations)) for _, importDeclarion := range declarations { importClause := importDeclarion.ImportClause if importDeclarion.FromClause == nil { @@ -325,6 +326,10 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { if localName == "" { localName = el.IdentifierName.String() } + if _, ok := names[localName]; ok { + return nil, fmt.Errorf("duplicate bounded name %s", localName) + } + names[localName] = struct{}{} result = append(result, importEntry{ moduleRequest: moduleRequest, importName: el.IdentifierName.String(), @@ -334,14 +339,24 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { } } if def := importClause.ImportedDefaultBinding; def != nil { + localName := def.Name.String() + if _, ok := names[localName]; ok { + return nil, fmt.Errorf("duplicate bounded name %s", localName) + } + names[localName] = struct{}{} result = append(result, importEntry{ moduleRequest: moduleRequest, importName: "default", - localName: def.Name.String(), + localName: localName, offset: int(importDeclarion.Idx0()), }) } if namespace := importClause.NameSpaceImport; namespace != nil { + localName := namespace.ImportedBinding.String() + if _, ok := names[localName]; ok { + return nil, fmt.Errorf("duplicate bounded name %s", localName) + } + names[localName] = struct{}{} result = append(result, importEntry{ moduleRequest: moduleRequest, importName: "*", @@ -350,7 +365,7 @@ func importEntriesFromAst(declarations []*ast.ImportDeclaration) []importEntry { }) } } - return result + return result, nil } func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { @@ -506,7 +521,13 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul func ModuleFromAST(name string, body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { requestedModules := requestedModulesFromAst(body.ImportEntries, body.ExportEntries) - importEntries := importEntriesFromAst(body.ImportEntries) + importEntries, err := importEntriesFromAst(body.ImportEntries) + if err != nil { + // TODO create a separate error type + return nil, &CompilerSyntaxError{CompilerError: CompilerError{ + Message: err.Error(), + }} + } // 6. Let importedBoundNames be ImportedLocalNames(importEntries). // ^ is skipped as we don't need it. From b4df0433b3d0c4497e1cbfa833ccb157d3738ffe Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sun, 19 Jun 2022 12:46:14 +0300 Subject: [PATCH 054/124] Don't print each Link error --- module.go | 1 - 1 file changed, 1 deletion(-) diff --git a/module.go b/module.go index ff90489c..d922c881 100644 --- a/module.go +++ b/module.go @@ -63,7 +63,6 @@ func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) err stack := []CyclicModuleRecord{} if _, err := c.innerModuleLinking(module, &stack, 0); err != nil { - fmt.Println(err) for _, m := range stack { /* if m.Status() != Linking { From 50f5a3c351cd4a823ac6d41389012dcc6b18ddd3 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 23 Jun 2022 11:36:13 +0300 Subject: [PATCH 055/124] WIP namespace support --- compiler.go | 39 ++++++++++++--------------- compiler_stmt.go | 13 +++++++-- module.go | 69 +++++++++++++++++++++++++++++++++++++++--------- vm.go | 13 ++++++++- 4 files changed, 97 insertions(+), 37 deletions(-) diff --git a/compiler.go b/compiler.go index 2e406902..496820c5 100644 --- a/compiler.go +++ b/compiler.go @@ -738,13 +738,9 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { scope.function = true } for _, in := range module.indirectExportEntries { - importedModule, err := c.hostResolveImportedModule(module, in.moduleRequest) - if err != nil { - panic(fmt.Errorf("previously resolved module returned error %w", err)) - } - resolution, ambiguous := importedModule.ResolveExport(in.importName) - if resolution == nil || ambiguous { - c.compileAmbiguousImport(unistring.String(in.importName)) + v, ambiguous := module.ResolveExport(in.exportName) + if v == nil || ambiguous { + c.compileAmbiguousImport(unistring.NewFromString(in.importName)) } } @@ -757,22 +753,16 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { panic(fmt.Errorf("previously resolved module returned error %w", err)) } if in.importName == "*" { + b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) + b.inStash = true } else { resolution, ambiguous := importedModule.ResolveExport(in.importName) if resolution == nil || ambiguous { - c.compileAmbiguousImport(unistring.String(in.importName)) + c.compileAmbiguousImport(unistring.NewFromString(in.importName)) continue } - if resolution.BindingName == "*namespace*" { - } else { - // c.createImportBinding(in.localName, resolution.Module, resolution.BindingName) - c.createImmutableBinding(unistring.String(in.localName), true) - } - } - } - for _, exp := range in.Body { - if imp, ok := exp.(*ast.ImportDeclaration); ok { - c.compileImportDeclaration(imp) + b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) + b.inStash = true } } // 15.2.1.17.4 step 9 end @@ -816,8 +806,13 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { } } + for _, exp := range in.Body { + if imp, ok := exp.(*ast.ImportDeclaration); ok { + c.compileImportDeclaration(imp) + } + } for _, entry := range module.localExportEntries { - name := unistring.String(entry.localName) + name := unistring.NewFromString(entry.localName) b, ok := scope.boundNames[name] if !ok { if entry.localName != "default" { @@ -830,7 +825,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { b.inStash = true b.markAccessPoint() - exportName := unistring.String(entry.exportName) + exportName := unistring.NewFromString(entry.exportName) callback := func(vm *vm, getter func() Value) { m := vm.r.modules[module] @@ -851,8 +846,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if err != nil { panic(err) // this should not be possible } - exportName := unistring.String(entry.exportName) - importName := unistring.String(entry.importName) + exportName := unistring.NewFromString(entry.exportName) + importName := unistring.NewFromString(entry.importName) c.emit(exportIndirect{callback: func(vm *vm) { m := vm.r.modules[module] m2 := vm.r.modules[otherModule] diff --git a/compiler_stmt.go b/compiler_stmt.go index cb244a9e..83eda16b 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -52,7 +52,7 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) { c.compileWithStatement(v, needResult) case *ast.DebuggerStatement: case *ast.ImportDeclaration: - c.compileImportDeclaration(v) + // c.compileImportDeclaration(v) case *ast.ExportDeclaration: c.compileExportDeclaration(v) default: @@ -845,7 +845,16 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { } if expr.ImportClause != nil { if namespace := expr.ImportClause.NameSpaceImport; namespace != nil { - c.throwSyntaxError(int(expr.Idx0()), "namespace imports not implemented") + idx := expr.Idx // TODO fix + c.emitLexicalAssign( + namespace.ImportedBinding, + int(idx), + c.compileEmitterExpr(func() { + c.emit(importNamespace{ + module: module, + }) + }, idx), + ) } if named := expr.ImportClause.NamedImports; named != nil { for _, name := range named.ImportsList { diff --git a/module.go b/module.go index d922c881..17376338 100644 --- a/module.go +++ b/module.go @@ -478,18 +478,20 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { return result } -func requestedModulesFromAst(imports []*ast.ImportDeclaration, exports []*ast.ExportDeclaration) []string { +func requestedModulesFromAst(statements []ast.Statement) []string { var result []string - for _, imp := range imports { - if imp.FromClause != nil { - result = append(result, imp.FromClause.ModuleSpecifier.String()) - } else { - result = append(result, imp.ModuleSpecifier.String()) - } - } - for _, exp := range exports { - if exp.FromClause != nil { - result = append(result, exp.FromClause.ModuleSpecifier.String()) + for _, st := range statements { + switch imp := st.(type) { + case *ast.ImportDeclaration: + if imp.FromClause != nil { + result = append(result, imp.FromClause.ModuleSpecifier.String()) + } else { + result = append(result, imp.ModuleSpecifier.String()) + } + case *ast.ExportDeclaration: + if imp.FromClause != nil { + result = append(result, imp.FromClause.ModuleSpecifier.String()) + } } } return result @@ -519,7 +521,7 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul } func ModuleFromAST(name string, body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { - requestedModules := requestedModulesFromAst(body.ImportEntries, body.ExportEntries) + requestedModules := requestedModulesFromAst(body.Body) importEntries, err := importEntriesFromAst(body.ImportEntries) if err != nil { // TODO create a separate error type @@ -852,3 +854,46 @@ func (c *cyclicModuleStub) SetNamespace(namespace *Namespace) { c.namespace = namespace } */ +type namespaceObject struct { + baseObject + m ModuleRecord + mi ModuleInstance +} + +func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { + o := &Object{runtime: r} + no := &namespaceObject{} + no.val = o + o.self = no + no.extensible = true + no.init() + + for _, exportName := range m.GetExportedNames() { + v, ambiguous := m.ResolveExport(exportName) + if ambiguous || v == nil { + continue + } + mi := r.modules[v.Module] + + b, ok := mi.GetBindingValue(unistring.NewFromString(exportName), true) + if !ok { + r.throwReferenceError(unistring.NewFromString(exportName)) + } + + no.baseObject.setOwnStr(unistring.NewFromString(exportName), b, true) + } + no.extensible = false + return no +} + +func (no *namespaceObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false +} + +func (no *namespaceObject) deleteStr(name unistring.String, throw bool) bool { + if _, exists := no.values[name]; exists { + no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + } + return false +} diff --git a/vm.go b/vm.go index d736bf7a..164572b7 100644 --- a/vm.go +++ b/vm.go @@ -405,7 +405,8 @@ func (vm *vm) run() { if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { break } - // fmt.Printf("Executing %#v\n", vm.prg.code[vm.pc]) + // inst := vm.prg.code[vm.pc] + // fmt.Printf("Executing %T %#v\n", inst, inst) vm.prg.code[vm.pc].exec(vm) ticks++ if ticks > 10000 { @@ -852,6 +853,16 @@ func (s initStack1) exec(vm *vm) { vm.sp-- } +type importNamespace struct { + module ModuleRecord +} + +func (i importNamespace) exec(vm *vm) { + namespace := vm.r.createNamespaceObject(i.module) + vm.push(namespace.val) + vm.pc++ +} + type export struct { idx uint32 callback func(*vm, func() Value) From 67ce71711d6baf85a511bbb4067316d65ee7eed3 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 23 Jun 2022 11:45:53 +0300 Subject: [PATCH 056/124] Disable one more test as it includes generators --- tc39_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tc39_test.go b/tc39_test.go index 95c67bca..85e55c47 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -175,6 +175,7 @@ var ( "test/built-ins/Object/seal/seal-generatorfunction.js": true, "test/language/module-code/instn-local-bndng-gen.js": true, "test/language/module-code/instn-local-bndng-export-gen.js": true, + "test/language/module-code/instn-star-props-nrml.js": true, // async "test/language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": true, From e2a6d0f0c620509077ed33e92d5dd1efa1a0bab5 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 23 Jun 2022 16:06:45 +0300 Subject: [PATCH 057/124] fix all namespace tests currently enabled --- compiler.go | 16 +++++++++--- compiler_stmt.go | 24 +++++++++++++---- module.go | 68 ++++++++++++++++++++++++++++++++++++++---------- vm.go | 3 ++- 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/compiler.go b/compiler.go index 496820c5..cede9fc4 100644 --- a/compiler.go +++ b/compiler.go @@ -825,7 +825,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { b.inStash = true b.markAccessPoint() - exportName := unistring.NewFromString(entry.exportName) + exportName := unistring.NewFromString(entry.localName) callback := func(vm *vm, getter func() Value) { m := vm.r.modules[module] @@ -846,11 +846,21 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if err != nil { panic(err) // this should not be possible } + if entry.importName == "*" { + continue + } + b, ambiguous := otherModule.ResolveExport(entry.importName) + if ambiguous || b == nil { + // fmt.Printf("entry %#v , module: %#v\n", entry, otherModule) + c.compileAmbiguousImport(unistring.NewFromString(entry.importName)) + continue + } + exportName := unistring.NewFromString(entry.exportName) - importName := unistring.NewFromString(entry.importName) + importName := unistring.NewFromString(b.BindingName) c.emit(exportIndirect{callback: func(vm *vm) { m := vm.r.modules[module] - m2 := vm.r.modules[otherModule] + m2 := vm.r.modules[b.Module] if s, ok := m.(*SourceTextModuleInstance); !ok { vm.r.throwReferenceError(exportName) // TODO fix diff --git a/compiler_stmt.go b/compiler_stmt.go index 83eda16b..b6e02738 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -863,20 +863,34 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if ambiguous || value == nil { // also ambiguous continue // ambiguous import already reports } - n := name.Alias if n.String() == "" { n = name.IdentifierName } + if value.BindingName == "*namespace*" { + idx := expr.Idx // TODO fix + c.emitLexicalAssign( + n, + int(idx), + c.compileEmitterExpr(func() { + c.emit(importNamespace{ + module: value.Module, + }) + }, idx), + ) + continue + } + c.checkIdentifierLName(n, int(expr.Idx)) localB, _ := c.scope.lookupName(n) if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } - identifier := name.IdentifierName // this + identifier := unistring.NewFromString(value.BindingName) // moduleName := expr.FromClause.ModuleSpecifier.String() + localB.getIndirect = func(vm *vm) Value { - m := vm.r.modules[module] + m := vm.r.modules[value.Module] v, ok := m.GetBindingValue(identifier, true) if !ok { vm.r.throwReferenceError(identifier) @@ -898,10 +912,10 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) } // moduleName := expr.FromClause.ModuleSpecifier.String() - identifier := unistring.String("default") + identifier := unistring.NewFromString(value.BindingName) localB.getIndirect = func(vm *vm) Value { // TODO this should be just "default", this also likely doesn't work for export aliasing - m := vm.r.modules[module] + m := vm.r.modules[value.Module] v, ok := m.GetBindingValue(identifier, true) if !ok { vm.r.throwReferenceError(identifier) diff --git a/module.go b/module.go index 17376338..1f8bd572 100644 --- a/module.go +++ b/module.go @@ -238,6 +238,7 @@ type ( ModuleInstance interface { // Evaluate(rt *Runtime) (ModuleInstance, error) GetBindingValue(unistring.String, bool) (Value, bool) + Namespace(*Runtime) *namespaceObject // export the type } CyclicModuleInstance interface { ModuleInstance @@ -263,6 +264,7 @@ type SourceTextModuleInstance struct { moduleRecord *SourceTextModuleRecord // TODO figure out omething less idiotic exportGetters map[unistring.String]func() Value + namespace *namespaceObject } func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, error) { @@ -278,6 +280,13 @@ func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool return getter(), true } +func (s *SourceTextModuleInstance) Namespace(rt *Runtime) *namespaceObject { + if s.namespace == nil { + s.namespace = rt.createNamespaceObject(s.moduleRecord) // TODO just use the instance :shrug: + } + return s.namespace +} + type SourceTextModuleRecord struct { cyclicModuleStub name string // TODO remove this :crossed_fingers: @@ -715,6 +724,7 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese if exportName == "" { panic("wat") } + // fmt.Println("in", exportName, resolveset) for _, r := range resolveset { if r.Module == module && exportName == r.ExportName { // TODO better return nil, false @@ -761,6 +771,7 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese panic(err) // TODO return err } resolution, ambiguous := importedModule.ResolveExport(exportName, resolveset...) + // fmt.Println(resolution, ambiguous, importedModule) if ambiguous { return nil, true } @@ -856,34 +867,63 @@ func (c *cyclicModuleStub) SetNamespace(namespace *Namespace) { */ type namespaceObject struct { baseObject - m ModuleRecord - mi ModuleInstance + m ModuleRecord + exports map[unistring.String]struct{} } func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { o := &Object{runtime: r} - no := &namespaceObject{} + no := &namespaceObject{m: m} no.val = o o.self = no - no.extensible = true no.init() + no.exports = make(map[unistring.String]struct{}) for _, exportName := range m.GetExportedNames() { - v, ambiguous := m.ResolveExport(exportName) + v, ambiguous := no.m.ResolveExport(exportName) if ambiguous || v == nil { continue } - mi := r.modules[v.Module] + no.exports[unistring.NewFromString(exportName)] = struct{}{} + } + return no +} - b, ok := mi.GetBindingValue(unistring.NewFromString(exportName), true) - if !ok { - r.throwReferenceError(unistring.NewFromString(exportName)) - } +func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { + // fmt.Println(exportName) + if _, ok := no.exports[name]; !ok { + return _undefined + } + v, ambiguous := no.m.ResolveExport(name.String()) + if ambiguous || v == nil { + no.val.runtime.throwReferenceError((name)) + } + mi := no.val.runtime.modules[v.Module] + // fmt.Println(v.Module) + // fmt.Println(v.BindingName) - no.baseObject.setOwnStr(unistring.NewFromString(exportName), b, true) + b, ok := mi.GetBindingValue(unistring.NewFromString(v.BindingName), true) + if !ok { + no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) } - no.extensible = false - return no + + return b +} + +func (no *namespaceObject) hasPropertyStr(name unistring.String) bool { + _, ok := no.exports[name] + return ok +} + +func (o *namespaceObject) getStr(name unistring.String, receiver Value) Value { + prop := o.getOwnPropStr(name) + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) + } + return prop } func (no *namespaceObject) setOwnStr(name unistring.String, val Value, throw bool) bool { @@ -892,7 +932,7 @@ func (no *namespaceObject) setOwnStr(name unistring.String, val Value, throw boo } func (no *namespaceObject) deleteStr(name unistring.String, throw bool) bool { - if _, exists := no.values[name]; exists { + if _, exists := no.exports[name]; exists { no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) } return false diff --git a/vm.go b/vm.go index 164572b7..3ea576cd 100644 --- a/vm.go +++ b/vm.go @@ -858,7 +858,8 @@ type importNamespace struct { } func (i importNamespace) exec(vm *vm) { - namespace := vm.r.createNamespaceObject(i.module) + mi := vm.r.modules[i.module] + namespace := mi.Namespace(vm.r) vm.push(namespace.val) vm.pc++ } From 298427a6b57da889b81183822054190ba3be1d29 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 14:39:27 +0300 Subject: [PATCH 058/124] Enable more tests and fix this in module code --- compiler.go | 2 -- compiler_expr.go | 10 ++++++++-- tc39_test.go | 7 ------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler.go b/compiler.go index cede9fc4..d2f8efee 100644 --- a/compiler.go +++ b/compiler.go @@ -272,8 +272,6 @@ type scope struct { argsNeeded bool // 'this' is used and non-strict, so need to box it (functions only) thisNeeded bool - // is module - // module *SourceTextModuleRecord } type block struct { diff --git a/compiler_expr.go b/compiler_expr.go index 94c5939e..4b0468c4 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1341,8 +1341,14 @@ func (e *compiledThisExpr) emitGetter(putOnStack bool) { } if scope != nil { - scope.thisNeeded = true - e.c.emit(loadStack(0)) + if e.c.module != nil && scope.outer != nil && scope.outer.outer == nil { // modules don't have this defined + // TODO maybe just add isTopLevel and rewrite the rest of the code + e.c.emit(_loadUndef{}) + } else { + + scope.thisNeeded = true + e.c.emit(loadStack(0)) + } } else { e.c.emit(loadGlobalObject) } diff --git a/tc39_test.go b/tc39_test.go index 85e55c47..0d7593df 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -226,11 +226,6 @@ var ( // Left-hand side as a CoverParenthesizedExpression "test/language/expressions/assignment/fn-name-lhs-cover.js": true, - - // eval this panics in modules - "test/language/module-code/eval-this.js": true, - // star and default leads to panic - "test/language/module-code/export-star-as-dflt.js": true, } featuresBlackList = []string{ @@ -308,8 +303,6 @@ func init() { // BigInt "test/built-ins/TypedArrayConstructors/BigUint64Array/", "test/built-ins/TypedArrayConstructors/BigInt64Array/", - // module namespace TODO remove - "test/language/module-code/namespace/", ) } From 0d601bf25ed096b85e3a3fd8721a2f90e96d2ab9 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 15:24:00 +0300 Subject: [PATCH 059/124] Fix namespace and default export combo --- compiler_stmt.go | 31 ++++++++++++++++++++++--------- module.go | 3 +++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/compiler_stmt.go b/compiler_stmt.go index b6e02738..be702733 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -911,16 +911,29 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) } - // moduleName := expr.FromClause.ModuleSpecifier.String() - identifier := unistring.NewFromString(value.BindingName) - localB.getIndirect = func(vm *vm) Value { - // TODO this should be just "default", this also likely doesn't work for export aliasing - m := vm.r.modules[value.Module] - v, ok := m.GetBindingValue(identifier, true) - if !ok { - vm.r.throwReferenceError(identifier) + if value.BindingName == "*namespace*" { + idx := expr.Idx // TODO fix + c.emitLexicalAssign( + def.Name, + int(idx), + c.compileEmitterExpr(func() { + c.emit(importNamespace{ + module: value.Module, + }) + }, idx), + ) + } else { + // moduleName := expr.FromClause.ModuleSpecifier.String() + identifier := unistring.NewFromString(value.BindingName) + localB.getIndirect = func(vm *vm) Value { + // TODO this should be just "default", this also likely doesn't work for export aliasing + m := vm.r.modules[value.Module] + v, ok := m.GetBindingValue(identifier, true) + if !ok { + vm.r.throwReferenceError(identifier) + } + return v } - return v } } } diff --git a/module.go b/module.go index 1f8bd572..0b8cd317 100644 --- a/module.go +++ b/module.go @@ -899,6 +899,9 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { no.val.runtime.throwReferenceError((name)) } mi := no.val.runtime.modules[v.Module] + if v.BindingName == "*namespace*" { + return mi.Namespace(no.val.runtime).val + } // fmt.Println(v.Module) // fmt.Println(v.BindingName) From 2a1776028d2583b387f5eb4b79c9a3075ca5fa6c Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 15:28:56 +0300 Subject: [PATCH 060/124] Fix Object.keys for namespace --- module.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/module.go b/module.go index 0b8cd317..9171ae64 100644 --- a/module.go +++ b/module.go @@ -889,8 +889,15 @@ func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { return no } +func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { + for name := range no.exports { + _ = no.getOwnPropStr(name) + accum = append(accum, stringValueFromRaw(name)) + } + return accum +} + func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { - // fmt.Println(exportName) if _, ok := no.exports[name]; !ok { return _undefined } From ffbc5560a93687d75e4105d0df566f1f1528018f Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 15:29:13 +0300 Subject: [PATCH 061/124] Fix namespace @toStrinTag --- module.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/module.go b/module.go index 9171ae64..316d3a9e 100644 --- a/module.go +++ b/module.go @@ -875,6 +875,11 @@ func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { o := &Object{runtime: r} no := &namespaceObject{m: m} no.val = o + no.extensible = true + no.defineOwnPropertySym(SymToStringTag, PropertyDescriptor{ + Value: newStringValue("Module"), + }, true) + no.extensible = false o.self = no no.init() no.exports = make(map[unistring.String]struct{}) From cc0f1face099acc9223da09db41f26de24974bc0 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 15:39:57 +0300 Subject: [PATCH 062/124] Dump exception stack on error in tc39 test --- tc39_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tc39_test.go b/tc39_test.go index 0d7593df..642fe341 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -482,7 +482,12 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. t.Skip("Test threw IgnorableTestError") } } - t.Fatalf("%s: %v", name, err) + var exc = new(Exception) + if errors.As(err, &exc) { + t.Fatalf("%s: %v", name, exc.String()) + } else { + t.Fatalf("%s: %v", name, err) + } } else { if (meta.Negative.Phase == "early" || meta.Negative.Phase == "parse") && !early || meta.Negative.Phase == "runtime" && early { t.Fatalf("%s: error %v happened at the wrong phase (expected %s)", name, err, meta.Negative.Phase) From 19c97051bd52a3ed53ebe63878c95e3efba0463b Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 17:05:25 +0300 Subject: [PATCH 063/124] Fix getOwnPropertyDesc on namespaces --- module.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/module.go b/module.go index 316d3a9e..bae7a312 100644 --- a/module.go +++ b/module.go @@ -904,7 +904,7 @@ func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { if _, ok := no.exports[name]; !ok { - return _undefined + return nil } v, ambiguous := no.m.ResolveExport(name.String()) if ambiguous || v == nil { @@ -914,9 +914,6 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { if v.BindingName == "*namespace*" { return mi.Namespace(no.val.runtime).val } - // fmt.Println(v.Module) - // fmt.Println(v.BindingName) - b, ok := mi.GetBindingValue(unistring.NewFromString(v.BindingName), true) if !ok { no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) @@ -925,8 +922,9 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { return b } -func (no *namespaceObject) hasPropertyStr(name unistring.String) bool { +func (no *namespaceObject) hasOwnPropertyStr(name unistring.String) bool { _, ok := no.exports[name] + no.getOwnPropStr(name) return ok } From 5df29559e492f13bdd9f64b2da0719997b97a492 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 27 Jun 2022 17:05:35 +0300 Subject: [PATCH 064/124] Disable test using generators --- tc39_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tc39_test.go b/tc39_test.go index 642fe341..9f033885 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -176,6 +176,7 @@ var ( "test/language/module-code/instn-local-bndng-gen.js": true, "test/language/module-code/instn-local-bndng-export-gen.js": true, "test/language/module-code/instn-star-props-nrml.js": true, + "test/language/module-code/namespace/internals/get-nested-namespace-props-nrml.js": true, // async "test/language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": true, From ca6a0567ff2b184d5b133bf161d4c4004e69d742 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 4 Jul 2022 14:37:14 +0300 Subject: [PATCH 065/124] Sort Namespace keys to fix 2 tests --- module.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/module.go b/module.go index bae7a312..e60ce2ac 100644 --- a/module.go +++ b/module.go @@ -899,6 +899,10 @@ func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { _ = no.getOwnPropStr(name) accum = append(accum, stringValueFromRaw(name)) } + // TODO optimize thsi + sort.Slice(accum, func(i, j int) bool { + return accum[i].String() < accum[j].String() + }) return accum } @@ -912,14 +916,24 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { } mi := no.val.runtime.modules[v.Module] if v.BindingName == "*namespace*" { - return mi.Namespace(no.val.runtime).val + return &valueProperty{ + value: mi.Namespace(no.val.runtime).val, + writable: true, + configurable: false, + enumerable: true, + } } b, ok := mi.GetBindingValue(unistring.NewFromString(v.BindingName), true) if !ok { no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) } - return b + return &valueProperty{ + value: b, + writable: true, + configurable: false, + enumerable: true, + } } func (no *namespaceObject) hasOwnPropertyStr(name unistring.String) bool { From a8cca031a518c75ef5181544fa605f688c9a9075 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 11:10:35 +0300 Subject: [PATCH 066/124] Fix a bunch of namespace tc39 tests --- compiler.go | 4 +-- module.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/compiler.go b/compiler.go index d2f8efee..4eac02d7 100644 --- a/compiler.go +++ b/compiler.go @@ -824,6 +824,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { b.markAccessPoint() exportName := unistring.NewFromString(entry.localName) + lex := entry.lex || !scope.boundNames[exportName].isVar callback := func(vm *vm, getter func() Value) { m := vm.r.modules[module] @@ -833,7 +834,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { s.exportGetters[exportName] = getter } } - if entry.lex { + if lex { c.emit(exportLex{callback: callback}) } else { c.emit(export{callback: callback}) @@ -849,7 +850,6 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { } b, ambiguous := otherModule.ResolveExport(entry.importName) if ambiguous || b == nil { - // fmt.Printf("entry %#v , module: %#v\n", entry, otherModule) c.compileAmbiguousImport(unistring.NewFromString(entry.importName)) continue } diff --git a/module.go b/module.go index e60ce2ac..7e4fd897 100644 --- a/module.go +++ b/module.go @@ -1,6 +1,7 @@ package goja import ( + "bytes" "errors" "fmt" "sort" @@ -724,7 +725,6 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese if exportName == "" { panic("wat") } - // fmt.Println("in", exportName, resolveset) for _, r := range resolveset { if r.Module == module && exportName == r.ExportName { // TODO better return nil, false @@ -771,7 +771,6 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese panic(err) // TODO return err } resolution, ambiguous := importedModule.ResolveExport(exportName, resolveset...) - // fmt.Println(resolution, ambiguous, importedModule) if ambiguous { return nil, true } @@ -867,8 +866,9 @@ func (c *cyclicModuleStub) SetNamespace(namespace *Namespace) { */ type namespaceObject struct { baseObject - m ModuleRecord - exports map[unistring.String]struct{} + m ModuleRecord + exports map[unistring.String]struct{} + exportsNames []unistring.String } func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { @@ -890,13 +890,16 @@ func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { continue } no.exports[unistring.NewFromString(exportName)] = struct{}{} + no.exportsNames = append(no.exportsNames, unistring.NewFromString(exportName)) } return no } func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { for name := range no.exports { - _ = no.getOwnPropStr(name) + if !all { // TODO this seems off + _ = no.getOwnPropStr(name) + } accum = append(accum, stringValueFromRaw(name)) } // TODO optimize thsi @@ -906,6 +909,35 @@ func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { return accum } +type namespacePropIter struct { + no *namespaceObject + idx int +} + +func (no *namespaceObject) iterateStringKeys() iterNextFunc { + return (&namespacePropIter{ + no: no, + }).next +} + +func (no *namespaceObject) iterateKeys() iterNextFunc { + return (&namespacePropIter{ + no: no, + }).next +} + +func (i *namespacePropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.no.exportsNames) { + name := i.no.exportsNames[i.idx] + i.idx++ + prop := i.no.getOwnPropStr(name) + if prop != nil { + return propIterItem{name: stringValueFromRaw(name), value: prop}, i.next + } + } + return propIterItem{}, nil +} + func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { if _, ok := no.exports[name]; !ok { return nil @@ -921,15 +953,27 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { writable: true, configurable: false, enumerable: true, + /* + accessor: true, + getterFunc: propGetter(no.val, no.val.runtime.ToValue(func() Value { + return + }), no.val.runtime), + */ } } + b, ok := mi.GetBindingValue(unistring.NewFromString(v.BindingName), true) if !ok { no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) } - return &valueProperty{ - value: b, + value: b, + /* + accessor: true, + getterFunc: no.val.runtime.newNativeFunc(func(FunctionCall) Value { + return b + }, nil, "", nil, 0), + */ writable: true, configurable: false, enumerable: true, @@ -938,7 +982,6 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { func (no *namespaceObject) hasOwnPropertyStr(name unistring.String) bool { _, ok := no.exports[name] - no.getOwnPropStr(name) return ok } @@ -961,6 +1004,41 @@ func (no *namespaceObject) setOwnStr(name unistring.String, val Value, throw boo func (no *namespaceObject) deleteStr(name unistring.String, throw bool) bool { if _, exists := no.exports[name]; exists { no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false } - return false + return true +} + +func (no *namespaceObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + returnFalse := func() bool { + var buf bytes.Buffer + for _, stack := range no.val.runtime.CaptureCallStack(0, nil) { + stack.Write(&buf) + buf.WriteRune('\n') + } + if throw { + no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + } + return false + } + if !no.hasOwnPropertyStr(name) { + return returnFalse() + } + if desc.Empty() { + return true + } + if desc.Writable == FLAG_FALSE { + return returnFalse() + } + if desc.Configurable == FLAG_TRUE { + return returnFalse() + } + if desc.Enumerable == FLAG_FALSE { + return returnFalse() + } + if desc.Value != nil && desc.Value != no.getOwnPropStr(name) { + return returnFalse() + } + // TODO more checks + return true } From ae9681cf8ac6302c6ed9cbc78fd0f4d559d833b6 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 11:10:55 +0300 Subject: [PATCH 067/124] Fix Object.hasOwnProperty - fixing two namespace tc39 tests --- object.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/object.go b/object.go index d3fc45f6..efe11a14 100644 --- a/object.go +++ b/object.go @@ -1440,14 +1440,8 @@ func (o *Object) getOwnProp(p Value) Value { } func (o *Object) hasOwnProperty(p Value) bool { - switch p := p.(type) { - case valueInt: - return o.self.hasOwnPropertyIdx(p) - case *Symbol: - return o.self.hasOwnPropertySym(p) - default: - return o.self.hasOwnPropertyStr(p.string()) - } + // https: // 262.ecma-international.org/12.0/#sec-hasownproperty + return o.getOwnProp(p) != nil } func (o *Object) hasProperty(p Value) bool { From 82e50707a8b9357d69de5542a5ebdae240adb9ae Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 11:38:13 +0300 Subject: [PATCH 068/124] Fix final failing tc39 test and 'fix' Object.freeze --- builtin_object.go | 23 ++++++++--------------- module.go | 3 +++ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/builtin_object.go b/builtin_object.go index 99c7724f..523e2e6d 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -279,23 +279,16 @@ func (r *Runtime) object_freeze(call FunctionCall) Value { obj.self.preventExtensions(true) for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() { - if prop, ok := item.value.(*valueProperty); ok { - prop.configurable = false - if !prop.accessor { - prop.writable = false - } + prop := obj.getOwnProp(item.name) + descr := PropertyDescriptor{ + Configurable: FLAG_FALSE, + } + if prop, ok := prop.(*valueProperty); ok && prop.accessor { + // no-op } else { - prop := obj.getOwnProp(item.name) - descr := PropertyDescriptor{ - Configurable: FLAG_FALSE, - } - if prop, ok := prop.(*valueProperty); ok && prop.accessor { - // no-op - } else { - descr.Writable = FLAG_FALSE - } - obj.defineOwnProperty(item.name, descr, true) + descr.Writable = FLAG_FALSE } + obj.defineOwnProperty(item.name, descr, true) } return obj } else { diff --git a/module.go b/module.go index 7e4fd897..f1dfb278 100644 --- a/module.go +++ b/module.go @@ -966,6 +966,9 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { if !ok { no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) } + if b == nil { + b = _null + } return &valueProperty{ value: b, /* From 0e6487119f85b3120df0c8a513f1a16825052722 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 11:49:17 +0300 Subject: [PATCH 069/124] Fix parser tests --- parser/parser_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/parser/parser_test.go b/parser/parser_test.go index 28785abc..98a37ff3 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -428,17 +428,17 @@ func TestParserErr(t *testing.T) { test("abc.enum = 1", nil) test("var enum;", "(anonymous): Line 1:5 Unexpected reserved word") - test("export", "(anonymous): Line 1:1 Unexpected reserved word") + test("export", "(anonymous): Line 1:7 export not supported in script") test("abc.export = 1", nil) - test("var export;", "(anonymous): Line 1:5 Unexpected reserved word") + test("var export;", "(anonymous): Line 1:5 Unexpected token export") test("extends", "(anonymous): Line 1:1 Unexpected reserved word") test("abc.extends = 1", nil) test("var extends;", "(anonymous): Line 1:5 Unexpected reserved word") - test("import", "(anonymous): Line 1:1 Unexpected reserved word") + test("import", "(anonymous): Line 1:7 import not supported in script") test("abc.import = 1", nil) - test("var import;", "(anonymous): Line 1:5 Unexpected reserved word") + test("var import;", "(anonymous): Line 1:5 Unexpected token import") test("super", "(anonymous): Line 1:1 Unexpected reserved word") test("abc.super = 1", nil) @@ -453,7 +453,7 @@ func TestParserErr(t *testing.T) { test("{a: 1,}", "(anonymous): Line 1:7 Unexpected token }") test("{a: 1, b: 2}", "(anonymous): Line 1:9 Unexpected token :") test("{a: 1, b: 2,}", "(anonymous): Line 1:9 Unexpected token :") - test(`let f = () => new import('');`, "(anonymous): Line 1:19 Unexpected reserved word") + test(`let f = () => new import('');`, "(anonymous): Line 1:19 Unexpected token import") } From cb598914afc22bd5f66100355ba8b463007e19a6 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 11:51:22 +0300 Subject: [PATCH 070/124] staticcheck fix --- module.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module.go b/module.go index f1dfb278..b91bcf9a 100644 --- a/module.go +++ b/module.go @@ -988,11 +988,11 @@ func (no *namespaceObject) hasOwnPropertyStr(name unistring.String) bool { return ok } -func (o *namespaceObject) getStr(name unistring.String, receiver Value) Value { - prop := o.getOwnPropStr(name) +func (no *namespaceObject) getStr(name unistring.String, receiver Value) Value { + prop := no.getOwnPropStr(name) if prop, ok := prop.(*valueProperty); ok { if receiver == nil { - return prop.get(o.val) + return prop.get(no.val) } return prop.get(receiver) } From c60a76bf09e13bb9733bcf633cd6972f4ae1752a Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:22:47 +0300 Subject: [PATCH 071/124] Cleaning up module.go --- module.go | 78 +++++++------------------------------------------------ 1 file changed, 9 insertions(+), 69 deletions(-) diff --git a/module.go b/module.go index b91bcf9a..8c159d08 100644 --- a/module.go +++ b/module.go @@ -137,14 +137,14 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor return index, nil } -func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string, resolve HostResolveImportedModuleFunc, +func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, err error) { // TODO asserts if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) } stackInstance := []CyclicModuleInstance{} - if mi, _, err = r.innerModuleEvaluation(c, &stackInstance, 0, name, resolve); err != nil { + if mi, _, err = r.innerModuleEvaluation(c, &stackInstance, 0, resolve); err != nil { /* for _, m := range stack { // TODO asserts @@ -162,7 +162,7 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, name string, func (r *Runtime) innerModuleEvaluation( m ModuleRecord, stack *[]CyclicModuleInstance, index uint, - name string, resolve HostResolveImportedModuleFunc, + resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, idx uint, err error) { if len(*stack) > 100000 { panic("too deep dependancy stack of 100000") @@ -203,7 +203,7 @@ func (r *Runtime) innerModuleEvaluation( return nil, 0, err } var requiredInstance ModuleInstance - requiredInstance, index, err = r.innerModuleEvaluation(requiredModule, stack, index, required, resolve) + requiredInstance, index, err = r.innerModuleEvaluation(requiredModule, stack, index, resolve) if err != nil { return nil, 0, err } @@ -237,7 +237,6 @@ func (r *Runtime) innerModuleEvaluation( type ( ModuleInstance interface { - // Evaluate(rt *Runtime) (ModuleInstance, error) GetBindingValue(unistring.String, bool) (Value, bool) Namespace(*Runtime) *namespaceObject // export the type } @@ -571,7 +570,6 @@ func ModuleFromAST(name string, body *ast.Program, resolveModule HostResolveImpo } s := &SourceTextModuleRecord{ - name: name, // realm isn't implement // environment is undefined // namespace is undefined @@ -675,42 +673,6 @@ func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { return } -/* -func (rt *Runtime) getModuleNamespace(module ModuleRecord) *Namespace { - if c, ok := module.(CyclicModuleRecord); ok && c.Status() == Unlinked { - panic("oops") // TODO beter oops - } - namespace := module.Namespace() - if namespace == nil { - exportedNames := module.GetExportedNames() - var unambiguousNames []string - for _, name := range exportedNames { - _, ok := module.ResolveExport(name) - if ok { - unambiguousNames = append(unambiguousNames, name) - } - } - namespace := rt.moduleNamespaceCreate(module, unambiguousNames) - module.SetNamespace(namespace) - } - return namespace -} - -// TODO this probably should really be goja.Object -type Namespace struct { - module ModuleRecord - exports []string -} - -func (rt *Runtime) moduleNamespaceCreate(module ModuleRecord, exports []string) *Namespace { - sort.Strings(exports) - return &Namespace{ - module: module, - exports: exports, - } -} - -*/ type ResolveSetElement struct { Module ModuleRecord ExportName string @@ -797,7 +759,7 @@ func (module *SourceTextModuleRecord) Instanciate() CyclicModuleInstance { } func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { - return rt.CyclicModuleRecordEvaluate(module, module.name, module.hostResolveImportedModule) + return rt.CyclicModuleRecordEvaluate(module, module.hostResolveImportedModule) } func (module *SourceTextModuleRecord) Link() error { @@ -807,7 +769,6 @@ func (module *SourceTextModuleRecord) Link() error { } type cyclicModuleStub struct { - // namespace *Namespace status CyclicModuleRecordStatus dfsIndex uint ancestorDfsIndex uint @@ -855,15 +816,6 @@ func (c *cyclicModuleStub) RequestedModules() []string { return c.requestedModules } -/* -func (c *cyclicModuleStub) Namespace() *Namespace { - return c.namespace -} - -func (c *cyclicModuleStub) SetNamespace(namespace *Namespace) { - c.namespace = namespace -} -*/ type namespaceObject struct { baseObject m ModuleRecord @@ -921,9 +873,7 @@ func (no *namespaceObject) iterateStringKeys() iterNextFunc { } func (no *namespaceObject) iterateKeys() iterNextFunc { - return (&namespacePropIter{ - no: no, - }).next + return no.iterateStringKeys() } func (i *namespacePropIter) next() (propIterItem, iterNextFunc) { @@ -953,12 +903,6 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { writable: true, configurable: false, enumerable: true, - /* - accessor: true, - getterFunc: propGetter(no.val, no.val.runtime.ToValue(func() Value { - return - }), no.val.runtime), - */ } } @@ -967,16 +911,12 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) } if b == nil { + // TODO figure this out - this is needed as otherwise valueproperty is thought to not have a value + // which isn't really correct in a particular test around isFrozen b = _null } return &valueProperty{ - value: b, - /* - accessor: true, - getterFunc: no.val.runtime.newNativeFunc(func(FunctionCall) Value { - return b - }, nil, "", nil, 0), - */ + value: b, writable: true, configurable: false, enumerable: true, From 85504f82c00fa3d85682a0f1a40aa3227e680724 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:23:09 +0300 Subject: [PATCH 072/124] Rename module.go to modules.go --- module.go => modules.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename module.go => modules.go (100%) diff --git a/module.go b/modules.go similarity index 100% rename from module.go rename to modules.go From 3d5ae3100fd6651e20b3acb4b8a06c6969181099 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:25:54 +0300 Subject: [PATCH 073/124] making token/token_const.go diff smaller --- token/token_const.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/token/token_const.go b/token/token_const.go index 87fb1e85..f7afe9a6 100644 --- a/token/token_const.go +++ b/token/token_const.go @@ -121,7 +121,6 @@ const ( // ES6 Modules EXPORT IMPORT - lastKeyword ) var token2string = [...]string{ @@ -301,12 +300,6 @@ var keywordTable = map[string]_keyword{ "instanceof": { token: INSTANCEOF, }, - "import": { - token: IMPORT, - }, - "export": { - token: EXPORT, - }, "const": { token: CONST, }, @@ -318,10 +311,16 @@ var keywordTable = map[string]_keyword{ token: KEYWORD, futureKeyword: true, }, + "import": { + token: IMPORT, + }, "extends": { token: KEYWORD, futureKeyword: true, }, + "export": { + token: EXPORT, + }, "super": { token: KEYWORD, futureKeyword: true, From 7d1c44b2938baf33f5b4e813995a36e70b0908f3 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:27:16 +0300 Subject: [PATCH 074/124] drops some logs from tc39_test.go --- tc39_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tc39_test.go b/tc39_test.go index 9f033885..2c470df5 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -483,7 +483,7 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. t.Skip("Test threw IgnorableTestError") } } - var exc = new(Exception) + exc := new(Exception) if errors.As(err, &exc) { t.Fatalf("%s: %v", name, exc.String()) } else { @@ -519,14 +519,12 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. } if errType != meta.Negative.Type { - fmt.Println(err) vm.vm.prg.dumpCode(t.Logf) t.Fatalf("%s: unexpected error type (%s), expected (%s)", name, errType, meta.Negative.Type) } } } else { if meta.Negative.Type != "" { - t.Fatalf("%s: Expected error: %v", name, err) vm.vm.prg.dumpCode(t.Logf) t.Fatalf("%s: Expected error: %v", name, err) } From 728479739369449bab21ff74ab8e553928e90756 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:34:32 +0300 Subject: [PATCH 075/124] Remove strange code --- compiler_expr.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler_expr.go b/compiler_expr.go index 4b0468c4..dcd7eac4 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1848,10 +1848,6 @@ func (e *compiledObjectLiteral) emitGetter(putOnStack bool) { switch prop := prop.(type) { case *ast.PropertyKeyed: keyExpr := e.c.compileExpression(prop.Key) - if keyExpr == nil { - // TODO - return - } computed := false var key unistring.String switch keyExpr := keyExpr.(type) { From 66b293cb97339dbc62a114d4d85d77ac4ad80d85 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:40:42 +0300 Subject: [PATCH 076/124] Revet some changes on compiler_expr.go --- compiler_expr.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler_expr.go b/compiler_expr.go index dcd7eac4..df524292 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1266,7 +1266,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { strict := s.strict p := e.c.p - // e.c.p.dumpCode(log.Default().Printf) + // e.c.p.dumpCode() if enterFunc2Mark != -1 { e.c.popScope() } @@ -2147,7 +2147,7 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { case float64: val = floatToValue(num) default: - panic(fmt.Errorf("unsupported number literal type: %T", v.Value)) + panic(fmt.Errorf("Unsupported number literal type: %T", v.Value)) } r := &compiledLiteral{ val: val, From 3df258edd7557a7feb00d0a6d40a1b5a336c13e0 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 15:50:14 +0300 Subject: [PATCH 077/124] some compiler.go cleanup and TODOs --- compiler.go | 27 ++++++++++----------------- compiler_stmt.go | 1 + 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/compiler.go b/compiler.go index 4eac02d7..17e8d296 100644 --- a/compiler.go +++ b/compiler.go @@ -76,9 +76,8 @@ type compiler struct { scope *scope block *block - enumGetExpr compiledEnumGetExpr - // TODO add a type and a set method - hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + enumGetExpr compiledEnumGetExpr + hostResolveImportedModule HostResolveImportedModuleFunc module *SourceTextModuleRecord evalVM *vm @@ -359,9 +358,6 @@ func newCompiler() *compiler { } func (p *Program) defineLiteralValue(val Value) uint32 { - if val == nil { - panic("wat") - } for idx, v := range p.values { if v.SameAs(val) { return uint32(idx) @@ -751,16 +747,14 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { panic(fmt.Errorf("previously resolved module returned error %w", err)) } if in.importName == "*" { - b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) - b.inStash = true + c.createImmutableBinding(unistring.NewFromString(in.localName), true) } else { resolution, ambiguous := importedModule.ResolveExport(in.importName) if resolution == nil || ambiguous { c.compileAmbiguousImport(unistring.NewFromString(in.importName)) continue } - b := c.createImmutableBinding(unistring.NewFromString(in.localName), true) - b.inStash = true + c.createImmutableBinding(unistring.NewFromString(in.localName), true) } } // 15.2.1.17.4 step 9 end @@ -843,7 +837,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { for _, entry := range module.indirectExportEntries { otherModule, err := c.hostResolveImportedModule(c.module, entry.moduleRequest) if err != nil { - panic(err) // this should not be possible + panic(fmt.Errorf("previously resolved module returned error %w", err)) } if entry.importName == "*" { continue @@ -1141,11 +1135,12 @@ func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { } } -func (c *compiler) createImmutableBinding(name unistring.String, isStrict bool) *binding { +func (c *compiler) createImmutableBinding(name unistring.String, isConst bool) *binding { b, _ := c.scope.bindName(name) - b.isConst = true - b.isVar = true - b.isStrict = isStrict + b.inStash = true + b.isConst = isConst + // b.isVar = true // TODO figure out if this needs to be true at some point + // b.isStrict = isStrict return b } @@ -1256,8 +1251,6 @@ func (c *compiler) compileStandaloneFunctionDecl(v *ast.FunctionDeclaration) { } func (c *compiler) emit(instructions ...instruction) { - // fmt.Printf("emit instructions %s\n", spew.Sdump(instructions)) - // fmt.Printf("on instructions %s\n", spew.Sdump(c.p.code)) c.p.code = append(c.p.code, instructions...) } diff --git a/compiler_stmt.go b/compiler_stmt.go index be702733..65a5228d 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -53,6 +53,7 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) { case *ast.DebuggerStatement: case *ast.ImportDeclaration: // c.compileImportDeclaration(v) + // TODO explain this maybe do something in here as well ? case *ast.ExportDeclaration: c.compileExportDeclaration(v) default: From d267aaba7b79714620acdb9d664c437ea6aab7b9 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 16:05:05 +0300 Subject: [PATCH 078/124] Remove go-spew from the requirements --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index b7d29e74..36f1c569 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/dop251/goja go 1.14 require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d github.com/go-sourcemap/sourcemap v2.1.3+incompatible diff --git a/go.sum b/go.sum index 3fe31c55..e4c355f6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= From 54cf237e0ce019e25b2a249d9bd41d1caac4d934 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 17:06:43 +0300 Subject: [PATCH 079/124] Fix staticcheck --- modules.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules.go b/modules.go index 8c159d08..3b922070 100644 --- a/modules.go +++ b/modules.go @@ -289,7 +289,6 @@ func (s *SourceTextModuleInstance) Namespace(rt *Runtime) *namespaceObject { type SourceTextModuleRecord struct { cyclicModuleStub - name string // TODO remove this :crossed_fingers: body *ast.Program p *Program // context @@ -526,10 +525,10 @@ func ParseModule(name, sourceText string, resolveModule HostResolveImportedModul if err != nil { return nil, err } - return ModuleFromAST(name, body, resolveModule) + return ModuleFromAST(body, resolveModule) } -func ModuleFromAST(name string, body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { +func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { requestedModules := requestedModulesFromAst(body.Body) importEntries, err := importEntriesFromAst(body.ImportEntries) if err != nil { From 54c482f54c871ee9d4cd24fe9c1f97a642a573fb Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 6 Jul 2022 18:34:05 +0300 Subject: [PATCH 080/124] Revert some vm.go changes --- vm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vm.go b/vm.go index 3ea576cd..46fa3ce4 100644 --- a/vm.go +++ b/vm.go @@ -405,8 +405,6 @@ func (vm *vm) run() { if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { break } - // inst := vm.prg.code[vm.pc] - // fmt.Printf("Executing %T %#v\n", inst, inst) vm.prg.code[vm.pc].exec(vm) ticks++ if ticks > 10000 { @@ -568,7 +566,8 @@ func (vm *vm) peek() Value { } func (vm *vm) saveCtx(ctx *context) { - ctx.prg, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args, ctx.funcName = vm.prg, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args, vm.funcName + ctx.prg, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args, ctx.funcName = + vm.prg, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args, vm.funcName } func (vm *vm) pushCtx() { @@ -585,7 +584,8 @@ func (vm *vm) pushCtx() { } func (vm *vm) restoreCtx(ctx *context) { - vm.prg, vm.funcName, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = ctx.prg, ctx.funcName, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args + vm.prg, vm.funcName, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = + ctx.prg, ctx.funcName, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args } func (vm *vm) popCtx() { From c8cb78c13ee260633a1b3d2a9c71359c5be74b0c Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 7 Jul 2022 17:35:05 +0300 Subject: [PATCH 081/124] refactor ModuleRecord#GetExportedNames --- modules.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules.go b/modules.go index 3b922070..47b96ae2 100644 --- a/modules.go +++ b/modules.go @@ -18,7 +18,7 @@ type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, s // ModuleRecord is the common interface for module record as defined in the EcmaScript specification type ModuleRecord interface { - GetExportedNames(resolveset ...*SourceTextModuleRecord) []string // TODO maybe this parameter is wrong + GetExportedNames(resolveset ...ModuleRecord) []string // TODO maybe this parameter is wrong ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error Evaluate(*Runtime) (ModuleInstance, error) @@ -621,7 +621,7 @@ func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { return exportedNames } -func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...*SourceTextModuleRecord) []string { +func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...ModuleRecord) []string { for _, el := range exportStarSet { if el == module { // better check // TODO assert From eea69e43330ffbf0ed61f0fc14ef24a6c146bf85 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 12 Jul 2022 11:35:22 +0300 Subject: [PATCH 082/124] Rework *Module* interfaces and add a bigger not source code module test --- modules.go | 223 +++++++++++++-------------------- modules_integration_test.go | 239 ++++++++++++++++++++++++++++++++++++ runtime.go | 3 +- vm.go | 10 +- 4 files changed, 326 insertions(+), 149 deletions(-) create mode 100644 modules_integration_test.go diff --git a/modules.go b/modules.go index 47b96ae2..01638d84 100644 --- a/modules.go +++ b/modules.go @@ -18,14 +18,11 @@ type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, s // ModuleRecord is the common interface for module record as defined in the EcmaScript specification type ModuleRecord interface { + // TODO move to ModuleInstance GetExportedNames(resolveset ...ModuleRecord) []string // TODO maybe this parameter is wrong ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error Evaluate(*Runtime) (ModuleInstance, error) - /* - Namespace() *Namespace - SetNamespace(*Namespace) - */ } type CyclicModuleRecordStatus uint8 @@ -39,63 +36,65 @@ const ( ) type CyclicModuleRecord interface { - // TODO this probably shouldn't really be an interface ... or at least the current one is quite bad and big ModuleRecord - Status() CyclicModuleRecordStatus - SetStatus(CyclicModuleRecordStatus) - EvaluationError() error - SetEvaluationError(error) - DFSIndex() uint - SetDFSIndex(uint) - DFSAncestorIndex() uint - SetDFSAncestorIndex(uint) RequestedModules() []string - InitializeEnvorinment() error - ExecuteModule(*Runtime) (Value, error) - Instanciate() CyclicModuleInstance + InitializeEnvironment() error + Instanciate() CyclicModuleInstance // TODO maybe should be taking the runtime } type LinkedSourceModuleRecord struct{} func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) error { - if module.Status() == Linking || module.Status() == Evaluating { - return fmt.Errorf("bad status %+v on link", module.Status()) + stack := []CyclicModuleRecord{} + if _, err := c.innerModuleLinking(newLinkState(), module, &stack, 0); err != nil { + return err } + return nil +} - stack := []CyclicModuleRecord{} - if _, err := c.innerModuleLinking(module, &stack, 0); err != nil { - for _, m := range stack { - /* - if m.Status() != Linking { - return fmt.Errorf("bad status %+v on link", m.Status()) - } - */ - m.SetStatus(Unlinked) +type linkState struct { + status map[ModuleRecord]CyclicModuleRecordStatus + dfsIndex map[ModuleRecord]uint + dfsAncestorIndex map[ModuleRecord]uint +} - // TODO reset the rest - } - module.SetStatus(Unlinked) - return err +func newLinkState() *linkState { + return &linkState{ + status: make(map[ModuleRecord]CyclicModuleRecordStatus), + dfsIndex: make(map[ModuleRecord]uint), + dfsAncestorIndex: make(map[ModuleRecord]uint), + } +} +type evaluationState struct { + status map[ModuleInstance]CyclicModuleRecordStatus + dfsIndex map[ModuleInstance]uint + dfsAncestorIndex map[ModuleInstance]uint +} + +func newEvaluationState() *evaluationState { + return &evaluationState{ + status: make(map[ModuleInstance]CyclicModuleRecordStatus), + dfsIndex: make(map[ModuleInstance]uint), + dfsAncestorIndex: make(map[ModuleInstance]uint), } - return nil } -func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { +func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { var module CyclicModuleRecord var ok bool if module, ok = m.(CyclicModuleRecord); !ok { err := m.Link() // TODO fix return index, err } - if status := module.Status(); status == Linking || status == Linked || status == Evaluated { + if status := state.status[module]; status == Linking || status == Linked || status == Evaluated { return index, nil } else if status != Unlinked { return 0, errors.New("bad status on link") // TODO fix } - module.SetStatus(Linking) - module.SetDFSIndex(index) - module.SetDFSAncestorIndex(index) + state.status[module] = Linking + state.dfsIndex[module] = index + state.dfsAncestorIndex[module] = index index++ *stack = append(*stack, module) var err error @@ -105,30 +104,26 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor if err != nil { return 0, err } - index, err = c.innerModuleLinking(requiredModule, stack, index) + index, err = c.innerModuleLinking(state, requiredModule, stack, index) if err != nil { return 0, err } if requiredC, ok := requiredModule.(CyclicModuleRecord); ok { - // TODO some asserts - if requiredC.Status() == Linking { - if ancestorIndex := module.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { - requiredC.SetDFSAncestorIndex(ancestorIndex) + if state.status[requiredC] == Linking { + if ancestorIndex := state.dfsAncestorIndex[module]; state.dfsAncestorIndex[requiredC] > ancestorIndex { + state.dfsAncestorIndex[requiredC] = ancestorIndex } } } } - err = module.InitializeEnvorinment() + err = module.InitializeEnvironment() if err != nil { return 0, err } - // TODO more asserts - - if module.DFSAncestorIndex() == module.DFSIndex() { + if state.dfsAncestorIndex[module] == state.dfsIndex[module] { for i := len(*stack) - 1; i >= 0; i-- { requiredModule := (*stack)[i] - // TODO assert - requiredModule.SetStatus(Linked) + state.status[requiredModule] = Linked if requiredModule == module { break } @@ -139,28 +134,19 @@ func (c *compiler) innerModuleLinking(m ModuleRecord, stack *[]CyclicModuleRecor func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, err error) { - // TODO asserts if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) } stackInstance := []CyclicModuleInstance{} - if mi, _, err = r.innerModuleEvaluation(c, &stackInstance, 0, resolve); err != nil { - /* - for _, m := range stack { - // TODO asserts - m.SetStatus(Evaluated) - m.SetEvaluationError(err) - } - */ - // TODO asserts + if mi, _, err = r.innerModuleEvaluation(newEvaluationState(), c, &stackInstance, 0, resolve); err != nil { return nil, err } - // TODO asserts return mi, nil } func (r *Runtime) innerModuleEvaluation( + state *evaluationState, m ModuleRecord, stack *[]CyclicModuleInstance, index uint, resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, idx uint, err error) { @@ -179,20 +165,18 @@ func (r *Runtime) innerModuleEvaluation( if ok { return mi, index, nil } - mi = c c = cr.Instanciate() + mi = c r.modules[m] = c } - if status := c.Status(); status == Evaluated { // TODO switch - return nil, index, c.EvaluationError() + if status := state.status[mi]; status == Evaluated { + return nil, index, nil } else if status == Evaluating { return nil, index, nil - } else if status != Linked { - return nil, 0, errors.New("module isn't linked when it's being evaluated") } - c.SetStatus(Evaluating) - c.SetDFSIndex(index) - c.SetDFSAncestorIndex(index) + state.status[mi] = Evaluating + state.dfsIndex[mi] = index + state.dfsAncestorIndex[mi] = index index++ *stack = append(*stack, c) @@ -203,15 +187,14 @@ func (r *Runtime) innerModuleEvaluation( return nil, 0, err } var requiredInstance ModuleInstance - requiredInstance, index, err = r.innerModuleEvaluation(requiredModule, stack, index, resolve) + requiredInstance, index, err = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) if err != nil { return nil, 0, err } if requiredC, ok := requiredInstance.(CyclicModuleInstance); ok { - // TODO some asserts - if requiredC.Status() == Evaluating { - if ancestorIndex := c.DFSAncestorIndex(); requiredC.DFSAncestorIndex() > ancestorIndex { - requiredC.SetDFSAncestorIndex(ancestorIndex) + if state.status[requiredC] == Evaluating { + if ancestorIndex := state.dfsAncestorIndex[c]; state.dfsAncestorIndex[requiredC] > ancestorIndex { + state.dfsAncestorIndex[requiredC] = ancestorIndex } } } @@ -220,14 +203,13 @@ func (r *Runtime) innerModuleEvaluation( if err != nil { return nil, 0, err } - // TODO asserts - if c.DFSAncestorIndex() == c.DFSIndex() { + if state.dfsAncestorIndex[c] == state.dfsIndex[c] { for i := len(*stack) - 1; i >= 0; i-- { - requiredModule := (*stack)[i] + requiredModuleInstance := (*stack)[i] // TODO assert - requiredModule.SetStatus(Evaluated) - if requiredModule == c { + state.status[requiredModuleInstance] = Evaluated + if requiredModuleInstance == c { break } } @@ -237,19 +219,11 @@ func (r *Runtime) innerModuleEvaluation( type ( ModuleInstance interface { + // TODO the second argument is pointless in this case GetBindingValue(unistring.String, bool) (Value, bool) - Namespace(*Runtime) *namespaceObject // export the type } CyclicModuleInstance interface { ModuleInstance - Status() CyclicModuleRecordStatus - SetStatus(CyclicModuleRecordStatus) - EvaluationError() error - SetEvaluationError(error) - DFSIndex() uint - SetDFSIndex(uint) - DFSAncestorIndex() uint - SetDFSAncestorIndex(uint) RequestedModules() []string ExecuteModule(*Runtime) (ModuleInstance, error) } @@ -264,7 +238,6 @@ type SourceTextModuleInstance struct { moduleRecord *SourceTextModuleRecord // TODO figure out omething less idiotic exportGetters map[unistring.String]func() Value - namespace *namespaceObject } func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, error) { @@ -280,13 +253,6 @@ func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool return getter(), true } -func (s *SourceTextModuleInstance) Namespace(rt *Runtime) *namespaceObject { - if s.namespace == nil { - s.namespace = rt.createNamespaceObject(s.moduleRecord) // TODO just use the instance :shrug: - } - return s.namespace -} - type SourceTextModuleRecord struct { cyclicModuleStub body *ast.Program @@ -573,7 +539,6 @@ func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFun // environment is undefined // namespace is undefined cyclicModuleStub: cyclicModuleStub{ - status: Unlinked, requestedModules: requestedModules, }, // hostDefined TODO @@ -604,12 +569,6 @@ func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFun return s, nil } -func (module *SourceTextModuleRecord) ExecuteModule(rt *Runtime) (Value, error) { - // TODO copy runtime.RunProgram here with some changes so that it doesn't touch the global ? - - return rt.RunProgram(module.p) -} - func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { exportedNames := make([]string, 0, len(module.localExportEntries)+len(module.indirectExportEntries)) for _, e := range module.localExportEntries { @@ -654,7 +613,7 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...ModuleRe return exportedNames } -func (module *SourceTextModuleRecord) InitializeEnvorinment() (err error) { +func (module *SourceTextModuleRecord) InitializeEnvironment() (err error) { c := newCompiler() defer func() { if x := recover(); x != nil { @@ -682,7 +641,14 @@ type ResolvedBinding struct { BindingName string } +// GetModuleInstance returns an instance of an already instanciated module. +// If the ModuleRecord was not instanciated at this time it will return nil +func (r *Runtime) GetModuleInstance(m ModuleRecord) ModuleInstance { + return r.modules[m] +} + func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) { + // TODO this whole algorithm can likely be used for not source module records a well if exportName == "" { panic("wat") } @@ -749,7 +715,6 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese func (module *SourceTextModuleRecord) Instanciate() CyclicModuleInstance { return &SourceTextModuleInstance{ cyclicModuleStub: cyclicModuleStub{ - status: module.status, requestedModules: module.requestedModules, }, moduleRecord: module, @@ -768,45 +733,9 @@ func (module *SourceTextModuleRecord) Link() error { } type cyclicModuleStub struct { - status CyclicModuleRecordStatus - dfsIndex uint - ancestorDfsIndex uint - evaluationError error requestedModules []string } -func (c *cyclicModuleStub) SetStatus(status CyclicModuleRecordStatus) { - c.status = status -} - -func (c *cyclicModuleStub) Status() CyclicModuleRecordStatus { - return c.status -} - -func (c *cyclicModuleStub) SetDFSIndex(index uint) { - c.dfsIndex = index -} - -func (c *cyclicModuleStub) DFSIndex() uint { - return c.dfsIndex -} - -func (c *cyclicModuleStub) SetDFSAncestorIndex(index uint) { - c.ancestorDfsIndex = index -} - -func (c *cyclicModuleStub) DFSAncestorIndex() uint { - return c.ancestorDfsIndex -} - -func (c *cyclicModuleStub) SetEvaluationError(err error) { - c.evaluationError = err -} - -func (c *cyclicModuleStub) EvaluationError() error { - return c.evaluationError -} - func (c *cyclicModuleStub) SetRequestedModules(modules []string) { c.requestedModules = modules } @@ -822,6 +751,18 @@ type namespaceObject struct { exportsNames []unistring.String } +func (r *Runtime) NamespaceObjectFor(m ModuleRecord) *Object { + if r.moduleNamespaces == nil { + r.moduleNamespaces = make(map[ModuleRecord]*namespaceObject) + } + if o, ok := r.moduleNamespaces[m]; ok { + return o.val + } + o := r.createNamespaceObject(m) + r.moduleNamespaces[m] = o + return o.val +} + func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { o := &Object{runtime: r} no := &namespaceObject{m: m} @@ -895,16 +836,16 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { if ambiguous || v == nil { no.val.runtime.throwReferenceError((name)) } - mi := no.val.runtime.modules[v.Module] if v.BindingName == "*namespace*" { return &valueProperty{ - value: mi.Namespace(no.val.runtime).val, + value: no.val.runtime.NamespaceObjectFor(v.Module), writable: true, configurable: false, enumerable: true, } } + mi := no.val.runtime.modules[v.Module] b, ok := mi.GetBindingValue(unistring.NewFromString(v.BindingName), true) if !ok { no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) diff --git a/modules_integration_test.go b/modules_integration_test.go new file mode 100644 index 00000000..f01b1f7f --- /dev/null +++ b/modules_integration_test.go @@ -0,0 +1,239 @@ +package goja_test // this is on purpose in a separate package + +import ( + "fmt" + "io/fs" + "strings" + "testing" + "testing/fstest" + + "github.com/dop251/goja" + "github.com/dop251/goja/unistring" +) + +type simpleComboResolver struct { + cache map[string]cacheElement + fs fs.FS + custom func(interface{}, string) (goja.ModuleRecord, error) +} +type cacheElement struct { + m goja.ModuleRecord + err error +} + +func newSimpleComboResolver() *simpleComboResolver { + return &simpleComboResolver{cache: make(map[string]cacheElement)} +} + +func (s *simpleComboResolver) resolve(referencingScriptOrModule interface{}, specifier string) (goja.ModuleRecord, error) { + k, ok := s.cache[specifier] + if ok { + return k.m, k.err + } + if strings.HasPrefix(specifier, "custom:") { + p, err := s.custom(referencingScriptOrModule, specifier) + s.cache[specifier] = cacheElement{m: p, err: err} + return p, err + } + b, err := fs.ReadFile(s.fs, specifier) + if err != nil { + s.cache[specifier] = cacheElement{err: err} + return nil, err + } + p, err := goja.ParseModule(specifier, string(b), s.resolve) + if err != nil { + s.cache[specifier] = cacheElement{err: err} + return nil, err + } + s.cache[specifier] = cacheElement{m: p} + return p, nil +} + +type unresolvedBinding struct { + module string + bidning string +} + +func TestNotSourceModulesBigTest(t *testing.T) { + t.Parallel() + resolver := newSimpleComboResolver() + resolver.custom = func(_ interface{}, specifier string) (goja.ModuleRecord, error) { + switch specifier { + case "custom:coolstuff": + return &simpleModuleImpl{}, nil + case "custom:coolstuff2": + return &cyclicModuleImpl{ + resolve: resolver.resolve, + requestedModules: []string{"custom:coolstuff3", "custom:coolstuff"}, + exports: map[string]unresolvedBinding{ + "coolStuff": { + bidning: "coolStuff", + module: "custom:coolstuff", + }, + "otherCoolStuff": { // request it from third module which will request it back from us + bidning: "coolStuff", + module: "custom:coolstuff3", + }, + }, + }, nil + case "custom:coolstuff3": + return &cyclicModuleImpl{ + resolve: resolver.resolve, + requestedModules: []string{"custom:coolstuff2"}, + exports: map[string]unresolvedBinding{ + "coolStuff": { // request it back from the module + bidning: "coolStuff", + module: "custom:coolstuff2", + }, + }, + }, nil + default: + return nil, fmt.Errorf("unknown module %q", specifier) + } + } + mapfs := make(fstest.MapFS) + mapfs["main.js"] = &fstest.MapFile{ + Data: []byte(` + import {coolStuff} from "custom:coolstuff"; + import {coolStuff as coolStuff3, otherCoolStuff} from "custom:coolstuff2"; + if (coolStuff != 5) { + throw "coolStuff isn't a 5 it is a "+ coolStuff + } + if (coolStuff3 != 5) { + throw "coolStuff3 isn't a 5 it is a "+ coolStuff3 + } + if (otherCoolStuff != 5) { + throw "otherCoolStuff isn't a 5 it is a "+ otherCoolStuff + } + `), + } + resolver.fs = mapfs + m, err := resolver.resolve(nil, "main.js") + if err != nil { + t.Fatalf("got error %s", err) + } + p := m.(*goja.SourceTextModuleRecord) + + err = p.Link() + if err != nil { + t.Fatalf("got error %s", err) + } + vm := goja.New() + _, err = m.Evaluate(vm) + if err != nil { + t.Fatalf("got error %s", err) + } +} + +// START of simple module implementation +type simpleModuleImpl struct{} + +var _ goja.ModuleRecord = &simpleModuleImpl{} + +func (s *simpleModuleImpl) Link() error { + // this does nothing on this + return nil +} + +func (s *simpleModuleImpl) ResolveExport(exportName string, resolveset ...goja.ResolveSetElement) (*goja.ResolvedBinding, bool) { + if exportName == "coolStuff" { + return &goja.ResolvedBinding{ + BindingName: exportName, + Module: s, + }, false + } + return nil, false +} + +func (s *simpleModuleImpl) Evaluate(rt *goja.Runtime) (goja.ModuleInstance, error) { + return &simpleModuleInstanceImpl{rt: rt}, nil +} + +func (s *simpleModuleImpl) GetExportedNames(records ...goja.ModuleRecord) []string { + return []string{"coolStuff"} +} + +type simpleModuleInstanceImpl struct { + rt *goja.Runtime +} + +func (si *simpleModuleInstanceImpl) GetBindingValue(exportName unistring.String, b bool) (goja.Value, bool) { + if exportName.String() == "coolStuff" { + return si.rt.ToValue(5), true + } + return nil, false +} + +// START of cyclic module implementation +type cyclicModuleImpl struct { + requestedModules []string + exports map[string]unresolvedBinding + resolve goja.HostResolveImportedModuleFunc +} + +var _ goja.CyclicModuleRecord = &cyclicModuleImpl{} + +func (s *cyclicModuleImpl) InitializeEnvironment() error { + return nil +} + +func (s *cyclicModuleImpl) Instanciate() goja.CyclicModuleInstance { + return &cyclicModuleInstanceImpl{module: s} +} + +func (s *cyclicModuleImpl) RequestedModules() []string { + return s.requestedModules +} + +func (s *cyclicModuleImpl) Link() error { + // this does nothing on this + return nil +} + +func (s *cyclicModuleImpl) Evaluate(rt *goja.Runtime) (goja.ModuleInstance, error) { + return nil, nil +} + +func (s *cyclicModuleImpl) ResolveExport(exportName string, resolveset ...goja.ResolveSetElement) (*goja.ResolvedBinding, bool) { + b, ok := s.exports[exportName] + if !ok { + return nil, false + } + + m, err := s.resolve(s, b.module) + if err != nil { + panic(err) + } + + return &goja.ResolvedBinding{ + Module: m, + BindingName: b.bidning, + }, false +} + +func (s *cyclicModuleImpl) GetExportedNames(records ...goja.ModuleRecord) []string { + return []string{"coolStuff"} +} + +type cyclicModuleInstanceImpl struct { + rt *goja.Runtime + module *cyclicModuleImpl +} + +func (si *cyclicModuleInstanceImpl) RequestedModules() []string { + return si.module.RequestedModules() +} + +func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.ModuleInstance, error) { + si.rt = rt + // TODO others + return nil, nil +} + +func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName unistring.String, _ bool) (goja.Value, bool) { + b, ambigious := si.module.ResolveExport(exportName.String()) + if ambigious || b == nil { + panic("fix this") + } + return si.rt.GetModuleInstance(b.Module).GetBindingValue(exportName, true) +} diff --git a/runtime.go b/runtime.go index 4add2d89..6778d7d8 100644 --- a/runtime.go +++ b/runtime.go @@ -183,7 +183,8 @@ type Runtime struct { hash *maphash.Hash idSeq uint64 - modules map[ModuleRecord]ModuleInstance + modules map[ModuleRecord]ModuleInstance + moduleNamespaces map[ModuleRecord]*namespaceObject jobQueue []func() diff --git a/vm.go b/vm.go index 46fa3ce4..609c87a6 100644 --- a/vm.go +++ b/vm.go @@ -566,8 +566,7 @@ func (vm *vm) peek() Value { } func (vm *vm) saveCtx(ctx *context) { - ctx.prg, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args, ctx.funcName = - vm.prg, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args, vm.funcName + ctx.prg, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args, ctx.funcName = vm.prg, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args, vm.funcName } func (vm *vm) pushCtx() { @@ -584,8 +583,7 @@ func (vm *vm) pushCtx() { } func (vm *vm) restoreCtx(ctx *context) { - vm.prg, vm.funcName, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = - ctx.prg, ctx.funcName, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args + vm.prg, vm.funcName, vm.stash, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = ctx.prg, ctx.funcName, ctx.stash, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args } func (vm *vm) popCtx() { @@ -858,9 +856,7 @@ type importNamespace struct { } func (i importNamespace) exec(vm *vm) { - mi := vm.r.modules[i.module] - namespace := mi.Namespace(vm.r) - vm.push(namespace.val) + vm.push(vm.r.NamespaceObjectFor(i.module)) vm.pc++ } From dce811c650d109b1f59fb2fcf9798c5bf2d5f685 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 21 Jul 2022 17:37:41 +0300 Subject: [PATCH 083/124] Bump minimal go to 1.16 for io/fs --- .github/workflows/main.yml | 2 +- README.md | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c82e1733..45cd72a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.14.x, 1.x] + go-version: [1.16.x, 1.x] os: [ubuntu-latest] arch: ["", "386"] fail-fast: false diff --git a/README.md b/README.md index 35a42442..d606d915 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ performance. This project was largely inspired by [otto](https://github.com/robertkrimen/otto). -Minimum required Go version is 1.14. +Minimum required Go version is 1.16. Features -------- diff --git a/go.mod b/go.mod index 36f1c569..9d425288 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/dop251/goja -go 1.14 +go 1.16 require ( github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 From ecf79f89f3f36017466c09abd7f10239edd29d4a Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 25 Jul 2022 12:42:53 +0300 Subject: [PATCH 084/124] Enable tc39 module tests again and add class support --- ast/node.go | 12 ++++++------ compiler.go | 14 +++++++++++++- compiler_stmt.go | 8 +++++++- modules.go | 15 +++++++++++++++ parser/statement.go | 19 +++++++++++++++++++ tc39_test.go | 22 ---------------------- 6 files changed, 60 insertions(+), 30 deletions(-) diff --git a/ast/node.go b/ast/node.go index df1fdf36..a5dedf0a 100644 --- a/ast/node.go +++ b/ast/node.go @@ -496,12 +496,12 @@ type ( } ExportDeclaration struct { - idx0 file.Idx - idx1 file.Idx - Variable *VariableStatement - AssignExpression Expression - LexicalDeclaration *LexicalDeclaration - // ClassDeclaration + idx0 file.Idx + idx1 file.Idx + Variable *VariableStatement + AssignExpression Expression + LexicalDeclaration *LexicalDeclaration + ClassDeclaration *ClassDeclaration NamedExports *NamedExports ExportFromClause *ExportFromClause FromClause *FromClause diff --git a/compiler.go b/compiler.go index 8294589f..72c36298 100644 --- a/compiler.go +++ b/compiler.go @@ -927,7 +927,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.newBlockScope() scope = c.scope scope.funcType = funcRegular - // scope.function = true // TODO check if needed + scope.variable = true } for _, in := range module.indirectExportEntries { v, ambiguous := module.ResolveExport(in.exportName) @@ -1423,6 +1423,18 @@ func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclare scopeDeclared = true } c.createLexicalBindings(exp.LexicalDeclaration) + } else if exp, ok := st.(*ast.ExportDeclaration); ok && exp.ClassDeclaration != nil { + // TODO refactor + if !scopeDeclared { + c.newBlockScope() + scopeDeclared = true + } + cls := exp.ClassDeclaration + if exp.IsDefault { + c.createLexicalIdBinding("default", false, int(exp.Idx0())-1) + } else { + c.createLexicalIdBinding(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1) + } } } return scopeDeclared diff --git a/compiler_stmt.go b/compiler_stmt.go index ea409a70..f0a4a252 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -791,6 +791,13 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { c.compileVariableStatement(expr.Variable) } else if expr.LexicalDeclaration != nil { c.compileLexicalDeclaration(expr.LexicalDeclaration) + } else if expr.ClassDeclaration != nil { + cls := expr.ClassDeclaration + if expr.IsDefault { + c.emitLexicalAssign("default", int(cls.Class.Class)-1, c.compileClassLiteral(cls.Class, false)) + } else { + c.compileClassDeclaration(cls) + } } else if expr.HoistableDeclaration != nil { // already done } else if assign := expr.AssignExpression; assign != nil { @@ -844,7 +851,6 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { } } } - // TODO } func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { diff --git a/modules.go b/modules.go index 01638d84..3a8f42af 100644 --- a/modules.go +++ b/modules.go @@ -445,6 +445,21 @@ func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { localName: "default", lex: true, }) + } else if exportDeclaration.ClassDeclaration != nil { + cls := exportDeclaration.ClassDeclaration.Class + if exportDeclaration.IsDefault { + result = append(result, exportEntry{ + exportName: "default", + localName: "default", + lex: true, + }) + } else { + result = append(result, exportEntry{ + exportName: cls.Name.Name.String(), + localName: cls.Name.Name.String(), + lex: true, + }) + } } else { panic("wat") } diff --git a/parser/statement.go b/parser/statement.go index 5c65780a..8a6754cb 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -1019,6 +1019,15 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { }, }, } + case token.CLASS: + decl := &ast.ExportDeclaration{ + ClassDeclaration: &ast.ClassDeclaration{ + Class: self.parseClass(true), + }, + } + self.insertSemicolon = true + return decl + case token.DEFAULT: self.next() var exp *ast.ExportDeclaration @@ -1038,6 +1047,16 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { }, IsDefault: true, } + case token.CLASS: + decl := &ast.ExportDeclaration{ + ClassDeclaration: &ast.ClassDeclaration{ + Class: self.parseClass(false), + }, + IsDefault: true, + } + self.insertSemicolon = true + return decl + default: exp = &ast.ExportDeclaration{ AssignExpression: self.parseAssignmentExpression(), diff --git a/tc39_test.go b/tc39_test.go index e713d0c8..8dd70bb4 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -49,23 +49,6 @@ var ( "test/language/statements/class/elements/private-setter-is-not-a-own-property.js": true, "test/language/statements/class/elements/private-getter-is-not-a-own-property.js": true, - "test/language/module-code/eval-export-dflt-cls-anon.js": true, - "test/language/module-code/eval-export-dflt-cls-anon-semi.js": true, - "test/language/module-code/eval-export-dflt-cls-named.js": true, - "test/language/module-code/eval-export-dflt-cls-named-semi.js": true, - "test/language/module-code/eval-export-dflt-cls-named-semi-meth.js": true, - "test/language/module-code/eval-export-dflt-expr-cls-named.js": true, - "test/language/module-code/eval-export-dflt-expr-cls-name-meth.js": true, - "test/language/module-code/instn-named-bndng-cls.js": true, - "test/language/module-code/instn-local-bndng-export-cls.js": true, - "test/language/module-code/instn-local-bndng-cls.js": true, - "test/language/module-code/eval-gtbndng-local-bndng-cls.js": true, - "test/language/module-code/instn-iee-bndng-cls.js": true, - "test/language/module-code/eval-export-dflt-expr-cls-anon.js": true, - "test/language/module-code/eval-export-dflt-cls-name-meth.js": true, - "test/language/module-code/eval-export-cls-semi.js": true, - "test/language/module-code/instn-named-bndng-dflt-cls.js": true, - // restricted unicode regexp syntax "test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true, "test/built-ins/RegExp/unicode_restricted_octal_escape.js": true, @@ -350,11 +333,6 @@ func init() { // legacy octal escape in strings in strict mode "test/language/literals/string/legacy-octal-", "test/language/literals/string/legacy-non-octal-", - - // modules - "test/language/export/", - "test/language/import/", - "test/language/module-code/", ) } From 31502c6661a0aac45ee845ae6f62624bdcc182b1 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 25 Jul 2022 15:44:54 +0300 Subject: [PATCH 085/124] Make GetBindingValue simpler --- compiler.go | 5 +---- compiler_stmt.go | 27 +++++---------------------- modules.go | 16 ++++------------ modules_integration_test.go | 10 +++++----- 4 files changed, 15 insertions(+), 43 deletions(-) diff --git a/compiler.go b/compiler.go index 72c36298..08b48870 100644 --- a/compiler.go +++ b/compiler.go @@ -1056,10 +1056,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { vm.r.throwReferenceError(exportName) // TODO fix } else { s.exportGetters[exportName] = func() Value { - v, ok := m2.GetBindingValue(importName, true) - if !ok { - vm.r.throwReferenceError(exportName) // TODO fix - } + v := m2.GetBindingValue(importName) return v } } diff --git a/compiler_stmt.go b/compiler_stmt.go index f0a4a252..6c4ff22a 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -828,7 +828,7 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { value, ambiguous := module.ResolveExport(name.IdentifierName.String()) if ambiguous || value == nil { // also ambiguous - continue // ambiguous import already reports + continue // ambiguous import already reported } n := name.Alias @@ -839,15 +839,10 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } - identifier := name.IdentifierName // this - // moduleName := expr.FromClause.ModuleSpecifier.String() + identifier := name.IdentifierName // name will be reused in the for loop localB.getIndirect = func(vm *vm) Value { m := vm.r.modules[module] - v, ok := m.GetBindingValue(identifier, true) - if !ok { - vm.r.throwReferenceError(identifier) - } - return v + return m.GetBindingValue(identifier) } } } @@ -906,15 +901,9 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } identifier := unistring.NewFromString(value.BindingName) - // moduleName := expr.FromClause.ModuleSpecifier.String() - localB.getIndirect = func(vm *vm) Value { m := vm.r.modules[value.Module] - v, ok := m.GetBindingValue(identifier, true) - if !ok { - vm.r.throwReferenceError(identifier) - } - return v + return m.GetBindingValue(identifier) } } } @@ -942,16 +931,10 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { }, idx), ) } else { - // moduleName := expr.FromClause.ModuleSpecifier.String() identifier := unistring.NewFromString(value.BindingName) localB.getIndirect = func(vm *vm) Value { - // TODO this should be just "default", this also likely doesn't work for export aliasing m := vm.r.modules[value.Module] - v, ok := m.GetBindingValue(identifier, true) - if !ok { - vm.r.throwReferenceError(identifier) - } - return v + return m.GetBindingValue(identifier) } } } diff --git a/modules.go b/modules.go index 3a8f42af..2c812dd7 100644 --- a/modules.go +++ b/modules.go @@ -219,8 +219,7 @@ func (r *Runtime) innerModuleEvaluation( type ( ModuleInstance interface { - // TODO the second argument is pointless in this case - GetBindingValue(unistring.String, bool) (Value, bool) + GetBindingValue(unistring.String) Value } CyclicModuleInstance interface { ModuleInstance @@ -245,12 +244,8 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, e return s, err } -func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String, b bool) (Value, bool) { - getter, ok := s.exportGetters[name] - if !ok { - return nil, false - } - return getter(), true +func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String) Value { + return s.exportGetters[name]() } type SourceTextModuleRecord struct { @@ -861,10 +856,7 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { } mi := no.val.runtime.modules[v.Module] - b, ok := mi.GetBindingValue(unistring.NewFromString(v.BindingName), true) - if !ok { - no.val.runtime.throwReferenceError(unistring.NewFromString(v.BindingName)) - } + b := mi.GetBindingValue(unistring.NewFromString(v.BindingName)) if b == nil { // TODO figure this out - this is needed as otherwise valueproperty is thought to not have a value // which isn't really correct in a particular test around isFrozen diff --git a/modules_integration_test.go b/modules_integration_test.go index f01b1f7f..416dc752 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -157,11 +157,11 @@ type simpleModuleInstanceImpl struct { rt *goja.Runtime } -func (si *simpleModuleInstanceImpl) GetBindingValue(exportName unistring.String, b bool) (goja.Value, bool) { +func (si *simpleModuleInstanceImpl) GetBindingValue(exportName unistring.String) goja.Value { if exportName.String() == "coolStuff" { - return si.rt.ToValue(5), true + return si.rt.ToValue(5) } - return nil, false + return nil } // START of cyclic module implementation @@ -230,10 +230,10 @@ func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.Module return nil, nil } -func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName unistring.String, _ bool) (goja.Value, bool) { +func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName unistring.String) goja.Value { b, ambigious := si.module.ResolveExport(exportName.String()) if ambigious || b == nil { panic("fix this") } - return si.rt.GetModuleInstance(b.Module).GetBindingValue(exportName, true) + return si.rt.GetModuleInstance(b.Module).GetBindingValue(exportName) } From bdf1719dbe5c21cca90bbbdf41f9e5b25ac39477 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 25 Jul 2022 15:52:56 +0300 Subject: [PATCH 086/124] don't panic in GetBindingValue when asked for not existing binding --- modules.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules.go b/modules.go index 2c812dd7..77305fc4 100644 --- a/modules.go +++ b/modules.go @@ -245,7 +245,11 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, e } func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String) Value { - return s.exportGetters[name]() + getter, ok := s.exportGetters[name] + if !ok { // let's not panic in case somebody asks for a binding that isn't exported + return nil + } + return getter() } type SourceTextModuleRecord struct { From 82f046e69d1bdb03ad6df6510f34a51a690b8ff7 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 26 Jul 2022 12:47:16 +0300 Subject: [PATCH 087/124] Don't require CyclicModuleRecord in a few places --- modules.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules.go b/modules.go index 77305fc4..ab05920f 100644 --- a/modules.go +++ b/modules.go @@ -44,7 +44,7 @@ type CyclicModuleRecord interface { type LinkedSourceModuleRecord struct{} -func (c *compiler) CyclicModuleRecordConcreteLink(module CyclicModuleRecord) error { +func (c *compiler) CyclicModuleRecordConcreteLink(module ModuleRecord) error { stack := []CyclicModuleRecord{} if _, err := c.innerModuleLinking(newLinkState(), module, &stack, 0); err != nil { return err @@ -132,7 +132,7 @@ func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[ return index, nil } -func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostResolveImportedModuleFunc, +func (r *Runtime) CyclicModuleRecordEvaluate(c ModuleRecord, resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, err error) { if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) From ec18d21ab9c2065f7901ce80f852894fa74bac44 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 26 Jul 2022 13:00:34 +0300 Subject: [PATCH 088/124] typo --- modules.go | 6 +++--- modules_integration_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules.go b/modules.go index ab05920f..871d7c27 100644 --- a/modules.go +++ b/modules.go @@ -39,7 +39,7 @@ type CyclicModuleRecord interface { ModuleRecord RequestedModules() []string InitializeEnvironment() error - Instanciate() CyclicModuleInstance // TODO maybe should be taking the runtime + Instantiate() CyclicModuleInstance // TODO maybe should be taking the runtime } type LinkedSourceModuleRecord struct{} @@ -165,7 +165,7 @@ func (r *Runtime) innerModuleEvaluation( if ok { return mi, index, nil } - c = cr.Instanciate() + c = cr.Instantiate() mi = c r.modules[m] = c } @@ -726,7 +726,7 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese return starResolution, false } -func (module *SourceTextModuleRecord) Instanciate() CyclicModuleInstance { +func (module *SourceTextModuleRecord) Instantiate() CyclicModuleInstance { return &SourceTextModuleInstance{ cyclicModuleStub: cyclicModuleStub{ requestedModules: module.requestedModules, diff --git a/modules_integration_test.go b/modules_integration_test.go index 416dc752..bf341eb3 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -177,7 +177,7 @@ func (s *cyclicModuleImpl) InitializeEnvironment() error { return nil } -func (s *cyclicModuleImpl) Instanciate() goja.CyclicModuleInstance { +func (s *cyclicModuleImpl) Instantiate() goja.CyclicModuleInstance { return &cyclicModuleInstanceImpl{module: s} } From 22f4a9a0cc88c82f9effbcf284086b3a7af543f6 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 28 Jul 2022 12:18:43 +0300 Subject: [PATCH 089/124] Drop some comments in the parser --- parser/statement.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index 8a6754cb..4afa9179 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -982,16 +982,11 @@ illegal: return &ast.BadStatement{From: idx, To: self.idx} } -// :white_check_mark: export ExportFromClause FromClause; (3. is missing) -// :white_check_mark: export NamedExports; -// :white_check_mark: export VariableStatement func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { self.next() - // TODO: parse `export Declaration` -> function || generator || async function || async generator || let || const - switch self.token { - case token.MULTIPLY: // FIXME: should also parse NamedExports if '{' + case token.MULTIPLY: exportFromClause := self.parseExportFromClause() fromClause := self.parseFromClause() self.semicolon() From abdba61a21b7c94d4a19a2e086174db359c5c654 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 28 Jul 2022 17:02:45 +0300 Subject: [PATCH 090/124] Fix emptying the stack in the modules inner methods --- modules.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules.go b/modules.go index 871d7c27..eefd1524 100644 --- a/modules.go +++ b/modules.go @@ -123,6 +123,7 @@ func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[ if state.dfsAncestorIndex[module] == state.dfsIndex[module] { for i := len(*stack) - 1; i >= 0; i-- { requiredModule := (*stack)[i] + *stack = (*stack)[:i] state.status[requiredModule] = Linked if requiredModule == module { break @@ -207,7 +208,7 @@ func (r *Runtime) innerModuleEvaluation( if state.dfsAncestorIndex[c] == state.dfsIndex[c] { for i := len(*stack) - 1; i >= 0; i-- { requiredModuleInstance := (*stack)[i] - // TODO assert + *stack = (*stack)[:i] state.status[requiredModuleInstance] = Evaluated if requiredModuleInstance == c { break From 0ebc65d1ac2ba75e20da631028bbf12f5f808ecd Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 1 Aug 2022 17:25:00 +0300 Subject: [PATCH 091/124] Fix import/export indexes --- ast/node.go | 14 +++++++++----- parser/statement.go | 31 ++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/ast/node.go b/ast/node.go index a5dedf0a..43e3b60f 100644 --- a/ast/node.go +++ b/ast/node.go @@ -496,8 +496,7 @@ type ( } ExportDeclaration struct { - idx0 file.Idx - idx1 file.Idx + Idx file.Idx Variable *VariableStatement AssignExpression Expression LexicalDeclaration *LexicalDeclaration @@ -754,7 +753,7 @@ func (self *PropertyShort) Idx0() file.Idx { return self.Name.Id func (self *PropertyKeyed) Idx0() file.Idx { return self.Key.Idx0() } func (self *ExpressionBody) Idx0() file.Idx { return self.Expression.Idx0() } -func (self *ExportDeclaration) Idx0() file.Idx { return self.idx0 } +func (self *ExportDeclaration) Idx0() file.Idx { return self.Idx } func (self *ImportDeclaration) Idx0() file.Idx { return self.Idx } func (self *FieldDefinition) Idx0() file.Idx { return self.Idx } @@ -870,8 +869,13 @@ func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() } func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() } -func (self *ExportDeclaration) Idx1() file.Idx { return self.idx1 } -func (self *ImportDeclaration) Idx1() file.Idx { return self.Idx } // TODO fix +func (self *ExportDeclaration) Idx1() file.Idx { + return self.Idx + file.Idx(len(token.EXPORT.String())) +} + +func (self *ImportDeclaration) Idx1() file.Idx { + return self.Idx + file.Idx(len(token.IMPORT.String())) +} func (self *FieldDefinition) Idx1() file.Idx { if self.Initializer != nil { diff --git a/parser/statement.go b/parser/statement.go index 4afa9179..8179e9a9 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -983,7 +983,7 @@ illegal: } func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { - self.next() + idx := self.expect(token.EXPORT) switch self.token { case token.MULTIPLY: @@ -991,6 +991,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { fromClause := self.parseFromClause() self.semicolon() return &ast.ExportDeclaration{ + Idx: idx, ExportFromClause: exportFromClause, FromClause: fromClause, } @@ -999,15 +1000,23 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { fromClause := self.parseFromClause() self.semicolon() return &ast.ExportDeclaration{ + Idx: idx, NamedExports: namedExports, FromClause: fromClause, } case token.VAR: - return &ast.ExportDeclaration{Variable: self.parseVariableStatement()} + return &ast.ExportDeclaration{ + Idx: idx, + Variable: self.parseVariableStatement(), + } case token.LET, token.CONST: - return &ast.ExportDeclaration{LexicalDeclaration: self.parseLexicalDeclaration(self.token)} + return &ast.ExportDeclaration{ + Idx: idx, + LexicalDeclaration: self.parseLexicalDeclaration(self.token), + } case token.FUNCTION: return &ast.ExportDeclaration{ + Idx: idx, HoistableDeclaration: &ast.HoistableDeclaration{ FunctionDeclaration: &ast.FunctionDeclaration{ Function: self.parseFunction(true), @@ -1016,6 +1025,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { } case token.CLASS: decl := &ast.ExportDeclaration{ + Idx: idx, ClassDeclaration: &ast.ClassDeclaration{ Class: self.parseClass(true), }, @@ -1034,6 +1044,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { f.Name = &ast.Identifier{Name: unistring.String("default"), Idx: f.Idx0()} } exp = &ast.ExportDeclaration{ + Idx: idx, HoistableDeclaration: &ast.HoistableDeclaration{ FunctionDeclaration: &ast.FunctionDeclaration{ Function: f, @@ -1044,6 +1055,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { } case token.CLASS: decl := &ast.ExportDeclaration{ + Idx: idx, ClassDeclaration: &ast.ClassDeclaration{ Class: self.parseClass(false), }, @@ -1054,6 +1066,7 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { default: exp = &ast.ExportDeclaration{ + Idx: idx, AssignExpression: self.parseAssignmentExpression(), IsDefault: true, } @@ -1063,20 +1076,20 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { default: namedExports := self.parseNamedExports() self.semicolon() - return &ast.ExportDeclaration{NamedExports: namedExports} + return &ast.ExportDeclaration{ + Idx: idx, + NamedExports: namedExports, + } } } func (self *_parser) parseImportDeclaration() *ast.ImportDeclaration { - idx := self.idx - self.next() + idx := self.expect(token.IMPORT) - // _, _, errString := self.scanString(0, false) // look at first character only if self.token == token.STRING { moduleSpecifier := self.parseModuleSpecifier() self.semicolon() - - return &ast.ImportDeclaration{ModuleSpecifier: moduleSpecifier} + return &ast.ImportDeclaration{Idx: idx, ModuleSpecifier: moduleSpecifier} } importClause := self.parseImportClause() From 262260dfd56f91e3620399a08a402639e8ba86b1 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 1 Aug 2022 17:36:37 +0300 Subject: [PATCH 092/124] Some parser comment cleanup --- parser/statement.go | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index 8179e9a9..2d37686f 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -1092,12 +1092,9 @@ func (self *_parser) parseImportDeclaration() *ast.ImportDeclaration { return &ast.ImportDeclaration{Idx: idx, ModuleSpecifier: moduleSpecifier} } - importClause := self.parseImportClause() - fromClause := self.parseFromClause() - return &ast.ImportDeclaration{ - ImportClause: importClause, - FromClause: fromClause, + ImportClause: self.parseImportClause(), + FromClause: self.parseFromClause(), Idx: idx, } } @@ -1143,29 +1140,19 @@ func (self *_parser) parseFromClause() *ast.FromClause { } } return nil - - // expression := self.parsePrimaryExpression() // module specifier (string literal) - // stringLiteral, ok := expression.(*ast.StringLiteral) - // if !ok { - // self.error(self.idx, "expected module specifier") - // return nil - // } } func (self *_parser) parseExportFromClause() *ast.ExportFromClause { - if self.token == token.MULTIPLY { // FIXME: rename token to STAR? + if self.token == token.MULTIPLY { self.next() if self.token != token.IDENTIFIER { return &ast.ExportFromClause{IsWildcard: true} } - // Is next token a "as" identifier - // nextToken, nextLiteral, _, _, := self.scan() - // literal, _, _, _ := self.scanIdentifier() if self.literal == "as" { self.next() - alias := self.parseIdentifier() // TODO: does it really match specification's IdentifierName? + alias := self.parseIdentifier() return &ast.ExportFromClause{ IsWildcard: true, Alias: alias.Name, @@ -1184,7 +1171,7 @@ func (self *_parser) parseExportFromClause() *ast.ExportFromClause { } func (self *_parser) parseNamedExports() *ast.NamedExports { - _ = self.expect(token.LEFT_BRACE) // FIXME: return idx? + _ = self.expect(token.LEFT_BRACE) exportsList := self.parseExportsList() @@ -1195,8 +1182,7 @@ func (self *_parser) parseNamedExports() *ast.NamedExports { } } -// MOVE IN EXPRESSION -func (self *_parser) parseExportsList() (exportsList []*ast.ExportSpecifier) { // FIXME: ast.Binding? +func (self *_parser) parseExportsList() (exportsList []*ast.ExportSpecifier) { if self.token == token.RIGHT_BRACE { return } @@ -1216,13 +1202,11 @@ func (self *_parser) parseExportsList() (exportsList []*ast.ExportSpecifier) { / func (self *_parser) parseExportSpecifier() *ast.ExportSpecifier { identifier := self.parseIdentifier() - // Is the next token an identifier? - if self.token != token.IDENTIFIER { // No "as" identifier found + if self.token != token.IDENTIFIER { return &ast.ExportSpecifier{IdentifierName: identifier.Name} } if self.literal != "as" { - // FAIL self.error(self.idx, "Expected 'as' keyword, found '%s' instead", self.literal) } self.next() @@ -1235,7 +1219,7 @@ func (self *_parser) parseExportSpecifier() *ast.ExportSpecifier { } } -func (self *_parser) parseImportClause() *ast.ImportClause { // FIXME: return type? +func (self *_parser) parseImportClause() *ast.ImportClause { switch self.token { case token.LEFT_BRACE: return &ast.ImportClause{NamedImports: self.parseNamedImports()} @@ -1271,7 +1255,7 @@ func (self *_parser) parseImportClause() *ast.ImportClause { // FIXME: return ty } func (self *_parser) parseModuleSpecifier() unistring.String { - expression := self.parsePrimaryExpression() // module specifier (string literal) + expression := self.parsePrimaryExpression() stringLiteral, ok := expression.(*ast.StringLiteral) if !ok { self.error(self.idx, "expected module specifier") @@ -1285,11 +1269,9 @@ func (self *_parser) parseImportedDefaultBinding() *ast.Identifier { return self.parseImportedBinding() } -// FIXME: return type? func (self *_parser) parseNameSpaceImport() *ast.NameSpaceImport { - self.expect(token.MULTIPLY) // * + self.expect(token.MULTIPLY) if self.literal != "as" { - // FAIL self.error(self.idx, "Expected 'as' keyword, found '%s' instead", self.literal) } self.next() @@ -1298,7 +1280,7 @@ func (self *_parser) parseNameSpaceImport() *ast.NameSpaceImport { } func (self *_parser) parseNamedImports() *ast.NamedImports { - _ = self.expect(token.LEFT_BRACE) // FIXME: should we do something with idx? + _ = self.expect(token.LEFT_BRACE) importsList := self.parseImportsList() From a3b018bf1c4841ddb59010303577b7c0989eff39 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 2 Aug 2022 15:07:31 +0300 Subject: [PATCH 093/124] Move modules' namespace in separate file --- modules.go | 180 ----------------------------------------- modules_namespace.go | 187 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 180 deletions(-) create mode 100644 modules_namespace.go diff --git a/modules.go b/modules.go index eefd1524..d1ffedf9 100644 --- a/modules.go +++ b/modules.go @@ -1,7 +1,6 @@ package goja import ( - "bytes" "errors" "fmt" "sort" @@ -758,182 +757,3 @@ func (c *cyclicModuleStub) SetRequestedModules(modules []string) { func (c *cyclicModuleStub) RequestedModules() []string { return c.requestedModules } - -type namespaceObject struct { - baseObject - m ModuleRecord - exports map[unistring.String]struct{} - exportsNames []unistring.String -} - -func (r *Runtime) NamespaceObjectFor(m ModuleRecord) *Object { - if r.moduleNamespaces == nil { - r.moduleNamespaces = make(map[ModuleRecord]*namespaceObject) - } - if o, ok := r.moduleNamespaces[m]; ok { - return o.val - } - o := r.createNamespaceObject(m) - r.moduleNamespaces[m] = o - return o.val -} - -func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { - o := &Object{runtime: r} - no := &namespaceObject{m: m} - no.val = o - no.extensible = true - no.defineOwnPropertySym(SymToStringTag, PropertyDescriptor{ - Value: newStringValue("Module"), - }, true) - no.extensible = false - o.self = no - no.init() - no.exports = make(map[unistring.String]struct{}) - - for _, exportName := range m.GetExportedNames() { - v, ambiguous := no.m.ResolveExport(exportName) - if ambiguous || v == nil { - continue - } - no.exports[unistring.NewFromString(exportName)] = struct{}{} - no.exportsNames = append(no.exportsNames, unistring.NewFromString(exportName)) - } - return no -} - -func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { - for name := range no.exports { - if !all { // TODO this seems off - _ = no.getOwnPropStr(name) - } - accum = append(accum, stringValueFromRaw(name)) - } - // TODO optimize thsi - sort.Slice(accum, func(i, j int) bool { - return accum[i].String() < accum[j].String() - }) - return accum -} - -type namespacePropIter struct { - no *namespaceObject - idx int -} - -func (no *namespaceObject) iterateStringKeys() iterNextFunc { - return (&namespacePropIter{ - no: no, - }).next -} - -func (no *namespaceObject) iterateKeys() iterNextFunc { - return no.iterateStringKeys() -} - -func (i *namespacePropIter) next() (propIterItem, iterNextFunc) { - for i.idx < len(i.no.exportsNames) { - name := i.no.exportsNames[i.idx] - i.idx++ - prop := i.no.getOwnPropStr(name) - if prop != nil { - return propIterItem{name: stringValueFromRaw(name), value: prop}, i.next - } - } - return propIterItem{}, nil -} - -func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { - if _, ok := no.exports[name]; !ok { - return nil - } - v, ambiguous := no.m.ResolveExport(name.String()) - if ambiguous || v == nil { - no.val.runtime.throwReferenceError((name)) - } - if v.BindingName == "*namespace*" { - return &valueProperty{ - value: no.val.runtime.NamespaceObjectFor(v.Module), - writable: true, - configurable: false, - enumerable: true, - } - } - - mi := no.val.runtime.modules[v.Module] - b := mi.GetBindingValue(unistring.NewFromString(v.BindingName)) - if b == nil { - // TODO figure this out - this is needed as otherwise valueproperty is thought to not have a value - // which isn't really correct in a particular test around isFrozen - b = _null - } - return &valueProperty{ - value: b, - writable: true, - configurable: false, - enumerable: true, - } -} - -func (no *namespaceObject) hasOwnPropertyStr(name unistring.String) bool { - _, ok := no.exports[name] - return ok -} - -func (no *namespaceObject) getStr(name unistring.String, receiver Value) Value { - prop := no.getOwnPropStr(name) - if prop, ok := prop.(*valueProperty); ok { - if receiver == nil { - return prop.get(no.val) - } - return prop.get(receiver) - } - return prop -} - -func (no *namespaceObject) setOwnStr(name unistring.String, val Value, throw bool) bool { - no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) - return false -} - -func (no *namespaceObject) deleteStr(name unistring.String, throw bool) bool { - if _, exists := no.exports[name]; exists { - no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) - return false - } - return true -} - -func (no *namespaceObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { - returnFalse := func() bool { - var buf bytes.Buffer - for _, stack := range no.val.runtime.CaptureCallStack(0, nil) { - stack.Write(&buf) - buf.WriteRune('\n') - } - if throw { - no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) - } - return false - } - if !no.hasOwnPropertyStr(name) { - return returnFalse() - } - if desc.Empty() { - return true - } - if desc.Writable == FLAG_FALSE { - return returnFalse() - } - if desc.Configurable == FLAG_TRUE { - return returnFalse() - } - if desc.Enumerable == FLAG_FALSE { - return returnFalse() - } - if desc.Value != nil && desc.Value != no.getOwnPropStr(name) { - return returnFalse() - } - // TODO more checks - return true -} diff --git a/modules_namespace.go b/modules_namespace.go new file mode 100644 index 00000000..bf161650 --- /dev/null +++ b/modules_namespace.go @@ -0,0 +1,187 @@ +package goja + +import ( + "bytes" + "sort" + + "github.com/dop251/goja/unistring" +) + +type namespaceObject struct { + baseObject + m ModuleRecord + exports map[unistring.String]struct{} + exportsNames []unistring.String +} + +func (r *Runtime) NamespaceObjectFor(m ModuleRecord) *Object { + if r.moduleNamespaces == nil { + r.moduleNamespaces = make(map[ModuleRecord]*namespaceObject) + } + if o, ok := r.moduleNamespaces[m]; ok { + return o.val + } + o := r.createNamespaceObject(m) + r.moduleNamespaces[m] = o + return o.val +} + +func (r *Runtime) createNamespaceObject(m ModuleRecord) *namespaceObject { + o := &Object{runtime: r} + no := &namespaceObject{m: m} + no.val = o + no.extensible = true + no.defineOwnPropertySym(SymToStringTag, PropertyDescriptor{ + Value: newStringValue("Module"), + }, true) + no.extensible = false + o.self = no + no.init() + no.exports = make(map[unistring.String]struct{}) + + for _, exportName := range m.GetExportedNames() { + v, ambiguous := no.m.ResolveExport(exportName) + if ambiguous || v == nil { + continue + } + no.exports[unistring.NewFromString(exportName)] = struct{}{} + no.exportsNames = append(no.exportsNames, unistring.NewFromString(exportName)) + } + return no +} + +func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { + for name := range no.exports { + if !all { // TODO this seems off + _ = no.getOwnPropStr(name) + } + accum = append(accum, stringValueFromRaw(name)) + } + // TODO optimize thsi + sort.Slice(accum, func(i, j int) bool { + return accum[i].String() < accum[j].String() + }) + return accum +} + +type namespacePropIter struct { + no *namespaceObject + idx int +} + +func (no *namespaceObject) iterateStringKeys() iterNextFunc { + return (&namespacePropIter{ + no: no, + }).next +} + +func (no *namespaceObject) iterateKeys() iterNextFunc { + return no.iterateStringKeys() +} + +func (i *namespacePropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.no.exportsNames) { + name := i.no.exportsNames[i.idx] + i.idx++ + prop := i.no.getOwnPropStr(name) + if prop != nil { + return propIterItem{name: stringValueFromRaw(name), value: prop}, i.next + } + } + return propIterItem{}, nil +} + +func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { + if _, ok := no.exports[name]; !ok { + return nil + } + v, ambiguous := no.m.ResolveExport(name.String()) + if ambiguous || v == nil { + no.val.runtime.throwReferenceError((name)) + } + if v.BindingName == "*namespace*" { + return &valueProperty{ + value: no.val.runtime.NamespaceObjectFor(v.Module), + writable: true, + configurable: false, + enumerable: true, + } + } + + mi := no.val.runtime.modules[v.Module] + b := mi.GetBindingValue(unistring.NewFromString(v.BindingName)) + if b == nil { + // TODO figure this out - this is needed as otherwise valueproperty is thought to not have a value + // which isn't really correct in a particular test around isFrozen + b = _null + } + return &valueProperty{ + value: b, + writable: true, + configurable: false, + enumerable: true, + } +} + +func (no *namespaceObject) hasOwnPropertyStr(name unistring.String) bool { + _, ok := no.exports[name] + return ok +} + +func (no *namespaceObject) getStr(name unistring.String, receiver Value) Value { + prop := no.getOwnPropStr(name) + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(no.val) + } + return prop.get(receiver) + } + return prop +} + +func (no *namespaceObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false +} + +func (no *namespaceObject) deleteStr(name unistring.String, throw bool) bool { + if _, exists := no.exports[name]; exists { + no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false + } + return true +} + +func (no *namespaceObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + returnFalse := func() bool { + var buf bytes.Buffer + for _, stack := range no.val.runtime.CaptureCallStack(0, nil) { + stack.Write(&buf) + buf.WriteRune('\n') + } + if throw { + no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + } + return false + } + if !no.hasOwnPropertyStr(name) { + return returnFalse() + } + if desc.Empty() { + return true + } + if desc.Writable == FLAG_FALSE { + return returnFalse() + } + if desc.Configurable == FLAG_TRUE { + return returnFalse() + } + if desc.Enumerable == FLAG_FALSE { + return returnFalse() + } + if desc.Value != nil && desc.Value != no.getOwnPropStr(name) { + return returnFalse() + } + // TODO more checks + return true +} From 9c468694bf18e003969ed353a57aea91b878c7b2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 2 Aug 2022 15:28:39 +0300 Subject: [PATCH 094/124] Some modules refactoring --- modules.go | 67 ++++++++++++++----------------------- modules_integration_test.go | 4 --- 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/modules.go b/modules.go index d1ffedf9..8803a2df 100644 --- a/modules.go +++ b/modules.go @@ -41,16 +41,6 @@ type CyclicModuleRecord interface { Instantiate() CyclicModuleInstance // TODO maybe should be taking the runtime } -type LinkedSourceModuleRecord struct{} - -func (c *compiler) CyclicModuleRecordConcreteLink(module ModuleRecord) error { - stack := []CyclicModuleRecord{} - if _, err := c.innerModuleLinking(newLinkState(), module, &stack, 0); err != nil { - return err - } - return nil -} - type linkState struct { status map[ModuleRecord]CyclicModuleRecordStatus dfsIndex map[ModuleRecord]uint @@ -65,18 +55,12 @@ func newLinkState() *linkState { } } -type evaluationState struct { - status map[ModuleInstance]CyclicModuleRecordStatus - dfsIndex map[ModuleInstance]uint - dfsAncestorIndex map[ModuleInstance]uint -} - -func newEvaluationState() *evaluationState { - return &evaluationState{ - status: make(map[ModuleInstance]CyclicModuleRecordStatus), - dfsIndex: make(map[ModuleInstance]uint), - dfsAncestorIndex: make(map[ModuleInstance]uint), +func (c *compiler) CyclicModuleRecordConcreteLink(module ModuleRecord) error { + stack := []CyclicModuleRecord{} + if _, err := c.innerModuleLinking(newLinkState(), module, &stack, 0); err != nil { + return err } + return nil } func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[]CyclicModuleRecord, index uint) (uint, error) { @@ -132,6 +116,20 @@ func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[ return index, nil } +type evaluationState struct { + status map[ModuleInstance]CyclicModuleRecordStatus + dfsIndex map[ModuleInstance]uint + dfsAncestorIndex map[ModuleInstance]uint +} + +func newEvaluationState() *evaluationState { + return &evaluationState{ + status: make(map[ModuleInstance]CyclicModuleRecordStatus), + dfsIndex: make(map[ModuleInstance]uint), + dfsAncestorIndex: make(map[ModuleInstance]uint), + } +} + func (r *Runtime) CyclicModuleRecordEvaluate(c ModuleRecord, resolve HostResolveImportedModuleFunc, ) (mi ModuleInstance, err error) { if r.modules == nil { @@ -181,7 +179,7 @@ func (r *Runtime) innerModuleEvaluation( *stack = append(*stack, c) var requiredModule ModuleRecord - for _, required := range c.RequestedModules() { + for _, required := range cr.RequestedModules() { requiredModule, err = resolve(m, required) if err != nil { return nil, 0, err @@ -223,7 +221,6 @@ type ( } CyclicModuleInstance interface { ModuleInstance - RequestedModules() []string ExecuteModule(*Runtime) (ModuleInstance, error) } ) @@ -233,7 +230,6 @@ var _ CyclicModuleRecord = &SourceTextModuleRecord{} var _ CyclicModuleInstance = &SourceTextModuleInstance{} type SourceTextModuleInstance struct { - cyclicModuleStub moduleRecord *SourceTextModuleRecord // TODO figure out omething less idiotic exportGetters map[unistring.String]func() Value @@ -253,11 +249,11 @@ func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String) Value } type SourceTextModuleRecord struct { - cyclicModuleStub body *ast.Program p *Program // context // importmeta + requestedModules []string importEntries []importEntry localExportEntries []exportEntry indirectExportEntries []exportEntry @@ -279,7 +275,7 @@ type exportEntry struct { importName string localName string - // no standard + // not standard lex bool } @@ -552,9 +548,7 @@ func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFun // realm isn't implement // environment is undefined // namespace is undefined - cyclicModuleStub: cyclicModuleStub{ - requestedModules: requestedModules, - }, + requestedModules: requestedModules, // hostDefined TODO body: body, // Context empty @@ -728,9 +722,6 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese func (module *SourceTextModuleRecord) Instantiate() CyclicModuleInstance { return &SourceTextModuleInstance{ - cyclicModuleStub: cyclicModuleStub{ - requestedModules: module.requestedModules, - }, moduleRecord: module, exportGetters: make(map[unistring.String]func() Value), } @@ -746,14 +737,6 @@ func (module *SourceTextModuleRecord) Link() error { return c.CyclicModuleRecordConcreteLink(module) } -type cyclicModuleStub struct { - requestedModules []string -} - -func (c *cyclicModuleStub) SetRequestedModules(modules []string) { - c.requestedModules = modules -} - -func (c *cyclicModuleStub) RequestedModules() []string { - return c.requestedModules +func (module *SourceTextModuleRecord) RequestedModules() []string { + return module.requestedModules } diff --git a/modules_integration_test.go b/modules_integration_test.go index bf341eb3..02241001 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -220,10 +220,6 @@ type cyclicModuleInstanceImpl struct { module *cyclicModuleImpl } -func (si *cyclicModuleInstanceImpl) RequestedModules() []string { - return si.module.RequestedModules() -} - func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.ModuleInstance, error) { si.rt = rt // TODO others From 17c74114f8e735f5ae20db291952b6372c8616b5 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 2 Aug 2022 17:27:26 +0300 Subject: [PATCH 095/124] Add import.meta support --- compiler.go | 10 ++++++ compiler_expr.go | 28 ++++++++++++++--- modules.go | 52 +++++++++++++++++++++++++++++++ modules_integration_test.go | 62 ++++++++++++++++++++++++++++++++++--- modules_namespace.go | 6 ---- parser/expression.go | 29 +++++++++++++++++ parser/statement.go | 25 ++++++++------- runtime.go | 4 +++ tc39_test.go | 1 - vm.go | 12 +++++++ 10 files changed, 202 insertions(+), 27 deletions(-) diff --git a/compiler.go b/compiler.go index 1bb8f1aa..0c3da85c 100644 --- a/compiler.go +++ b/compiler.go @@ -71,6 +71,8 @@ type Program struct { funcName unistring.String src *file.File srcMap []srcMapItem + + scriptOrModule interface{} } type compiler struct { @@ -89,6 +91,13 @@ type compiler struct { ctxVM *vm // VM in which an eval() code is compiled } +func (c *compiler) getScriptOrModule() interface{} { + if c.module != nil { + return c.module + } + return c.p // TODO figure comething better +} + type binding struct { scope *scope name unistring.String @@ -909,6 +918,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.hostResolveImportedModule = oldResolve }() in := module.body + c.p.scriptOrModule = module c.p.src = in.File strict := true inGlobal := false diff --git a/compiler_expr.go b/compiler_expr.go index 1f1fcc51..dd5a9739 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -156,6 +156,9 @@ type compiledNewTarget struct { baseCompiledExpr } +type compiledImportMeta struct { + baseCompiledExpr +} type compiledSequenceExpr struct { baseCompiledExpr sequence []compiledExpr @@ -1332,7 +1335,8 @@ func (e *compiledFunctionLiteral) compile() (prg *Program, name unistring.String savedPrg := e.c.p e.c.p = &Program{ - src: e.c.p.src, + src: e.c.p.src, + scriptOrModule: e.c.getScriptOrModule(), } e.c.newScope() s := e.c.scope @@ -2129,9 +2133,10 @@ func (e *compiledClassLiteral) compileFieldsAndStaticBlocks(elements []clsElemen } e.c.p = &Program{ - src: savedPrg.src, - funcName: funcName, - code: make([]instruction, 2, len(elements)*2+3), + src: savedPrg.src, + funcName: funcName, + code: make([]instruction, 2, len(elements)*2+3), + scriptOrModule: e.c.getScriptOrModule(), } e.c.newScope() @@ -2342,12 +2347,25 @@ func (e *compiledNewTarget) emitGetter(putOnStack bool) { } } +func (e *compiledImportMeta) emitGetter(putOnStack bool) { + if putOnStack { + e.addSrcMap() + e.c.emit(loadImportMeta) + } +} + func (c *compiler) compileMetaProperty(v *ast.MetaProperty) compiledExpr { - if v.Meta.Name == "new" || v.Property.Name != "target" { + if v.Meta.Name == "new" && v.Property.Name == "target" { r := &compiledNewTarget{} r.init(c, v.Idx0()) return r } + if v.Meta.Name == "import" && v.Property.Name == "meta" { + r := &compiledImportMeta{} + r.init(c, v.Idx0()) + return r + } + c.throwSyntaxError(int(v.Idx)-1, "Unsupported meta property: %s.%s", v.Meta.Name, v.Property.Name) return nil } diff --git a/modules.go b/modules.go index 8803a2df..aee2cf2b 100644 --- a/modules.go +++ b/modules.go @@ -740,3 +740,55 @@ func (module *SourceTextModuleRecord) Link() error { func (module *SourceTextModuleRecord) RequestedModules() []string { return module.requestedModules } + +func (r *Runtime) GetActiveScriptOrModule() interface{} { // have some better type + if r.vm.prg.scriptOrModule != nil { + return r.vm.prg.scriptOrModule + } + for i := len(r.vm.callStack) - 1; i >= 0; i-- { + prg := r.vm.callStack[i].prg + if prg.scriptOrModule != nil { + return prg.scriptOrModule + } + } + return nil +} + +func (r *Runtime) getImportMetaFor(m ModuleRecord) *Object { + if r.importMetas == nil { + r.importMetas = make(map[ModuleRecord]*Object) + } + if o, ok := r.importMetas[m]; ok { + return o + } + o := r.NewObject() + + var properties []MetaProperty + if r.getImportMetaProperties != nil { + properties = r.getImportMetaProperties(m) + } + + for _, property := range properties { + o.Set(property.Key, property.Value) + } + + if r.finalizeImportMeta != nil { + r.finalizeImportMeta(o, m) + } + + r.importMetas[m] = o + return o +} + +type MetaProperty struct { + Key string + Value Value +} + +func (r *Runtime) SetGetImportMetaProperties(fn func(ModuleRecord) []MetaProperty) { + r.getImportMetaProperties = fn +} + +func (r *Runtime) SetFinalImportMeta(fn func(*Object, ModuleRecord)) { + r.finalizeImportMeta = fn +} diff --git a/modules_integration_test.go b/modules_integration_test.go index 02241001..58f5ebda 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -12,9 +12,10 @@ import ( ) type simpleComboResolver struct { - cache map[string]cacheElement - fs fs.FS - custom func(interface{}, string) (goja.ModuleRecord, error) + cache map[string]cacheElement + reverseCache map[goja.ModuleRecord]string + fs fs.FS + custom func(interface{}, string) (goja.ModuleRecord, error) } type cacheElement struct { m goja.ModuleRecord @@ -22,7 +23,7 @@ type cacheElement struct { } func newSimpleComboResolver() *simpleComboResolver { - return &simpleComboResolver{cache: make(map[string]cacheElement)} + return &simpleComboResolver{cache: make(map[string]cacheElement), reverseCache: make(map[goja.ModuleRecord]string)} } func (s *simpleComboResolver) resolve(referencingScriptOrModule interface{}, specifier string) (goja.ModuleRecord, error) { @@ -46,6 +47,7 @@ func (s *simpleComboResolver) resolve(referencingScriptOrModule interface{}, spe return nil, err } s.cache[specifier] = cacheElement{m: p} + s.reverseCache[p] = specifier return p, nil } @@ -233,3 +235,55 @@ func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName unistring.String) } return si.rt.GetModuleInstance(b.Module).GetBindingValue(exportName) } + +func TestSourceMetaImport(t *testing.T) { + t.Parallel() + resolver := newSimpleComboResolver() + mapfs := make(fstest.MapFS) + mapfs["main.js"] = &fstest.MapFile{ + Data: []byte(` + import { meta } from "b.js" + + if (meta.url != "file:///b.js") { + throw "wrong url " + meta.url + " for b.js" + } + + if (import.meta.url != "file:///main.js") { + throw "wrong url " + import.meta.url + " for main.js" + } + `), + } + mapfs["b.js"] = &fstest.MapFile{ + Data: []byte(` + export var meta = import.meta + `), + } + resolver.fs = mapfs + m, err := resolver.resolve(nil, "main.js") + if err != nil { + t.Fatalf("got error %s", err) + } + p := m.(*goja.SourceTextModuleRecord) + + err = p.Link() + if err != nil { + t.Fatalf("got error %s", err) + } + vm := goja.New() + vm.SetGetImportMetaProperties(func(m goja.ModuleRecord) []goja.MetaProperty { + specifier, ok := resolver.reverseCache[m] + if !ok { + panic("we got import.meta for module that wasn't imported") + } + return []goja.MetaProperty{ + { + Key: "url", + Value: vm.ToValue("file:///" + specifier), + }, + } + }) + _, err = m.Evaluate(vm) + if err != nil { + t.Fatalf("got error %s", err) + } +} diff --git a/modules_namespace.go b/modules_namespace.go index bf161650..a920f82a 100644 --- a/modules_namespace.go +++ b/modules_namespace.go @@ -1,7 +1,6 @@ package goja import ( - "bytes" "sort" "github.com/dop251/goja/unistring" @@ -154,11 +153,6 @@ func (no *namespaceObject) deleteStr(name unistring.String, throw bool) bool { func (no *namespaceObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { returnFalse := func() bool { - var buf bytes.Buffer - for _, stack := range no.val.runtime.CaptureCallStack(0, nil) { - stack.Write(&buf) - buf.WriteRune('\n') - } if throw { no.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) } diff --git a/parser/expression.go b/parser/expression.go index a6c67948..1f8ac209 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -91,6 +91,12 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { return self.parseFunction(false) case token.CLASS: return self.parseClass(false) + case token.IMPORT: + if self.opts.module { + return self.parseImportMeta() + } + self.error(self.idx, "import not supported in script") + self.next() } if isBindingId(self.token, parsedLiteral) { @@ -106,6 +112,29 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { return &ast.BadExpression{From: idx, To: self.idx} } +func (self *_parser) parseImportMeta() *ast.MetaProperty { + idx := self.expect(token.IMPORT) + self.expect(token.PERIOD) + if self.literal == "meta" { + return &ast.MetaProperty{ + Meta: &ast.Identifier{ + Name: unistring.String(token.IMPORT.String()), + Idx: idx, + }, + Idx: idx, + Property: self.parseIdentifier(), + } + } + self.errorUnexpectedToken(self.token) + return &ast.MetaProperty{ + Meta: &ast.Identifier{ + Name: unistring.String(token.IMPORT.String()), + Idx: idx, + }, + Idx: idx, + } +} + func (self *_parser) parseSuperProperty() ast.Expression { idx := self.idx self.next() diff --git a/parser/statement.go b/parser/statement.go index 2d37686f..ad9da4f3 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -93,13 +93,13 @@ func (self *_parser) parseStatement() ast.Statement { return self.parseTryStatement() case token.EXPORT: if !allowImportExport { - self.next() self.error(self.idx, "export only allowed in global scope") + self.next() return &ast.BadStatement{From: self.idx, To: self.idx + 1} } if !self.opts.module { - self.next() self.error(self.idx, "export not supported in script") + self.next() return &ast.BadStatement{From: self.idx, To: self.idx + 1} } exp := self.parseExportDeclaration() @@ -109,20 +109,23 @@ func (self *_parser) parseStatement() ast.Statement { return exp } case token.IMPORT: - if !allowImportExport { - self.next() - self.error(self.idx, "import only allowed in global scope") - return &ast.BadStatement{From: self.idx, To: self.idx + 1} - } if !self.opts.module { - self.next() self.error(self.idx, "import not supported in script") + self.next() return &ast.BadStatement{From: self.idx, To: self.idx + 1} } - imp := self.parseImportDeclaration() - self.scope.importEntries = append(self.scope.importEntries, imp) + if self.peek() != token.PERIOD { + // this will be parsed as expression + if !allowImportExport { + self.error(self.idx, "import only allowed in global scope") + self.next() + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } + imp := self.parseImportDeclaration() + self.scope.importEntries = append(self.scope.importEntries, imp) - return imp + return imp + } } expression := self.parseExpression() diff --git a/runtime.go b/runtime.go index 3cbdd470..bc0d07ce 100644 --- a/runtime.go +++ b/runtime.go @@ -185,6 +185,10 @@ type Runtime struct { modules map[ModuleRecord]ModuleInstance moduleNamespaces map[ModuleRecord]*namespaceObject + importMetas map[ModuleRecord]*Object + + getImportMetaProperties func(ModuleRecord) []MetaProperty + finalizeImportMeta func(*Object, ModuleRecord) jobQueue []func() diff --git a/tc39_test.go b/tc39_test.go index 8dd70bb4..7e05de18 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -238,7 +238,6 @@ var ( "import-assertions", "dynamic-import", "logical-assignment-operators", - "import.meta", "Atomics", "Atomics.waitAsync", "FinalizationRegistry", diff --git a/vm.go b/vm.go index 6de8b8c4..5fc12fb3 100644 --- a/vm.go +++ b/vm.go @@ -4386,6 +4386,18 @@ func (_loadNewTarget) exec(vm *vm) { vm.pc++ } +type _loadImportMeta struct{} + +var loadImportMeta _loadImportMeta + +func (_loadImportMeta) exec(vm *vm) { + // https://262.ecma-international.org/12.0/#sec-meta-properties-runtime-semantics-evaluation + t := vm.r.GetActiveScriptOrModule() + m := t.(ModuleRecord) // There should be now way for this to have compiled + vm.push(vm.r.getImportMetaFor(m)) + vm.pc++ +} + type _typeof struct{} var typeof _typeof From 1917e747b0a093175b787007e93f6452455cacde Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 2 Aug 2022 17:47:27 +0300 Subject: [PATCH 096/124] fix parser tests --- parser/parser_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parser/parser_test.go b/parser/parser_test.go index 2ef2c4d9..3590d464 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -428,7 +428,7 @@ func TestParserErr(t *testing.T) { test("abc.enum = 1", nil) test("var enum;", "(anonymous): Line 1:5 Unexpected reserved word") - test("export", "(anonymous): Line 1:7 export not supported in script") + test("export", "(anonymous): Line 1:1 export not supported in script") test("abc.export = 1", nil) test("var export;", "(anonymous): Line 1:5 Unexpected token export") @@ -436,7 +436,7 @@ func TestParserErr(t *testing.T) { test("abc.extends = 1", nil) test("var extends;", "(anonymous): Line 1:5 Unexpected token extends") - test("import", "(anonymous): Line 1:7 import not supported in script") + test("import", "(anonymous): Line 1:1 import not supported in script") test("abc.import = 1", nil) test("var import;", "(anonymous): Line 1:5 Unexpected token import") @@ -453,7 +453,7 @@ func TestParserErr(t *testing.T) { test("{a: 1,}", "(anonymous): Line 1:7 Unexpected token }") test("{a: 1, b: 2}", "(anonymous): Line 1:9 Unexpected token :") test("{a: 1, b: 2,}", "(anonymous): Line 1:9 Unexpected token :") - test(`let f = () => new import('');`, "(anonymous): Line 1:19 Unexpected token import") + test(`let f = () => new import('');`, "(anonymous): Line 1:19 import not supported in script") } From 822c8e2bd156f20006b8c9a6d8a46408def3d03e Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 11 Aug 2022 16:19:04 +0300 Subject: [PATCH 097/124] Fix usage of exports before the module is evaluated This does require quiter the hacks ... but otherwise I have no idea how to do it without even more changes to goja --- compiler.go | 2 ++ modules.go | 26 ++++++++++++++------- modules_integration_test.go | 6 ++--- modules_test.go | 7 ++++++ runtime.go | 46 +++++++++++++++++++++++++++++++++++++ vm.go | 24 +++++++++++++++++++ 6 files changed, 100 insertions(+), 11 deletions(-) diff --git a/compiler.go b/compiler.go index 28bac1b0..49733e04 100644 --- a/compiler.go +++ b/compiler.go @@ -1106,6 +1106,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if !inGlobal || ownVarScope { c.compileFunctions(funcs) } + c.emit(notReallyYield) // this to stop us execute once after we initialize globals + // TODO figure something better :grimacing: c.compileStatements(in.Body, true) if enter != nil { c.leaveScopeBlock(enter) diff --git a/modules.go b/modules.go index aee2cf2b..b06fdc63 100644 --- a/modules.go +++ b/modules.go @@ -38,7 +38,7 @@ type CyclicModuleRecord interface { ModuleRecord RequestedModules() []string InitializeEnvironment() error - Instantiate() CyclicModuleInstance // TODO maybe should be taking the runtime + Instantiate(rt *Runtime) (CyclicModuleInstance, error) } type linkState struct { @@ -163,7 +163,11 @@ func (r *Runtime) innerModuleEvaluation( if ok { return mi, index, nil } - c = cr.Instantiate() + c, err = cr.Instantiate(r) + if err != nil { + return nil, index, err + } + mi = c r.modules[m] = c } @@ -221,7 +225,7 @@ type ( } CyclicModuleInstance interface { ModuleInstance - ExecuteModule(*Runtime) (ModuleInstance, error) + ExecuteModule(*Runtime) (CyclicModuleInstance, error) } ) @@ -233,10 +237,12 @@ type SourceTextModuleInstance struct { moduleRecord *SourceTextModuleRecord // TODO figure out omething less idiotic exportGetters map[unistring.String]func() Value + context *context // hacks haxx + stack valueStack } -func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (ModuleInstance, error) { - _, err := rt.RunProgram(s.moduleRecord.p) +func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (CyclicModuleInstance, error) { + _, err := rt.continueRunProgram(s.moduleRecord.p, s.context, s.stack) return s, err } @@ -720,11 +726,15 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese return starResolution, false } -func (module *SourceTextModuleRecord) Instantiate() CyclicModuleInstance { - return &SourceTextModuleInstance{ +func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInstance, error) { + mi := &SourceTextModuleInstance{ moduleRecord: module, exportGetters: make(map[unistring.String]func() Value), } + rt.modules[module] = mi + // TODO figure a better way + _, err := rt.RunProgram(mi.moduleRecord.p) + return mi, err } func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { @@ -742,7 +752,7 @@ func (module *SourceTextModuleRecord) RequestedModules() []string { } func (r *Runtime) GetActiveScriptOrModule() interface{} { // have some better type - if r.vm.prg.scriptOrModule != nil { + if r.vm.prg != nil && r.vm.prg.scriptOrModule != nil { return r.vm.prg.scriptOrModule } for i := len(r.vm.callStack) - 1; i >= 0; i-- { diff --git a/modules_integration_test.go b/modules_integration_test.go index 58f5ebda..1a38dd69 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -179,8 +179,8 @@ func (s *cyclicModuleImpl) InitializeEnvironment() error { return nil } -func (s *cyclicModuleImpl) Instantiate() goja.CyclicModuleInstance { - return &cyclicModuleInstanceImpl{module: s} +func (s *cyclicModuleImpl) Instantiate(_ *goja.Runtime) (goja.CyclicModuleInstance, error) { + return &cyclicModuleInstanceImpl{module: s}, nil } func (s *cyclicModuleImpl) RequestedModules() []string { @@ -222,7 +222,7 @@ type cyclicModuleInstanceImpl struct { module *cyclicModuleImpl } -func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.ModuleInstance, error) { +func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.CyclicModuleInstance, error) { si.rt = rt // TODO others return nil, nil diff --git a/modules_test.go b/modules_test.go index 63fe77e1..685697b4 100644 --- a/modules_test.go +++ b/modules_test.go @@ -72,6 +72,13 @@ globalThis.s = b() b: `function f() {return 5;}; export { f as default };`, }, + "export usage before evaluation as": { + a: `import "dep.js"; + export function a() {return 5;} +`, + b: `import { a } from "a.js"; + globalThis.s = a();`, + }, } for name, cases := range testCases { a, b := cases.a, cases.b diff --git a/runtime.go b/runtime.go index bade78f0..64292abe 100644 --- a/runtime.go +++ b/runtime.go @@ -1431,6 +1431,52 @@ func (r *Runtime) RunProgram(p *Program) (result Value, err error) { return } +// RunProgram executes a pre-compiled (see Compile()) code in the global context. +func (r *Runtime) continueRunProgram(_ *Program, context *context, stack valueStack) (result Value, err error) { + defer func() { + if x := recover(); x != nil { + if ex, ok := x.(*uncatchableException); ok { + err = ex.err + if len(r.vm.callStack) == 0 { + r.leaveAbrupt() + } + } else { + panic(x) + } + } + }() + vm := r.vm + recursive := false + if len(vm.callStack) > 0 { + recursive = true + vm.pushCtx() + vm.stash = &r.global.stash + vm.sb = vm.sp - 1 + } + vm.result = _undefined + vm.restoreCtx(context) + vm.stack = stack + // fmt.Println("continue sb ", vm.sb, vm.callStack) + // fmt.Println("stack at continue", vm.stack) + ex := vm.runTry() + if ex == nil { + result = r.vm.result + } else { + err = ex + } + if recursive { + vm.popCtx() + vm.halt = false + vm.clearStack() + } else { + vm.stack = nil + vm.prg = nil + vm.funcName = "" + r.leave() + } + return +} + // CaptureCallStack appends the current call stack frames to the stack slice (which may be nil) up to the specified depth. // The most recent frame will be the first one. // If depth <= 0 or more than the number of available frames, returns the entire stack. diff --git a/vm.go b/vm.go index 902eac14..9b2ef5cc 100644 --- a/vm.go +++ b/vm.go @@ -248,6 +248,7 @@ type vm struct { pc int stack valueStack sp, sb, args int + oldsp int // haxx stash *stash privEnv *privateEnv @@ -556,6 +557,8 @@ func (vm *vm) try(f func()) (ex *Exception) { ctxOffset := len(vm.callStack) sp := vm.sp + oldsp := vm.oldsp + vm.oldsp = sp iterLen := len(vm.iterStack) refLen := len(vm.refStack) @@ -565,6 +568,7 @@ func (vm *vm) try(f func()) (ex *Exception) { vm.callStack = vm.callStack[:ctxOffset] vm.restoreCtx(&ctx) vm.sp = sp + vm.oldsp = oldsp // Restore other stacks iterTail := vm.iterStack[iterLen:] @@ -1422,6 +1426,26 @@ func (_halt) exec(vm *vm) { vm.pc++ } +type _notReallyYield struct{} + +var notReallyYield _notReallyYield + +func (_notReallyYield) exec(vm *vm) { + vm.halt = true + vm.pc++ + mi := vm.r.modules[vm.r.GetActiveScriptOrModule().(ModuleRecord)].(*SourceTextModuleInstance) + if vm.sp > vm.oldsp { + stack := make(valueStack, vm.sp-vm.oldsp-1) + _ = copy(stack, vm.stack[vm.oldsp:]) + // fmt.Println("yield sb ", vm.sb, vm.args, stack) + mi.stack = stack + } + // fmt.Println("stack at yield", vm.stack) + context := &context{} + vm.saveCtx(context) + mi.context = context +} + type jump int32 func (j jump) exec(vm *vm) { From 8164826e42e5e006f52d197af2f5672bea272151 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 12 Aug 2022 16:10:26 +0300 Subject: [PATCH 098/124] dynamic import :tada: --- ast/node.go | 140 ++++++------ builtin_reflect.go | 3 +- compiler_expr.go | 17 +- modules.go | 1 + parser/expression.go | 45 +++- parser/statement.go | 27 ++- runtime.go | 3 + tc39_test.go | 533 ++++++++++++++++++++++++++++++------------- vm.go | 53 ++++- 9 files changed, 572 insertions(+), 250 deletions(-) diff --git a/ast/node.go b/ast/node.go index 43e3b60f..79077ecc 100644 --- a/ast/node.go +++ b/ast/node.go @@ -275,6 +275,9 @@ type ( SuperExpression struct { Idx file.Idx } + DynamicImportExpression struct { + Idx file.Idx + } UnaryExpression struct { Operator token.Token @@ -291,35 +294,36 @@ type ( // _expressionNode -func (*ArrayLiteral) _expressionNode() {} -func (*AssignExpression) _expressionNode() {} -func (*BadExpression) _expressionNode() {} -func (*BinaryExpression) _expressionNode() {} -func (*BooleanLiteral) _expressionNode() {} -func (*BracketExpression) _expressionNode() {} -func (*CallExpression) _expressionNode() {} -func (*ConditionalExpression) _expressionNode() {} -func (*DotExpression) _expressionNode() {} -func (*PrivateDotExpression) _expressionNode() {} -func (*FunctionLiteral) _expressionNode() {} -func (*ClassLiteral) _expressionNode() {} -func (*ArrowFunctionLiteral) _expressionNode() {} -func (*Identifier) _expressionNode() {} -func (*NewExpression) _expressionNode() {} -func (*NullLiteral) _expressionNode() {} -func (*NumberLiteral) _expressionNode() {} -func (*ObjectLiteral) _expressionNode() {} -func (*RegExpLiteral) _expressionNode() {} -func (*SequenceExpression) _expressionNode() {} -func (*StringLiteral) _expressionNode() {} -func (*TemplateLiteral) _expressionNode() {} -func (*ThisExpression) _expressionNode() {} -func (*SuperExpression) _expressionNode() {} -func (*UnaryExpression) _expressionNode() {} -func (*MetaProperty) _expressionNode() {} -func (*ObjectPattern) _expressionNode() {} -func (*ArrayPattern) _expressionNode() {} -func (*Binding) _expressionNode() {} +func (*ArrayLiteral) _expressionNode() {} +func (*AssignExpression) _expressionNode() {} +func (*BadExpression) _expressionNode() {} +func (*BinaryExpression) _expressionNode() {} +func (*BooleanLiteral) _expressionNode() {} +func (*BracketExpression) _expressionNode() {} +func (*CallExpression) _expressionNode() {} +func (*ConditionalExpression) _expressionNode() {} +func (*DotExpression) _expressionNode() {} +func (*PrivateDotExpression) _expressionNode() {} +func (*FunctionLiteral) _expressionNode() {} +func (*ClassLiteral) _expressionNode() {} +func (*ArrowFunctionLiteral) _expressionNode() {} +func (*Identifier) _expressionNode() {} +func (*NewExpression) _expressionNode() {} +func (*NullLiteral) _expressionNode() {} +func (*NumberLiteral) _expressionNode() {} +func (*ObjectLiteral) _expressionNode() {} +func (*RegExpLiteral) _expressionNode() {} +func (*SequenceExpression) _expressionNode() {} +func (*StringLiteral) _expressionNode() {} +func (*TemplateLiteral) _expressionNode() {} +func (*ThisExpression) _expressionNode() {} +func (*SuperExpression) _expressionNode() {} +func (*DynamicImportExpression) _expressionNode() {} +func (*UnaryExpression) _expressionNode() {} +func (*MetaProperty) _expressionNode() {} +func (*ObjectPattern) _expressionNode() {} +func (*ArrayPattern) _expressionNode() {} +func (*Binding) _expressionNode() {} func (*PropertyShort) _expressionNode() {} func (*PropertyKeyed) _expressionNode() {} @@ -692,34 +696,35 @@ type Program struct { // Idx0 // // ==== // -func (self *ArrayLiteral) Idx0() file.Idx { return self.LeftBracket } -func (self *ArrayPattern) Idx0() file.Idx { return self.LeftBracket } -func (self *ObjectPattern) Idx0() file.Idx { return self.LeftBrace } -func (self *AssignExpression) Idx0() file.Idx { return self.Left.Idx0() } -func (self *BadExpression) Idx0() file.Idx { return self.From } -func (self *BinaryExpression) Idx0() file.Idx { return self.Left.Idx0() } -func (self *BooleanLiteral) Idx0() file.Idx { return self.Idx } -func (self *BracketExpression) Idx0() file.Idx { return self.Left.Idx0() } -func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() } -func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() } -func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() } -func (self *PrivateDotExpression) Idx0() file.Idx { return self.Left.Idx0() } -func (self *FunctionLiteral) Idx0() file.Idx { return self.Function } -func (self *ClassLiteral) Idx0() file.Idx { return self.Class } -func (self *ArrowFunctionLiteral) Idx0() file.Idx { return self.Start } -func (self *Identifier) Idx0() file.Idx { return self.Idx } -func (self *NewExpression) Idx0() file.Idx { return self.New } -func (self *NullLiteral) Idx0() file.Idx { return self.Idx } -func (self *NumberLiteral) Idx0() file.Idx { return self.Idx } -func (self *ObjectLiteral) Idx0() file.Idx { return self.LeftBrace } -func (self *RegExpLiteral) Idx0() file.Idx { return self.Idx } -func (self *SequenceExpression) Idx0() file.Idx { return self.Sequence[0].Idx0() } -func (self *StringLiteral) Idx0() file.Idx { return self.Idx } -func (self *TemplateLiteral) Idx0() file.Idx { return self.OpenQuote } -func (self *ThisExpression) Idx0() file.Idx { return self.Idx } -func (self *SuperExpression) Idx0() file.Idx { return self.Idx } -func (self *UnaryExpression) Idx0() file.Idx { return self.Idx } -func (self *MetaProperty) Idx0() file.Idx { return self.Idx } +func (self *ArrayLiteral) Idx0() file.Idx { return self.LeftBracket } +func (self *ArrayPattern) Idx0() file.Idx { return self.LeftBracket } +func (self *ObjectPattern) Idx0() file.Idx { return self.LeftBrace } +func (self *AssignExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *BadExpression) Idx0() file.Idx { return self.From } +func (self *BinaryExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *BooleanLiteral) Idx0() file.Idx { return self.Idx } +func (self *BracketExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() } +func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() } +func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *PrivateDotExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *FunctionLiteral) Idx0() file.Idx { return self.Function } +func (self *ClassLiteral) Idx0() file.Idx { return self.Class } +func (self *ArrowFunctionLiteral) Idx0() file.Idx { return self.Start } +func (self *Identifier) Idx0() file.Idx { return self.Idx } +func (self *NewExpression) Idx0() file.Idx { return self.New } +func (self *NullLiteral) Idx0() file.Idx { return self.Idx } +func (self *NumberLiteral) Idx0() file.Idx { return self.Idx } +func (self *ObjectLiteral) Idx0() file.Idx { return self.LeftBrace } +func (self *RegExpLiteral) Idx0() file.Idx { return self.Idx } +func (self *SequenceExpression) Idx0() file.Idx { return self.Sequence[0].Idx0() } +func (self *StringLiteral) Idx0() file.Idx { return self.Idx } +func (self *TemplateLiteral) Idx0() file.Idx { return self.OpenQuote } +func (self *ThisExpression) Idx0() file.Idx { return self.Idx } +func (self *SuperExpression) Idx0() file.Idx { return self.Idx } +func (self *DynamicImportExpression) Idx0() file.Idx { return self.Idx } +func (self *UnaryExpression) Idx0() file.Idx { return self.Idx } +func (self *MetaProperty) Idx0() file.Idx { return self.Idx } func (self *BadStatement) Idx0() file.Idx { return self.From } func (self *BlockStatement) Idx0() file.Idx { return self.LeftBrace } @@ -790,16 +795,17 @@ func (self *NewExpression) Idx1() file.Idx { return self.Callee.Idx1() } } -func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null" -func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace + 1 } -func (self *ObjectPattern) Idx1() file.Idx { return self.RightBrace + 1 } -func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() } -func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *TemplateLiteral) Idx1() file.Idx { return self.CloseQuote + 1 } -func (self *ThisExpression) Idx1() file.Idx { return self.Idx + 4 } -func (self *SuperExpression) Idx1() file.Idx { return self.Idx + 5 } +func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null" +func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ObjectPattern) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() } +func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *TemplateLiteral) Idx1() file.Idx { return self.CloseQuote + 1 } +func (self *ThisExpression) Idx1() file.Idx { return self.Idx + 4 } +func (self *SuperExpression) Idx1() file.Idx { return self.Idx + 5 } +func (self *DynamicImportExpression) Idx1() file.Idx { return self.Idx + 6 } func (self *UnaryExpression) Idx1() file.Idx { if self.Postfix { return self.Operand.Idx1() + 2 // ++ -- diff --git a/builtin_reflect.go b/builtin_reflect.go index 1df45618..b3a39367 100644 --- a/builtin_reflect.go +++ b/builtin_reflect.go @@ -3,7 +3,8 @@ package goja func (r *Runtime) builtin_reflect_apply(call FunctionCall) Value { return r.toCallable(call.Argument(0))(FunctionCall{ This: call.Argument(1), - Arguments: r.createListFromArrayLike(call.Argument(2))}) + Arguments: r.createListFromArrayLike(call.Argument(2)), + }) } func (r *Runtime) toConstructor(v Value) func(args []Value, newTarget *Object) *Object { diff --git a/compiler_expr.go b/compiler_expr.go index 63894966..59b2e25b 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -220,6 +220,9 @@ type compiledOptional struct { baseCompiledExpr expr compiledExpr } +type compiledDynamicImport struct { + baseCompiledExpr +} func (e *defaultDeleteExpr) emitGetter(putOnStack bool) { e.expr.emitGetter(false) @@ -3142,10 +3145,16 @@ func (c *compiler) compileCallee(v ast.Expression) compiledExpr { c.throwSyntaxError(int(v.Idx0())-1, "'super' keyword unexpected here") panic("unreachable") } + if imp, ok := v.(*ast.DynamicImportExpression); ok { + r := &compiledDynamicImport{} + r.init(c, imp.Idx) + return r + } return c.compileExpression(v) } func (c *compiler) compileCallExpression(v *ast.CallExpression) compiledExpr { + // fmt.Printf("%+v %+v %T\n", v, v.Callee, v.Callee) args := make([]compiledExpr, len(v.ArgumentList)) isVariadic := false for i, argExpr := range v.ArgumentList { @@ -3240,8 +3249,6 @@ func (c *compiler) compileBooleanLiteral(v *ast.BooleanLiteral) compiledExpr { } func (c *compiler) compileAssignExpression(v *ast.AssignExpression) compiledExpr { - // log.Printf("compileAssignExpression(): %+v", v) - r := &compiledAssignExpr{ left: c.compileExpression(v.Left), right: c.compileExpression(v.Right), @@ -3540,3 +3547,9 @@ func (e *compiledOptional) emitGetter(putOnStack bool) { e.c.emit(nil) } } + +func (e *compiledDynamicImport) emitGetter(putOnStack bool) { + if putOnStack { + e.c.emit(dynamicImport) + } +} diff --git a/modules.go b/modules.go index b06fdc63..3c491ff3 100644 --- a/modules.go +++ b/modules.go @@ -772,6 +772,7 @@ func (r *Runtime) getImportMetaFor(m ModuleRecord) *Object { return o } o := r.NewObject() + o.SetPrototype(nil) var properties []MetaProperty if r.getImportMetaProperties != nil { diff --git a/parser/expression.go b/parser/expression.go index 1f8ac209..9595b0c3 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -91,12 +91,6 @@ func (self *_parser) parsePrimaryExpression() ast.Expression { return self.parseFunction(false) case token.CLASS: return self.parseClass(false) - case token.IMPORT: - if self.opts.module { - return self.parseImportMeta() - } - self.error(self.idx, "import not supported in script") - self.next() } if isBindingId(self.token, parsedLiteral) { @@ -635,7 +629,7 @@ func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, i return } -func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression { +func (self *_parser) parseCallExpression(left ast.Expression) *ast.CallExpression { argumentList, idx0, idx1 := self.parseArgumentList() return &ast.CallExpression{ Callee: left, @@ -717,6 +711,14 @@ func (self *_parser) parseNewExpression() ast.Expression { bad.From = idx return bad } + + if call, ok := callee.(*ast.CallExpression); ok { + if _, ok := call.Callee.(*ast.DynamicImportExpression); ok { + self.error(idx, "You can't use new with import()") + return &ast.BadExpression{From: idx, To: self.idx} + } + } + node := &ast.NewExpression{ New: idx, Callee: callee, @@ -734,6 +736,8 @@ func (self *_parser) parseLeftHandSideExpression() ast.Expression { var left ast.Expression if self.token == token.NEW { left = self.parseNewExpression() + } else if self.token == token.IMPORT { + left = self.parseImportExpression() } else { left = self.parsePrimaryExpression() } @@ -754,6 +758,31 @@ L: return left } +func (self *_parser) parseImportExpression() ast.Expression { + idx := self.idx + if self.peek() == token.LEFT_PARENTHESIS { + self.expect(token.IMPORT) + cexp := self.parseCallExpression(&ast.DynamicImportExpression{}) + if len(cexp.ArgumentList) != 1 { + self.error(self.idx, "dynamic import requires exactly one argument") + return &ast.BadExpression{From: idx, To: self.idx} + } + + if _, ok := cexp.ArgumentList[0].(*ast.SpreadElement); ok { + self.error(self.idx, "dynamic import can't use spread list") + return &ast.BadExpression{From: idx, To: self.idx} + } + + return cexp + } + if self.opts.module { + return self.parseImportMeta() + } + self.error(self.idx, "import not supported in script") + self.next() + return &ast.BadExpression{From: idx, To: self.idx} +} + func (self *_parser) parseLeftHandSideExpressionAllowCall() ast.Expression { allowIn := self.scope.allowIn self.scope.allowIn = true @@ -765,6 +794,8 @@ func (self *_parser) parseLeftHandSideExpressionAllowCall() ast.Expression { start := self.idx if self.token == token.NEW { left = self.parseNewExpression() + } else if self.token == token.IMPORT { + left = self.parseImportExpression() } else { left = self.parsePrimaryExpression() } diff --git a/parser/statement.go b/parser/statement.go index ad9da4f3..cabb2dd8 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -109,26 +109,29 @@ func (self *_parser) parseStatement() ast.Statement { return exp } case token.IMPORT: - if !self.opts.module { - self.error(self.idx, "import not supported in script") - self.next() - return &ast.BadStatement{From: self.idx, To: self.idx + 1} - } - if self.peek() != token.PERIOD { - // this will be parsed as expression - if !allowImportExport { - self.error(self.idx, "import only allowed in global scope") + if self.peek() != token.LEFT_PARENTHESIS { + if !self.opts.module { + self.error(self.idx, "import not supported in script") self.next() return &ast.BadStatement{From: self.idx, To: self.idx + 1} } - imp := self.parseImportDeclaration() - self.scope.importEntries = append(self.scope.importEntries, imp) + if self.peek() != token.PERIOD { + // this will be parsed as expression + if !allowImportExport { + self.error(self.idx, "import only allowed in global scope") + self.next() + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } + imp := self.parseImportDeclaration() + self.scope.importEntries = append(self.scope.importEntries, imp) - return imp + return imp + } } } expression := self.parseExpression() + // spew.Dump(expression) if identifier, isIdentifier := expression.(*ast.Identifier); isIdentifier && self.token == token.COLON { // LabelledStatement diff --git a/runtime.go b/runtime.go index 64292abe..36eb4d35 100644 --- a/runtime.go +++ b/runtime.go @@ -188,6 +188,7 @@ type Runtime struct { getImportMetaProperties func(ModuleRecord) []MetaProperty finalizeImportMeta func(*Object, ModuleRecord) + importModuleDynamically func(interface{}, Value, interface{}) jobQueue []func() @@ -1454,8 +1455,10 @@ func (r *Runtime) continueRunProgram(_ *Program, context *context, stack valueSt vm.sb = vm.sp - 1 } vm.result = _undefined + // sb := vm.sb vm.restoreCtx(context) vm.stack = stack + // vm.sb = sb // fmt.Println("continue sb ", vm.sb, vm.callStack) // fmt.Println("stack at continue", vm.stack) ex := vm.runTry() diff --git a/tc39_test.go b/tc39_test.go index 7e05de18..7ef689aa 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -75,114 +75,271 @@ var ( "test/language/literals/regexp/S7.8.5_A2.4_T2.js": true, // generators - "test/language/module-code/instn-local-bndng-gen.js": true, - "test/language/module-code/instn-local-bndng-export-gen.js": true, - "test/language/module-code/instn-star-props-nrml.js": true, - "test/language/module-code/namespace/internals/get-nested-namespace-props-nrml.js": true, + "test/language/module-code/instn-local-bndng-gen.js": true, + "test/language/module-code/instn-local-bndng-export-gen.js": true, + "test/language/module-code/instn-star-props-nrml.js": true, + "test/language/module-code/namespace/internals/get-nested-namespace-props-nrml.js": true, + "test/language/expressions/dynamic-import/namespace/promise-then-ns-get-nested-namespace-props-nrml.js": true, // async - "test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true, - "test/language/statements/switch/scope-lex-generator.js": true, - "test/language/expressions/in/rhs-yield-present.js": true, - "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-yield-expression.js": true, - "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-generator-function-declaration.js": true, - "test/built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js": true, - "test/built-ins/Object/seal/seal-generatorfunction.js": true, - "test/language/statements/class/syntax/class-declaration-computed-method-generator-definition.js": true, - "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-yield-expression.js": true, - "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-generator-function-declaration.js": true, - "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-yield-expression.js": true, - "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-generator-function-declaration.js": true, - "test/language/statements/class/cpn-class-decl-computed-property-name-from-yield-expression.js": true, - "test/language/statements/class/cpn-class-decl-computed-property-name-from-generator-function-declaration.js": true, - "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-yield-expression.js": true, - "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-generator-function-declaration.js": true, - "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-yield-expression.js": true, - "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-generator-function-declaration.js": true, - "test/language/expressions/class/cpn-class-expr-computed-property-name-from-yield-expression.js": true, - "test/language/expressions/class/cpn-class-expr-computed-property-name-from-generator-function-declaration.js": true, - "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-yield-expression.js": true, - "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-yield-expression.js": true, - "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-generator-function-declaration.js": true, - "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-generator-function-declaration.js": true, - "test/language/statements/class/static-init-arguments-methods.js": true, - "test/language/statements/class/static-init-arguments-functions.js": true, - "test/language/expressions/object/method-definition/static-init-await-reference-generator.js": true, - "test/language/expressions/generators/static-init-await-binding.js": true, - "test/language/expressions/generators/static-init-await-reference.js": true, - "test/language/expressions/optional-chaining/member-expression.js": true, - "test/language/expressions/class/elements/private-generator-method-name.js": true, - "test/language/statements/class/elements/private-generator-method-name.js": true, - "test/language/expressions/in/private-field-rhs-yield-present.js": true, - "test/language/expressions/class/elements/private-static-generator-method-name.js": true, - "test/language/expressions/class/elements/private-static-async-generator-method-name.js": true, - "test/language/computed-property-names/class/static/generator-prototype.js": true, - "test/language/computed-property-names/class/method/constructor-can-be-generator.js": true, - "test/language/computed-property-names/class/static/generator-constructor.js": true, - "test/language/computed-property-names/class/method/generator.js": true, - "test/language/computed-property-names/object/method/generator.js": true, - "test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js": true, - "test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js": true, - - // async - "test/language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": true, - "test/language/statements/switch/scope-lex-async-generator.js": true, - "test/language/statements/switch/scope-lex-async-function.js": true, - "test/language/statements/for-of/head-lhs-async-invalid.js": true, - "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-await-expression.js": true, - "test/language/statements/async-function/evaluation-body.js": true, - "test/language/expressions/object/method-definition/object-method-returns-promise.js": true, - "test/language/expressions/object/method-definition/async-super-call-param.js": true, - "test/language/expressions/object/method-definition/async-super-call-body.js": true, - "test/built-ins/Object/seal/seal-asyncgeneratorfunction.js": true, - "test/built-ins/Object/seal/seal-asyncfunction.js": true, - "test/built-ins/Object/seal/seal-asyncarrowfunction.js": true, - "test/language/statements/for/head-init-async-of.js": true, - "test/language/reserved-words/await-module.js": true, - "test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js": true, - "test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js": true, - "test/language/expressions/optional-chaining/member-expression-async-this.js": true, - "test/language/expressions/optional-chaining/member-expression-async-literal.js": true, - "test/language/expressions/optional-chaining/member-expression-async-identifier.js": true, - "test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true, - "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/statements/class/cpn-class-decl-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/expressions/class/cpn-class-expr-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-async-arrow-function-expression.js": true, - "test/language/statements/let/static-init-await-binding-invalid.js": true, - "test/language/statements/labeled/static-init-invalid-await.js": true, - "test/language/statements/variable/dstr/obj-ptrn-elem-id-static-init-await-invalid.js": true, - "test/language/statements/variable/static-init-await-binding-invalid.js": true, - "test/language/statements/variable/dstr/ary-ptrn-elem-id-static-init-await-invalid.js": true, - "test/language/statements/try/static-init-await-binding-invalid.js": true, - "test/language/statements/function/static-init-await-binding-invalid.js": true, - "test/language/statements/const/static-init-await-binding-invalid.js": true, - "test/language/statements/class/static-init-await-binding-invalid.js": true, - "test/language/identifier-resolution/static-init-invalid-await.js": true, - "test/language/expressions/class/static-init-await-binding.js": true, - "test/language/expressions/class/heritage-async-arrow-function.js": true, - "test/language/expressions/arrow-function/static-init-await-reference.js": true, - "test/language/expressions/arrow-function/static-init-await-binding.js": true, - "test/language/expressions/object/method-definition/static-init-await-binding-generator.js": true, - "test/language/expressions/object/identifier-shorthand-static-init-await-invalid.js": true, - "test/language/expressions/class/heritage-arrow-function.js": true, - "test/language/expressions/class/elements/private-async-generator-method-name.js": true, - "test/language/expressions/class/elements/private-async-method-name.js": true, - "test/language/statements/class/elements/private-async-generator-method-name.js": true, - "test/language/statements/class/elements/private-async-method-name.js": true, - "test/language/statements/labeled/value-await-module.js": true, - "test/language/statements/labeled/value-await-module-escaped.js": true, - "test/language/expressions/in/private-field-rhs-await-present.js": true, - "test/language/expressions/class/elements/private-static-async-method-name.js": true, - "test/language/statements/class/class-name-ident-await-module.js": true, - "test/language/statements/class/class-name-ident-await-escaped-module.js": true, - "test/language/expressions/class/class-name-ident-await-module.js": true, - "test/language/expressions/class/class-name-ident-await-escaped-module.js": true, + "test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true, + "test/language/statements/switch/scope-lex-generator.js": true, + "test/language/expressions/in/rhs-yield-present.js": true, + "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-yield-expression.js": true, + "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-generator-function-declaration.js": true, + "test/built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js": true, + "test/built-ins/Object/seal/seal-generatorfunction.js": true, + "test/language/statements/class/syntax/class-declaration-computed-method-generator-definition.js": true, + "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-yield-expression.js": true, + "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-generator-function-declaration.js": true, + "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-yield-expression.js": true, + "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-generator-function-declaration.js": true, + "test/language/statements/class/cpn-class-decl-computed-property-name-from-yield-expression.js": true, + "test/language/statements/class/cpn-class-decl-computed-property-name-from-generator-function-declaration.js": true, + "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-yield-expression.js": true, + "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-generator-function-declaration.js": true, + "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-yield-expression.js": true, + "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-generator-function-declaration.js": true, + "test/language/expressions/class/cpn-class-expr-computed-property-name-from-yield-expression.js": true, + "test/language/expressions/class/cpn-class-expr-computed-property-name-from-generator-function-declaration.js": true, + "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-yield-expression.js": true, + "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-yield-expression.js": true, + "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-generator-function-declaration.js": true, + "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-generator-function-declaration.js": true, + "test/language/statements/class/static-init-arguments-methods.js": true, + "test/language/statements/class/static-init-arguments-functions.js": true, + "test/language/expressions/object/method-definition/static-init-await-reference-generator.js": true, + "test/language/expressions/generators/static-init-await-binding.js": true, + "test/language/expressions/generators/static-init-await-reference.js": true, + "test/language/expressions/optional-chaining/member-expression.js": true, + "test/language/expressions/class/elements/private-generator-method-name.js": true, + "test/language/statements/class/elements/private-generator-method-name.js": true, + "test/language/expressions/in/private-field-rhs-yield-present.js": true, + "test/language/expressions/class/elements/private-static-generator-method-name.js": true, + "test/language/expressions/class/elements/private-static-async-generator-method-name.js": true, + "test/language/computed-property-names/class/static/generator-prototype.js": true, + "test/language/computed-property-names/class/method/constructor-can-be-generator.js": true, + "test/language/computed-property-names/class/static/generator-constructor.js": true, + "test/language/computed-property-names/class/method/generator.js": true, + "test/language/computed-property-names/object/method/generator.js": true, + "test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js": true, + "test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js": true, + "test/language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": true, + "test/language/statements/switch/scope-lex-async-generator.js": true, + "test/language/statements/switch/scope-lex-async-function.js": true, + "test/language/statements/for-of/head-lhs-async-invalid.js": true, + "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-await-expression.js": true, + "test/language/statements/async-function/evaluation-body.js": true, + "test/language/expressions/object/method-definition/object-method-returns-promise.js": true, + "test/language/expressions/object/method-definition/async-super-call-param.js": true, + "test/language/expressions/object/method-definition/async-super-call-body.js": true, + "test/built-ins/Object/seal/seal-asyncgeneratorfunction.js": true, + "test/built-ins/Object/seal/seal-asyncfunction.js": true, + "test/built-ins/Object/seal/seal-asyncarrowfunction.js": true, + "test/language/statements/for/head-init-async-of.js": true, + "test/language/reserved-words/await-module.js": true, + "test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js": true, + "test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js": true, + "test/language/expressions/optional-chaining/member-expression-async-this.js": true, + "test/language/expressions/optional-chaining/member-expression-async-literal.js": true, + "test/language/expressions/optional-chaining/member-expression-async-identifier.js": true, + "test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true, + "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/statements/class/cpn-class-decl-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/expressions/class/cpn-class-expr-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-async-arrow-function-expression.js": true, + "test/language/statements/let/static-init-await-binding-invalid.js": true, + "test/language/statements/labeled/static-init-invalid-await.js": true, + "test/language/statements/variable/dstr/obj-ptrn-elem-id-static-init-await-invalid.js": true, + "test/language/statements/variable/static-init-await-binding-invalid.js": true, + "test/language/statements/variable/dstr/ary-ptrn-elem-id-static-init-await-invalid.js": true, + "test/language/statements/try/static-init-await-binding-invalid.js": true, + "test/language/statements/function/static-init-await-binding-invalid.js": true, + "test/language/statements/const/static-init-await-binding-invalid.js": true, + "test/language/statements/class/static-init-await-binding-invalid.js": true, + "test/language/identifier-resolution/static-init-invalid-await.js": true, + "test/language/expressions/class/static-init-await-binding.js": true, + "test/language/expressions/class/heritage-async-arrow-function.js": true, + "test/language/expressions/arrow-function/static-init-await-reference.js": true, + "test/language/expressions/arrow-function/static-init-await-binding.js": true, + "test/language/expressions/object/method-definition/static-init-await-binding-generator.js": true, + "test/language/expressions/object/identifier-shorthand-static-init-await-invalid.js": true, + "test/language/expressions/class/heritage-arrow-function.js": true, + "test/language/expressions/class/elements/private-async-generator-method-name.js": true, + "test/language/expressions/class/elements/private-async-method-name.js": true, + "test/language/statements/class/elements/private-async-generator-method-name.js": true, + "test/language/statements/class/elements/private-async-method-name.js": true, + "test/language/statements/labeled/value-await-module.js": true, + "test/language/statements/labeled/value-await-module-escaped.js": true, + "test/language/expressions/in/private-field-rhs-await-present.js": true, + "test/language/expressions/class/elements/private-static-async-method-name.js": true, + "test/language/statements/class/class-name-ident-await-module.js": true, + "test/language/statements/class/class-name-ident-await-escaped-module.js": true, + "test/language/expressions/class/class-name-ident-await-module.js": true, + "test/language/expressions/class/class-name-ident-await-escaped-module.js": true, + "test/language/expressions/dynamic-import/for-await-resolution-and-error-agen-yield.js": true, + "test/language/expressions/dynamic-import/2nd-param-await-ident.js": true, + "test/language/expressions/dynamic-import/for-await-resolution-and-error.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-assignment-expr-not-optional.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-no-new-call-expression.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-await-nested-imports.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-return-await-trailing-comma-second.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-gen-await-empty-str-is-valid-assign-expr.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-return-await-script-code-valid.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-return-await-trailing-comma-first.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-gen-await-trailing-comma-second.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-await-trailing-comma-second.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-await-script-code-valid.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-return-await-empty-str-is-valid-assign-expr.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-await-empty-str-is-valid-assign-expr.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-return-await-nested-imports.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-return-await-trailing-comma-first.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-await-trailing-comma-first.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-return-await-nested-imports.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-gen-await-script-code-valid.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-await-script-code-valid.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-await-nested-imports.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-return-await-empty-str-is-valid-assign-expr.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-gen-await-trailing-comma-first.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-arrow-function-await-trailing-comma-second.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-await-trailing-comma-first.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-return-await-trailing-comma-second.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-gen-await-nested-imports.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-await-empty-str-is-valid-assign-expr.js": true, + "test/language/expressions/dynamic-import/syntax/valid/nested-async-function-return-await-script-code-valid.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-assignment-expr-not-optional.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-assignment-expr-not-optional.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-no-new-call-expression.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-assignment-expr-not-optional.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-not-extensible-args.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-not-extensible-args.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-no-rest-param.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-not-extensible-args.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-no-rest-param.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-not-extensible-args.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-no-new-call-expression.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-not-extensible-args.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-no-rest-param.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-no-new-call-expression.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-assignment-expr-not-optional.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-no-rest-param.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-no-new-call-expression.js": true, + "test/language/expressions/dynamic-import/update-to-dynamic-import.js": true, + "test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-no-rest-param.js": true, + "test/language/expressions/dynamic-import/eval-rqstd-once.js": true, + "test/language/expressions/dynamic-import/for-await-resolution-and-error-agen.js": true, + "test/language/expressions/dynamic-import/imported-self-update.js": true, + "test/language/expressions/dynamic-import/indirect-resolution.js": true, + "test/language/expressions/dynamic-import/assignment-expression/identifier.js": true, + "test/language/expressions/dynamic-import/assignment-expression/await-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/call-expr-arguments.js": true, + "test/language/expressions/dynamic-import/assignment-expression/additive-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/array-literal.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-eval-rqstd-abrupt-typeerror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-eval-rqstd-abrupt-urierror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-instn-iee-err-circular.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-delete-exported-init-strict.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-eval-script-code-target.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-file-does-not-exist.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-eval-rqstd-abrupt-typeerror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-instn-iee-err-circular.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-set-same-values-strict.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-nested-namespace-props-nrml.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-eval-script-code-target.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-eval-rqstd-abrupt-typeerror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-file-does-not-exist.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-has-property-str-found-init.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-no-iterator.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-file-does-not-exist.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-nested-namespace-dflt-direct.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-extensible.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-delete-exported-init-no-strict.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-own-property-str-not-found.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-own-property-keys-sort.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-has-property-sym-found.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-set-prototype-of-null.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-set-no-strict.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-define-own-property.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-prototype.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-specifier-tostring-abrupt-rejects.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-nested-namespace-dflt-indirect.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-str-not-found.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-prevent-extensions-object.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-delete-non-exported-strict.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-delete-non-exported-no-strict.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-set-prototype-of.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-has-property-sym-not-found.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-str-found.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-set-same-values-no-strict.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-eval-script-code-target.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-specifier-tostring-abrupt-rejects.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-eval-rqstd-abrupt-typeerror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-instn-iee-err-ambiguous-import.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-own-property-str-found-init.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-instn-iee-err-circular.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-eval-rqstd-abrupt-urierror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-instn-iee-err-ambiguous-import.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-own-property-sym.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-specifier-tostring-abrupt-rejects.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-instn-iee-err-ambiguous-import.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-specifier-tostring-abrupt-rejects.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-file-does-not-exist.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-file-does-not-exist.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-instn-iee-err-circular.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-prop-descs.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-sym-not-found.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-instn-iee-err-circular.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-eval-rqstd-abrupt-typeerror.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-has-property-str-not-found.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-prevent-extensions-reflect.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-file-does-not-exist.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-eval-rqstd-abrupt-urierror.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-get-sym-found.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-instn-iee-err-ambiguous-import.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-eval-rqstd-abrupt-typeerror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-eval-rqstd-abrupt-urierror.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-Symbol-toStringTag.js": true, + "test/language/expressions/dynamic-import/namespace/await-ns-set-strict.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-eval-script-code-target.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-specifier-tostring-abrupt-rejects.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-await-eval-rqstd-abrupt-urierror.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-eval-script-code-target.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-instn-iee-err-circular.js": true, + "test/language/expressions/dynamic-import/assignment-expression/ternary.js": true, + "test/language/expressions/dynamic-import/assignment-expression/logical-or-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/lhs-assign-operator-assign-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/arrow-function.js": true, + "test/language/expressions/dynamic-import/assignment-expression/lhs-eq-assign-expr-nostrict.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-await-specifier-tostring-abrupt-rejects.js": true, + "test/language/expressions/dynamic-import/assignment-expression/lhs-eq-assign-expr.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-await-eval-rqstd-abrupt-urierror.js": true, + "test/language/expressions/dynamic-import/assignment-expression/object-literal.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-arrow-function-return-await-eval-script-code-target.js": true, + "test/language/expressions/dynamic-import/assignment-expression/cover-parenthesized-expr.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-gen-return-await-instn-iee-err-ambiguous-import.js": true, + "test/language/expressions/dynamic-import/assignment-expression/yield-assign-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/tagged-function-call.js": true, + "test/language/expressions/dynamic-import/assignment-expression/new-target.js": true, + "test/language/expressions/dynamic-import/assignment-expression/yield-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/this.js": true, + "test/language/expressions/dynamic-import/assignment-expression/member-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/await-identifier.js": true, + "test/language/expressions/dynamic-import/assignment-expression/cover-call-expr.js": true, + "test/language/expressions/dynamic-import/catch/nested-async-function-return-await-instn-iee-err-ambiguous-import.js": true, + "test/language/expressions/dynamic-import/assignment-expression/call-expr-identifier.js": true, + "test/language/expressions/dynamic-import/assignment-expression/call-expr-expr.js": true, + "test/language/expressions/dynamic-import/assignment-expression/logical-and-expr.js": true, + "test/language/expressions/dynamic-import/await-import-evaluation.js": true, + "test/language/expressions/dynamic-import/assignment-expression/yield-identifier.js": true, + "test/language/expressions/dynamic-import/custom-primitive.js": true, + "test/language/expressions/dynamic-import/eval-self-once-module.js": true, + "test/language/expressions/dynamic-import/eval-self-once-script.js": true, + "test/language/expressions/dynamic-import/2nd-param-await-expr.js": true, // legacy number literals "test/language/literals/numeric/non-octal-decimal-integer.js": true, @@ -217,6 +374,9 @@ var ( // Left-hand side as a CoverParenthesizedExpression "test/language/expressions/assignment/fn-name-lhs-cover.js": true, + + // 'new' with import + "test/language/expressions/dynamic-import/syntax/valid/new-covered-expression-is-valid.js": true, } featuresBlackList = []string{ @@ -236,7 +396,6 @@ var ( "tail-call-optimization", "Temporal", "import-assertions", - "dynamic-import", "logical-assignment-operators", "Atomics", "Atomics.waitAsync", @@ -296,6 +455,9 @@ func init() { "test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-", "test/language/statements/class/elements/multiple-definitions-rs-static-async-", "test/language/expressions/class/elements/multiple-definitions-rs-static-async-", + "test/language/expressions/dynamic-import/catch/nested-async-", + "test/language/expressions/dynamic-import/usage/nested-async-", + "test/language/expressions/dynamic-import/syntax/valid/nested-async-", // generators "test/language/eval-code/direct/gen-", @@ -321,6 +483,7 @@ func init() { "test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-generator-", "test/language/statements/class/elements/multiple-definitions-rs-static-generator-", "test/language/expressions/class/elements/multiple-definitions-rs-static-generator-", + "test/language/expressions/dynamic-import/assignment-expression/yield-", // BigInt "test/built-ins/TypedArrayConstructors/BigUint64Array/", @@ -410,6 +573,15 @@ func (m *tc39Meta) hasFlag(flag string) bool { return false } +func (m *tc39Meta) hasFeature(feature string) bool { + for _, f := range m.Features { + if f == feature { + return true + } + } + return false +} + func parseTC39File(name string) (*tc39Meta, string, error) { f, err := os.Open(name) if err != nil { @@ -483,7 +655,86 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. vm.Set("IgnorableTestError", ignorableTestError) vm.RunProgram(ctx.sabStub) var out []string + eventLoopQueue := make(chan func(), 2) // the most basic and likely buggy event loop async := meta.hasFlag("async") + + type cacheElement struct { + m ModuleRecord + err error + } + cache := make(map[string]cacheElement) + mx := sync.Mutex{} + + var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) + hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { + mx.Lock() + defer mx.Unlock() + fname := path.Join(ctx.base, path.Dir(name), specifier) + k, ok := cache[fname] + if ok { + return k.m, k.err + } + f, err := os.Open(fname) + if err != nil { + cache[fname] = cacheElement{err: err} + return nil, err + } + defer f.Close() + + b, err := ioutil.ReadAll(f) + if err != nil { + cache[fname] = cacheElement{err: err} + return nil, err + } + + str := string(b) + p, err := ParseModule(fname, str, hostResolveImportedModule) + if err != nil { + cache[fname] = cacheElement{err: err} + return nil, err + } + cache[fname] = cacheElement{m: p} + return p, nil + } + + dynamicImport := meta.hasFeature("dynamic-import") + if dynamicImport { + vm.importModuleDynamically = func(referencingScriptOrModule interface{}, specifierValue Value, pcap interface{}) { + specifier := specifierValue.String() + // TODO have this be a lot less stupid + p := pcap.(*promiseCapability) // FIX + go func() { + m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) + + eventLoopQueue <- func() { + defer vm.RunString("") // haxx + if err == nil { + err = m.Link() + if err == nil { + _, err = m.Evaluate(vm) + } + } + if err != nil { + // fmt.Printf("err %T %+v", err, err) + // TODO figure how to this more centralized + switch x1 := err.(type) { + case *Exception: + p.reject(x1.val) + case *CompilerSyntaxError: + p.reject(vm.builtin_new(vm.global.SyntaxError, []Value{newStringValue(x1.Error())})) + case *CompilerReferenceError: + p.reject(vm.newError(vm.global.ReferenceError, x1.Message)) + default: + p.reject(vm.ToValue(err)) + } + return + } + // finalize + p.resolve(vm.NamespaceObjectFor(m)) + } + }() + } + } if async { err := ctx.runFile(ctx.base, path.Join("harness", "doneprintHandle.js"), vm) if err != nil { @@ -499,7 +750,7 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. var err error var early bool if meta.hasFlag("module") { - err, early = ctx.runTC39Module(name, src, meta.Includes, vm) + err, early = ctx.runTC39Module(name, src, meta.Includes, vm, hostResolveImportedModule) } else { err, early = ctx.runTC39Script(name, src, meta.Includes, vm) } @@ -553,7 +804,7 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. } } else { if meta.Negative.Type != "" { - vm.vm.prg.dumpCode(t.Logf) + // vm.vm.prg.dumpCode(t.Logf) t.Fatalf("%s: Expected error: %v", name, err) } } @@ -566,19 +817,27 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. t.Fatalf("iter stack is not empty: %d", l) } if async { - complete := false - for _, line := range out { - if strings.HasPrefix(line, "Test262:AsyncTestFailure:") { - t.Fatal(line) - } else if line == "Test262:AsyncTestComplete" { - complete = true + for { + complete := false + for _, line := range out { + if strings.HasPrefix(line, "Test262:AsyncTestFailure:") { + t.Fatal(line) + } else if line == "Test262:AsyncTestComplete" { + complete = true + } + } + if complete { + return } - } - if !complete { for _, line := range out { t.Log(line) } - t.Fatal("Test262:AsyncTestComplete was not printed") + select { + case fn := <-eventLoopQueue: + fn() + case <-time.After(time.Millisecond * 5000): + t.Fatal("nothing happened in 500ms :(") + } } } } @@ -689,7 +948,7 @@ func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error { return err } -func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *Runtime) (err error, early bool) { +func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *Runtime, hostResolveImportedModule HostResolveImportedModuleFunc) (err error, early bool) { early = true err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm) if err != nil { @@ -707,42 +966,6 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R return } } - type cacheElement struct { - m ModuleRecord - err error - } - cache := make(map[string]cacheElement) - - var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) - hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { - fname := path.Join(ctx.base, path.Dir(name), specifier) - k, ok := cache[fname] - if ok { - return k.m, k.err - } - f, err := os.Open(fname) - if err != nil { - cache[fname] = cacheElement{err: err} - return nil, err - } - defer f.Close() - - b, err := ioutil.ReadAll(f) - if err != nil { - cache[fname] = cacheElement{err: err} - return nil, err - } - - str := string(b) - p, err := ParseModule(fname, str, hostResolveImportedModule) - if err != nil { - cache[fname] = cacheElement{err: err} - return nil, err - } - cache[fname] = cacheElement{m: p} - return p, nil - } - m, err := hostResolveImportedModule(nil, path.Base(name)) if err != nil { return diff --git a/vm.go b/vm.go index 9b2ef5cc..c509b7e9 100644 --- a/vm.go +++ b/vm.go @@ -493,6 +493,13 @@ func (vm *vm) run() { if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { break } + /* + fmt.Printf("code: ") + for _, code := range vm.prg.code[vm.pc:] { + fmt.Printf("{%T: %#v},", code, code) + } + fmt.Print("\n") + //*/ vm.prg.code[vm.pc].exec(vm) ticks++ if ticks > 10000 { @@ -808,6 +815,7 @@ func (l loadStackLex) exec(vm *vm) { } else { p = &vm.stack[vm.sb+vm.args+int(l)] } + // fmt.Println(vm.stack, vm.sb, vm.args, l, p, *p) if *p == nil { panic(errAccessBeforeInit) } @@ -1435,10 +1443,10 @@ func (_notReallyYield) exec(vm *vm) { vm.pc++ mi := vm.r.modules[vm.r.GetActiveScriptOrModule().(ModuleRecord)].(*SourceTextModuleInstance) if vm.sp > vm.oldsp { - stack := make(valueStack, vm.sp-vm.oldsp-1) - _ = copy(stack, vm.stack[vm.oldsp:]) - // fmt.Println("yield sb ", vm.sb, vm.args, stack) - mi.stack = stack + toCopy := vm.stack[vm.oldsp:] + mi.stack = make(valueStack, len(toCopy)) + _ = copy(mi.stack, toCopy) + // fmt.Println("yield sb ", vm.sb, vm.args, mi.stack) } // fmt.Println("stack at yield", vm.stack) context := &context{} @@ -2647,7 +2655,8 @@ func (s setGlobalStrict) exec(vm *vm) { type loadIndirect func(vm *vm) Value func (g loadIndirect) exec(vm *vm) { - vm.push(nilSafe(g(vm))) + v := nilSafe(g(vm)) + vm.push(v) vm.pc++ } @@ -4415,13 +4424,45 @@ type _loadImportMeta struct{} var loadImportMeta _loadImportMeta func (_loadImportMeta) exec(vm *vm) { - // https://262.ecma-international.org/12.0/#sec-meta-properties-runtime-semantics-evaluation + // https://262.ecma-international.org/13.0/#sec-meta-properties-runtime-semantics-evaluation t := vm.r.GetActiveScriptOrModule() m := t.(ModuleRecord) // There should be now way for this to have compiled vm.push(vm.r.getImportMetaFor(m)) vm.pc++ } +type _loadDynamicImport struct{} + +var dynamicImport _loadDynamicImport + +func (_loadDynamicImport) exec(vm *vm) { + // https://262.ecma-international.org/13.0/#sec-import-call-runtime-semantics-evaluation + vm.push(vm.r.ToValue(func(specifier Value) Value { // TODO remove this function + t := vm.r.GetActiveScriptOrModule() + + pcap := vm.r.newPromiseCapability(vm.r.global.Promise) + var specifierStr valueString + err := vm.r.runWrapped(func() { + specifierStr = specifier.toString() + }) + if err != nil { + if ex, ok := err.(*Exception); ok { + pcap.reject(vm.r.ToValue(ex.val)) + } else { + pcap.reject(vm.r.ToValue(err)) + } + } else { + if vm.r.importModuleDynamically == nil { + pcap.reject(asciiString("dynamic modules not enabled in the host program")) + } else { + vm.r.importModuleDynamically(t, specifierStr, pcap) + } + } + return pcap.promise + })) + vm.pc++ +} + type _typeof struct{} var typeof _typeof From 48efda292f766193226f926c57ed28b93aa04d23 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 15 Aug 2022 18:51:04 +0300 Subject: [PATCH 099/124] fix parser test --- parser/parser_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser_test.go b/parser/parser_test.go index 3590d464..10a93eb2 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -453,7 +453,7 @@ func TestParserErr(t *testing.T) { test("{a: 1,}", "(anonymous): Line 1:7 Unexpected token }") test("{a: 1, b: 2}", "(anonymous): Line 1:9 Unexpected token :") test("{a: 1, b: 2,}", "(anonymous): Line 1:9 Unexpected token :") - test(`let f = () => new import('');`, "(anonymous): Line 1:19 import not supported in script") + test(`let f = () => new import('');`, "(anonymous): Line 1:15 You can't use new with import()") } From 135e8501cc88948d3d2919eaa1e03ef8fc740f8d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 16 Aug 2022 12:17:10 +0300 Subject: [PATCH 100/124] refactor --- compiler.go | 87 ++++++++++++++++++------------------------------ compiler_stmt.go | 39 ++++++++++------------ modules.go | 6 ++-- 3 files changed, 51 insertions(+), 81 deletions(-) diff --git a/compiler.go b/compiler.go index 49733e04..1fefa806 100644 --- a/compiler.go +++ b/compiler.go @@ -959,11 +959,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if v == nil || ambiguous { c.compileAmbiguousImport(unistring.NewFromString(in.importName)) } - } - // scope.module = module - // module.scope = scope - // 15.2.1.17.4 step 9 start + for _, in := range module.importEntries { importedModule, err := c.hostResolveImportedModule(module, in.moduleRequest) if err != nil { @@ -980,7 +977,6 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.createImmutableBinding(unistring.NewFromString(in.localName), true) } } - // 15.2.1.17.4 step 9 end funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) numFuncs := len(scope.bindings) @@ -1041,17 +1037,11 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { b.markAccessPoint() exportName := unistring.NewFromString(entry.localName) - lex := entry.lex || !scope.boundNames[exportName].isVar callback := func(vm *vm, getter func() Value) { - m := vm.r.modules[module] - - if s, ok := m.(*SourceTextModuleInstance); !ok { - vm.r.throwReferenceError(exportName) // TODO fix - } else { - s.exportGetters[exportName] = getter - } + vm.r.modules[module].(*SourceTextModuleInstance).exportGetters[exportName] = getter } - if lex { + + if entry.lex || !scope.boundNames[exportName].isVar { c.emit(exportLex{callback: callback}) } else { c.emit(export{callback: callback}) @@ -1076,14 +1066,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.emit(exportIndirect{callback: func(vm *vm) { m := vm.r.modules[module] m2 := vm.r.modules[b.Module] - - if s, ok := m.(*SourceTextModuleInstance); !ok { - vm.r.throwReferenceError(exportName) // TODO fix - } else { - s.exportGetters[exportName] = func() Value { - v := m2.GetBindingValue(importName) - return v - } + m.(*SourceTextModuleInstance).exportGetters[exportName] = func() Value { + return m2.GetBindingValue(importName) } }}) } @@ -1106,8 +1090,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if !inGlobal || ownVarScope { c.compileFunctions(funcs) } - c.emit(notReallyYield) // this to stop us execute once after we initialize globals - // TODO figure something better :grimacing: + // TODO figure something better 😬 + c.emit(notReallyYield) // this to stop us execute once after we initialize globals c.compileStatements(in.Body, true) if enter != nil { c.leaveScopeBlock(enter) @@ -1372,8 +1356,6 @@ func (c *compiler) createImmutableBinding(name unistring.String, isConst bool) * b, _ := c.scope.bindName(name) b.inStash = true b.isConst = isConst - // b.isVar = true // TODO figure out if this needs to be true at some point - // b.isStrict = isStrict return b } @@ -1427,37 +1409,32 @@ func (c *compiler) createLexicalBindings(lex *ast.LexicalDeclaration) { } func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclared bool) bool { + declareScope := func() { + if !scopeDeclared { + c.newBlockScope() + scopeDeclared = true + } + } for _, st := range list { - if lex, ok := st.(*ast.LexicalDeclaration); ok { - if !scopeDeclared { - c.newBlockScope() - scopeDeclared = true - } + switch lex := st.(type) { + case *ast.LexicalDeclaration: + declareScope() c.createLexicalBindings(lex) - } else if cls, ok := st.(*ast.ClassDeclaration); ok { - if !scopeDeclared { - c.newBlockScope() - scopeDeclared = true - } - c.createLexicalIdBinding(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1) - } else if exp, ok := st.(*ast.ExportDeclaration); ok && exp.LexicalDeclaration != nil { - // TODO refactor - if !scopeDeclared { - c.newBlockScope() - scopeDeclared = true - } - c.createLexicalBindings(exp.LexicalDeclaration) - } else if exp, ok := st.(*ast.ExportDeclaration); ok && exp.ClassDeclaration != nil { - // TODO refactor - if !scopeDeclared { - c.newBlockScope() - scopeDeclared = true - } - cls := exp.ClassDeclaration - if exp.IsDefault { - c.createLexicalIdBinding("default", false, int(exp.Idx0())-1) - } else { - c.createLexicalIdBinding(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1) + case *ast.ClassDeclaration: + declareScope() + c.createLexicalIdBinding(lex.Class.Name.Name, false, int(lex.Class.Name.Idx)-1) + case *ast.ExportDeclaration: + if lex.LexicalDeclaration != nil { + declareScope() + c.createLexicalBindings(lex.LexicalDeclaration) + } else if lex.ClassDeclaration != nil { + declareScope() + if lex.IsDefault { + c.createLexicalIdBinding("default", false, int(lex.Idx0())-1) + } else { + cls := lex.ClassDeclaration + c.createLexicalIdBinding(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1) + } } } } diff --git a/compiler_stmt.go b/compiler_stmt.go index 6c4ff22a..9a570e4c 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -52,8 +52,7 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) { c.compileWithStatement(v, needResult) case *ast.DebuggerStatement: case *ast.ImportDeclaration: - // c.compileImportDeclaration(v) - // TODO explain this maybe do something in here as well ? + // this is already done, earlier case *ast.ExportDeclaration: c.compileExportDeclaration(v) default: @@ -785,22 +784,21 @@ func (c *compiler) emitVarAssign(name unistring.String, offset int, init compile } func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { - // module := c.module // the compiler.module might be different at execution of this - // fmt.Printf("Export %#v\n", expr) - if expr.Variable != nil { + switch { + case expr.Variable != nil: c.compileVariableStatement(expr.Variable) - } else if expr.LexicalDeclaration != nil { + case expr.LexicalDeclaration != nil: c.compileLexicalDeclaration(expr.LexicalDeclaration) - } else if expr.ClassDeclaration != nil { + case expr.ClassDeclaration != nil: cls := expr.ClassDeclaration if expr.IsDefault { c.emitLexicalAssign("default", int(cls.Class.Class)-1, c.compileClassLiteral(cls.Class, false)) } else { c.compileClassDeclaration(cls) } - } else if expr.HoistableDeclaration != nil { - // already done - } else if assign := expr.AssignExpression; assign != nil { + case expr.HoistableDeclaration != nil: // already handled + case expr.AssignExpression != nil: + assign := expr.AssignExpression c.compileLexicalDeclaration(&ast.LexicalDeclaration{ Idx: assign.Idx0(), Token: token.CONST, @@ -814,14 +812,13 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { }, }, }) - } else if expr.ExportFromClause != nil { + case expr.ExportFromClause != nil: from := expr.ExportFromClause module, err := c.hostResolveImportedModule(c.module, expr.FromClause.ModuleSpecifier.String()) if err != nil { - // TODO this should in practice never happen c.throwSyntaxError(int(expr.Idx0()), err.Error()) } - if from.NamedExports == nil { // star export -nothing to do + if from.NamedExports == nil { // star export - nothing to do return } for _, name := range from.NamedExports.ExportsList { @@ -841,8 +838,7 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { } identifier := name.IdentifierName // name will be reused in the for loop localB.getIndirect = func(vm *vm) Value { - m := vm.r.modules[module] - return m.GetBindingValue(identifier) + return vm.r.modules[module].GetBindingValue(identifier) } } } @@ -850,16 +846,15 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if expr.FromClause == nil { - return // TODO is this right? + return // import "specifier"; } module, err := c.hostResolveImportedModule(c.module, expr.FromClause.ModuleSpecifier.String()) if err != nil { - // TODO this should in practice never happen c.throwSyntaxError(int(expr.Idx0()), err.Error()) } if expr.ImportClause != nil { if namespace := expr.ImportClause.NameSpaceImport; namespace != nil { - idx := expr.Idx // TODO fix + idx := expr.Idx c.emitLexicalAssign( namespace.ImportedBinding, int(idx), @@ -874,7 +869,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { for _, name := range named.ImportsList { value, ambiguous := module.ResolveExport(name.IdentifierName.String()) - if ambiguous || value == nil { // also ambiguous + if ambiguous || value == nil { continue // ambiguous import already reports } n := name.Alias @@ -882,7 +877,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { n = name.IdentifierName } if value.BindingName == "*namespace*" { - idx := expr.Idx // TODO fix + idx := expr.Idx c.emitLexicalAssign( n, int(idx), @@ -911,7 +906,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if def := expr.ImportClause.ImportedDefaultBinding; def != nil { value, ambiguous := module.ResolveExport("default") - if ambiguous || value == nil { // also ambiguous + if ambiguous || value == nil { return // already handled } @@ -920,7 +915,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", def.Name) } if value.BindingName == "*namespace*" { - idx := expr.Idx // TODO fix + idx := expr.Idx c.emitLexicalAssign( def.Name, int(idx), diff --git a/modules.go b/modules.go index 3c491ff3..1577fd13 100644 --- a/modules.go +++ b/modules.go @@ -17,8 +17,7 @@ type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, s // ModuleRecord is the common interface for module record as defined in the EcmaScript specification type ModuleRecord interface { - // TODO move to ModuleInstance - GetExportedNames(resolveset ...ModuleRecord) []string // TODO maybe this parameter is wrong + GetExportedNames(resolveset ...ModuleRecord) []string ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error Evaluate(*Runtime) (ModuleInstance, error) @@ -67,8 +66,7 @@ func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[ var module CyclicModuleRecord var ok bool if module, ok = m.(CyclicModuleRecord); !ok { - err := m.Link() // TODO fix - return index, err + return index, m.Link() } if status := state.status[module]; status == Linking || status == Linked || status == Evaluated { return index, nil From aab267b16459f73f4d014ae0ebbc533405392f4d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 17 Aug 2022 10:28:03 +0300 Subject: [PATCH 101/124] Make dynamic import usable with not source modules --- modules.go | 26 ++++++++ modules_integration_test.go | 123 +++++++++++++++++++++++++++++++++++- runtime.go | 2 +- tc39_test.go | 22 +------ 4 files changed, 151 insertions(+), 22 deletions(-) diff --git a/modules.go b/modules.go index 1577fd13..b600ad8f 100644 --- a/modules.go +++ b/modules.go @@ -801,3 +801,29 @@ func (r *Runtime) SetGetImportMetaProperties(fn func(ModuleRecord) []MetaPropert func (r *Runtime) SetFinalImportMeta(fn func(*Object, ModuleRecord)) { r.finalizeImportMeta = fn } + +// TODO fix signature +type ImportModuleDynamicallyCallback func(referencingScriptOrModule interface{}, specifier Value, promiseCapability interface{}) + +func (r *Runtime) SetImportModuleDynamically(callback ImportModuleDynamicallyCallback) { + r.importModuleDynamically = callback +} + +// TODO figure out the arguments +func (r *Runtime) FinalizeDynamicImport(m ModuleRecord, pcap interface{}, err interface{}) { + p := pcap.(*promiseCapability) + if err != nil { + switch x1 := err.(type) { + case *Exception: + p.reject(x1.val) + case *CompilerSyntaxError: + p.reject(r.builtin_new(r.global.SyntaxError, []Value{newStringValue(x1.Error())})) + case *CompilerReferenceError: + p.reject(r.newError(r.global.ReferenceError, x1.Message)) + default: + p.reject(r.ToValue(err)) + } + return + } + p.resolve(r.NamespaceObjectFor(m)) +} diff --git a/modules_integration_test.go b/modules_integration_test.go index 1a38dd69..9b590ad5 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -3,9 +3,11 @@ package goja_test // this is on purpose in a separate package import ( "fmt" "io/fs" + "sort" "strings" "testing" "testing/fstest" + "time" "github.com/dop251/goja" "github.com/dop251/goja/unistring" @@ -107,6 +109,7 @@ func TestNotSourceModulesBigTest(t *testing.T) { if (otherCoolStuff != 5) { throw "otherCoolStuff isn't a 5 it is a "+ otherCoolStuff } + globalThis.s = true `), } resolver.fs = mapfs @@ -121,10 +124,123 @@ func TestNotSourceModulesBigTest(t *testing.T) { t.Fatalf("got error %s", err) } vm := goja.New() + _, err = vm.CyclicModuleRecordEvaluate(m, resolver.resolve) + if err != nil { + t.Fatalf("got error %s", err) + } + if s := vm.GlobalObject().Get("s"); s == nil || !s.ToBoolean() { + t.Fatalf("test didn't run till the end") + } +} + +func TestNotSourceModulesBigTestDynamicImport(t *testing.T) { + t.Parallel() + resolver := newSimpleComboResolver() + resolver.custom = func(_ interface{}, specifier string) (goja.ModuleRecord, error) { + switch specifier { + case "custom:coolstuff": + return &simpleModuleImpl{}, nil + case "custom:coolstuff2": + return &cyclicModuleImpl{ + resolve: resolver.resolve, + requestedModules: []string{"custom:coolstuff3", "custom:coolstuff"}, + exports: map[string]unresolvedBinding{ + "coolStuff": { + bidning: "coolStuff", + module: "custom:coolstuff", + }, + "otherCoolStuff": { // request it from third module which will request it back from us + bidning: "coolStuff", + module: "custom:coolstuff3", + }, + }, + }, nil + case "custom:coolstuff3": + return &cyclicModuleImpl{ + resolve: resolver.resolve, + requestedModules: []string{"custom:coolstuff2"}, + exports: map[string]unresolvedBinding{ + "coolStuff": { // request it back from the module + bidning: "coolStuff", + module: "custom:coolstuff2", + }, + }, + }, nil + default: + return nil, fmt.Errorf("unknown module %q", specifier) + } + } + mapfs := make(fstest.MapFS) + mapfs["main.js"] = &fstest.MapFile{ + Data: []byte(` + Promise.all([import("custom:coolstuff"), import("custom:coolstuff2")]).then((res)=> { + let coolStuff = res[0].coolStuff + let coolStuff3 = res[1].coolStuff + let otherCoolStuff = res[1].otherCoolStuff + + if (coolStuff != 5) { + throw "coolStuff isn't a 5 it is a "+ coolStuff + } + if (coolStuff3 != 5) { + throw "coolStuff3 isn't a 5 it is a "+ coolStuff3 + } + if (otherCoolStuff != 5) { + throw "otherCoolStuff isn't a 5 it is a "+ otherCoolStuff + } + globalThis.s = true; + })`), + } + resolver.fs = mapfs + m, err := resolver.resolve(nil, "main.js") + if err != nil { + t.Fatalf("got error %s", err) + } + p := m.(*goja.SourceTextModuleRecord) + err = p.Link() + if err != nil { + t.Fatalf("got error %s", err) + } + vm := goja.New() + eventLoopQueue := make(chan func(), 2) // the most basic and likely buggy event loop + vm.SetPromiseRejectionTracker(func(p *goja.Promise, operation goja.PromiseRejectionOperation) { + t.Fatal(p.Result()) + }) + vm.SetImportModuleDynamically(func(referencingScriptOrModule interface{}, specifierValue goja.Value, promiseCapability interface{}) { + specifier := specifierValue.String() + go func() { + m, err := resolver.resolve(referencingScriptOrModule, specifier) + + eventLoopQueue <- func() { + defer vm.RunString("") // haxx // maybe have leave in ffinalize :?!?! + if err == nil { + err = m.Link() + if err == nil { + _, err = vm.CyclicModuleRecordEvaluate(m, resolver.resolve) + } + } + vm.FinalizeDynamicImport(m, promiseCapability, err) + } + }() + }) _, err = m.Evaluate(vm) if err != nil { t.Fatalf("got error %s", err) } + const timeout = time.Millisecond * 1000 + for { + if s := vm.GlobalObject().Get("s"); s != nil { + if !s.ToBoolean() { + t.Fatal("s has wrong value false") + } + return + } + select { + case fn := <-eventLoopQueue: + fn() + case <-time.After(timeout): + t.Fatalf("nothing happened in %s :(", timeout) + } + } } // START of simple module implementation @@ -214,7 +330,12 @@ func (s *cyclicModuleImpl) ResolveExport(exportName string, resolveset ...goja.R } func (s *cyclicModuleImpl) GetExportedNames(records ...goja.ModuleRecord) []string { - return []string{"coolStuff"} + result := make([]string, len(s.exports)) + for k := range s.exports { + result = append(result, k) + } + sort.Strings(result) + return result } type cyclicModuleInstanceImpl struct { diff --git a/runtime.go b/runtime.go index d5dc7b96..3b222aa7 100644 --- a/runtime.go +++ b/runtime.go @@ -188,7 +188,7 @@ type Runtime struct { getImportMetaProperties func(ModuleRecord) []MetaProperty finalizeImportMeta func(*Object, ModuleRecord) - importModuleDynamically func(interface{}, Value, interface{}) + importModuleDynamically ImportModuleDynamicallyCallback jobQueue []func() diff --git a/tc39_test.go b/tc39_test.go index 43415e7e..bef17fdd 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -757,36 +757,18 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. if dynamicImport { vm.importModuleDynamically = func(referencingScriptOrModule interface{}, specifierValue Value, pcap interface{}) { specifier := specifierValue.String() - // TODO have this be a lot less stupid - p := pcap.(*promiseCapability) // FIX go func() { m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) eventLoopQueue <- func() { - defer vm.RunString("") // haxx + defer vm.RunString("") // haxx // maybe have leave in ffinalize :?!?! if err == nil { err = m.Link() if err == nil { _, err = m.Evaluate(vm) } } - if err != nil { - // fmt.Printf("err %T %+v", err, err) - // TODO figure how to this more centralized - switch x1 := err.(type) { - case *Exception: - p.reject(x1.val) - case *CompilerSyntaxError: - p.reject(vm.builtin_new(vm.global.SyntaxError, []Value{newStringValue(x1.Error())})) - case *CompilerReferenceError: - p.reject(vm.newError(vm.global.ReferenceError, x1.Message)) - default: - p.reject(vm.ToValue(err)) - } - return - } - // finalize - p.resolve(vm.NamespaceObjectFor(m)) + vm.FinalizeDynamicImport(m, pcap, err) } }() } From ca4fe85e018f94610600ef562da14420a7fd2b30 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 17 Aug 2022 10:43:02 +0300 Subject: [PATCH 102/124] Don't require the usage of unistring.String from ESM users --- compiler.go | 6 +++--- compiler_stmt.go | 6 +++--- modules.go | 9 ++++----- modules_integration_test.go | 9 ++++----- modules_namespace.go | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/compiler.go b/compiler.go index 1fefa806..78ad89d2 100644 --- a/compiler.go +++ b/compiler.go @@ -1038,7 +1038,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { exportName := unistring.NewFromString(entry.localName) callback := func(vm *vm, getter func() Value) { - vm.r.modules[module].(*SourceTextModuleInstance).exportGetters[exportName] = getter + vm.r.modules[module].(*SourceTextModuleInstance).exportGetters[exportName.String()] = getter } if entry.lex || !scope.boundNames[exportName].isVar { @@ -1061,8 +1061,8 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { continue } - exportName := unistring.NewFromString(entry.exportName) - importName := unistring.NewFromString(b.BindingName) + exportName := unistring.NewFromString(entry.exportName).String() + importName := unistring.NewFromString(b.BindingName).String() c.emit(exportIndirect{callback: func(vm *vm) { m := vm.r.modules[module] m2 := vm.r.modules[b.Module] diff --git a/compiler_stmt.go b/compiler_stmt.go index 9a570e4c..411acf98 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -836,7 +836,7 @@ func (c *compiler) compileExportDeclaration(expr *ast.ExportDeclaration) { if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } - identifier := name.IdentifierName // name will be reused in the for loop + identifier := name.IdentifierName.String() localB.getIndirect = func(vm *vm) Value { return vm.r.modules[module].GetBindingValue(identifier) } @@ -895,7 +895,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { if localB == nil { c.throwSyntaxError(int(expr.Idx0()), "couldn't lookup %s", n) } - identifier := unistring.NewFromString(value.BindingName) + identifier := unistring.NewFromString(value.BindingName).String() localB.getIndirect = func(vm *vm) Value { m := vm.r.modules[value.Module] return m.GetBindingValue(identifier) @@ -926,7 +926,7 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { }, idx), ) } else { - identifier := unistring.NewFromString(value.BindingName) + identifier := unistring.NewFromString(value.BindingName).String() localB.getIndirect = func(vm *vm) Value { m := vm.r.modules[value.Module] return m.GetBindingValue(identifier) diff --git a/modules.go b/modules.go index b600ad8f..cfbf4d10 100644 --- a/modules.go +++ b/modules.go @@ -7,7 +7,6 @@ import ( "github.com/dop251/goja/ast" "github.com/dop251/goja/parser" - "github.com/dop251/goja/unistring" ) type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) @@ -219,7 +218,7 @@ func (r *Runtime) innerModuleEvaluation( type ( ModuleInstance interface { - GetBindingValue(unistring.String) Value + GetBindingValue(string) Value } CyclicModuleInstance interface { ModuleInstance @@ -234,7 +233,7 @@ var _ CyclicModuleInstance = &SourceTextModuleInstance{} type SourceTextModuleInstance struct { moduleRecord *SourceTextModuleRecord // TODO figure out omething less idiotic - exportGetters map[unistring.String]func() Value + exportGetters map[string]func() Value context *context // hacks haxx stack valueStack } @@ -244,7 +243,7 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (CyclicModuleInsta return s, err } -func (s *SourceTextModuleInstance) GetBindingValue(name unistring.String) Value { +func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { getter, ok := s.exportGetters[name] if !ok { // let's not panic in case somebody asks for a binding that isn't exported return nil @@ -727,7 +726,7 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInstance, error) { mi := &SourceTextModuleInstance{ moduleRecord: module, - exportGetters: make(map[unistring.String]func() Value), + exportGetters: make(map[string]func() Value), } rt.modules[module] = mi // TODO figure a better way diff --git a/modules_integration_test.go b/modules_integration_test.go index 9b590ad5..97881988 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/dop251/goja" - "github.com/dop251/goja/unistring" ) type simpleComboResolver struct { @@ -275,8 +274,8 @@ type simpleModuleInstanceImpl struct { rt *goja.Runtime } -func (si *simpleModuleInstanceImpl) GetBindingValue(exportName unistring.String) goja.Value { - if exportName.String() == "coolStuff" { +func (si *simpleModuleInstanceImpl) GetBindingValue(exportName string) goja.Value { + if exportName == "coolStuff" { return si.rt.ToValue(5) } return nil @@ -349,8 +348,8 @@ func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.Cyclic return nil, nil } -func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName unistring.String) goja.Value { - b, ambigious := si.module.ResolveExport(exportName.String()) +func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName string) goja.Value { + b, ambigious := si.module.ResolveExport(exportName) if ambigious || b == nil { panic("fix this") } diff --git a/modules_namespace.go b/modules_namespace.go index a920f82a..11c85dbb 100644 --- a/modules_namespace.go +++ b/modules_namespace.go @@ -108,7 +108,7 @@ func (no *namespaceObject) getOwnPropStr(name unistring.String) Value { } mi := no.val.runtime.modules[v.Module] - b := mi.GetBindingValue(unistring.NewFromString(v.BindingName)) + b := mi.GetBindingValue(v.BindingName) if b == nil { // TODO figure this out - this is needed as otherwise valueproperty is thought to not have a value // which isn't really correct in a particular test around isFrozen From e96fa2595a23da4eaac6df4be8aeba4cfad7a154 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 17 Aug 2022 11:06:48 +0300 Subject: [PATCH 103/124] Fix race in the simpleComboResolver --- modules_integration_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules_integration_test.go b/modules_integration_test.go index 97881988..552d4c63 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -5,6 +5,7 @@ import ( "io/fs" "sort" "strings" + "sync" "testing" "testing/fstest" "time" @@ -13,6 +14,7 @@ import ( ) type simpleComboResolver struct { + mu sync.Mutex cache map[string]cacheElement reverseCache map[goja.ModuleRecord]string fs fs.FS @@ -28,6 +30,8 @@ func newSimpleComboResolver() *simpleComboResolver { } func (s *simpleComboResolver) resolve(referencingScriptOrModule interface{}, specifier string) (goja.ModuleRecord, error) { + s.mu.Lock() + defer s.mu.Unlock() k, ok := s.cache[specifier] if ok { return k.m, k.err From 99a802cd4defff0c52bc0d5b6fa8dc7a6ee84f86 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 17 Aug 2022 19:52:04 +0300 Subject: [PATCH 104/124] Move SourceText module in separate file --- modules.go | 547 +----------------------------------------- modules_sourcetext.go | 532 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 542 insertions(+), 537 deletions(-) create mode 100644 modules_sourcetext.go diff --git a/modules.go b/modules.go index cfbf4d10..f085b434 100644 --- a/modules.go +++ b/modules.go @@ -2,11 +2,6 @@ package goja import ( "errors" - "fmt" - "sort" - - "github.com/dop251/goja/ast" - "github.com/dop251/goja/parser" ) type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) @@ -39,6 +34,16 @@ type CyclicModuleRecord interface { Instantiate(rt *Runtime) (CyclicModuleInstance, error) } +type ( + ModuleInstance interface { + GetBindingValue(string) Value + } + CyclicModuleInstance interface { + ModuleInstance + ExecuteModule(*Runtime) (CyclicModuleInstance, error) + } +) + type linkState struct { status map[ModuleRecord]CyclicModuleRecordStatus dfsIndex map[ModuleRecord]uint @@ -216,538 +221,6 @@ func (r *Runtime) innerModuleEvaluation( return mi, index, nil } -type ( - ModuleInstance interface { - GetBindingValue(string) Value - } - CyclicModuleInstance interface { - ModuleInstance - ExecuteModule(*Runtime) (CyclicModuleInstance, error) - } -) - -var _ CyclicModuleRecord = &SourceTextModuleRecord{} - -var _ CyclicModuleInstance = &SourceTextModuleInstance{} - -type SourceTextModuleInstance struct { - moduleRecord *SourceTextModuleRecord - // TODO figure out omething less idiotic - exportGetters map[string]func() Value - context *context // hacks haxx - stack valueStack -} - -func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (CyclicModuleInstance, error) { - _, err := rt.continueRunProgram(s.moduleRecord.p, s.context, s.stack) - return s, err -} - -func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { - getter, ok := s.exportGetters[name] - if !ok { // let's not panic in case somebody asks for a binding that isn't exported - return nil - } - return getter() -} - -type SourceTextModuleRecord struct { - body *ast.Program - p *Program - // context - // importmeta - requestedModules []string - importEntries []importEntry - localExportEntries []exportEntry - indirectExportEntries []exportEntry - starExportEntries []exportEntry - - hostResolveImportedModule HostResolveImportedModuleFunc -} - -type importEntry struct { - moduleRequest string - importName string - localName string - offset int -} - -type exportEntry struct { - exportName string - moduleRequest string - importName string - localName string - - // not standard - lex bool -} - -func importEntriesFromAst(declarations []*ast.ImportDeclaration) ([]importEntry, error) { - var result []importEntry - names := make(map[string]struct{}, len(declarations)) - for _, importDeclarion := range declarations { - importClause := importDeclarion.ImportClause - if importDeclarion.FromClause == nil { - continue // no entry in this case - } - moduleRequest := importDeclarion.FromClause.ModuleSpecifier.String() - if named := importClause.NamedImports; named != nil { - for _, el := range named.ImportsList { - localName := el.Alias.String() - if localName == "" { - localName = el.IdentifierName.String() - } - if _, ok := names[localName]; ok { - return nil, fmt.Errorf("duplicate bounded name %s", localName) - } - names[localName] = struct{}{} - result = append(result, importEntry{ - moduleRequest: moduleRequest, - importName: el.IdentifierName.String(), - localName: localName, - offset: int(importDeclarion.Idx0()), - }) - } - } - if def := importClause.ImportedDefaultBinding; def != nil { - localName := def.Name.String() - if _, ok := names[localName]; ok { - return nil, fmt.Errorf("duplicate bounded name %s", localName) - } - names[localName] = struct{}{} - result = append(result, importEntry{ - moduleRequest: moduleRequest, - importName: "default", - localName: localName, - offset: int(importDeclarion.Idx0()), - }) - } - if namespace := importClause.NameSpaceImport; namespace != nil { - localName := namespace.ImportedBinding.String() - if _, ok := names[localName]; ok { - return nil, fmt.Errorf("duplicate bounded name %s", localName) - } - names[localName] = struct{}{} - result = append(result, importEntry{ - moduleRequest: moduleRequest, - importName: "*", - localName: namespace.ImportedBinding.String(), - offset: int(importDeclarion.Idx0()), - }) - } - } - return result, nil -} - -func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { - var result []exportEntry - for _, exportDeclaration := range declarations { - if exportDeclaration.ExportFromClause != nil { - exportFromClause := exportDeclaration.ExportFromClause - if namedExports := exportFromClause.NamedExports; namedExports != nil { - for _, spec := range namedExports.ExportsList { - result = append(result, exportEntry{ - localName: spec.IdentifierName.String(), - exportName: spec.Alias.String(), - }) - } - } else if exportFromClause.IsWildcard { - if from := exportDeclaration.FromClause; from != nil { - result = append(result, exportEntry{ - exportName: exportFromClause.Alias.String(), - importName: "*", - moduleRequest: from.ModuleSpecifier.String(), - }) - } else { - result = append(result, exportEntry{ - exportName: exportFromClause.Alias.String(), - importName: "*", - }) - } - } else { - panic("wat") - } - } else if variableDeclaration := exportDeclaration.Variable; variableDeclaration != nil { - for _, l := range variableDeclaration.List { - id, ok := l.Target.(*ast.Identifier) - if !ok { - panic("target wasn;t identifier") - } - result = append(result, exportEntry{ - localName: id.Name.String(), - exportName: id.Name.String(), - lex: false, - }) - - } - } else if LexicalDeclaration := exportDeclaration.LexicalDeclaration; LexicalDeclaration != nil { - for _, l := range LexicalDeclaration.List { - - id, ok := l.Target.(*ast.Identifier) - if !ok { - panic("target wasn;t identifier") - } - result = append(result, exportEntry{ - localName: id.Name.String(), - exportName: id.Name.String(), - lex: true, - }) - - } - } else if hoistable := exportDeclaration.HoistableDeclaration; hoistable != nil { - localName := "default" - exportName := "default" - if hoistable.FunctionDeclaration != nil { - if hoistable.FunctionDeclaration.Function.Name != nil { - localName = string(hoistable.FunctionDeclaration.Function.Name.Name.String()) - } - } - if !exportDeclaration.IsDefault { - exportName = localName - } - result = append(result, exportEntry{ - localName: localName, - exportName: exportName, - lex: true, - }) - } else if fromClause := exportDeclaration.FromClause; fromClause != nil { - if namedExports := exportDeclaration.NamedExports; namedExports != nil { - for _, spec := range namedExports.ExportsList { - alias := spec.IdentifierName.String() - if spec.Alias.String() != "" { // TODO fix - alias = spec.Alias.String() - } - result = append(result, exportEntry{ - importName: spec.IdentifierName.String(), - exportName: alias, - moduleRequest: fromClause.ModuleSpecifier.String(), - }) - } - } else { - panic("wat") - } - } else if namedExports := exportDeclaration.NamedExports; namedExports != nil { - for _, spec := range namedExports.ExportsList { - alias := spec.IdentifierName.String() - if spec.Alias.String() != "" { // TODO fix - alias = spec.Alias.String() - } - result = append(result, exportEntry{ - localName: spec.IdentifierName.String(), - exportName: alias, - }) - } - } else if exportDeclaration.AssignExpression != nil { - result = append(result, exportEntry{ - exportName: "default", - localName: "default", - lex: true, - }) - } else if exportDeclaration.ClassDeclaration != nil { - cls := exportDeclaration.ClassDeclaration.Class - if exportDeclaration.IsDefault { - result = append(result, exportEntry{ - exportName: "default", - localName: "default", - lex: true, - }) - } else { - result = append(result, exportEntry{ - exportName: cls.Name.Name.String(), - localName: cls.Name.Name.String(), - lex: true, - }) - } - } else { - panic("wat") - } - } - return result -} - -func requestedModulesFromAst(statements []ast.Statement) []string { - var result []string - for _, st := range statements { - switch imp := st.(type) { - case *ast.ImportDeclaration: - if imp.FromClause != nil { - result = append(result, imp.FromClause.ModuleSpecifier.String()) - } else { - result = append(result, imp.ModuleSpecifier.String()) - } - case *ast.ExportDeclaration: - if imp.FromClause != nil { - result = append(result, imp.FromClause.ModuleSpecifier.String()) - } - } - } - return result -} - -func findImportByLocalName(importEntries []importEntry, name string) (importEntry, bool) { - for _, i := range importEntries { - if i.localName == name { - return i, true - } - } - - return importEntry{}, false -} - -// This should probably be part of Parse -// TODO arguments to this need fixing -func ParseModule(name, sourceText string, resolveModule HostResolveImportedModuleFunc, opts ...parser.Option) (*SourceTextModuleRecord, error) { - // TODO asserts - opts = append(opts, parser.IsModule) - body, err := Parse(name, sourceText, opts...) - _ = body - if err != nil { - return nil, err - } - return ModuleFromAST(body, resolveModule) -} - -func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { - requestedModules := requestedModulesFromAst(body.Body) - importEntries, err := importEntriesFromAst(body.ImportEntries) - if err != nil { - // TODO create a separate error type - return nil, &CompilerSyntaxError{CompilerError: CompilerError{ - Message: err.Error(), - }} - } - // 6. Let importedBoundNames be ImportedLocalNames(importEntries). - // ^ is skipped as we don't need it. - - var indirectExportEntries []exportEntry - var localExportEntries []exportEntry - var starExportEntries []exportEntry - exportEntries := exportEntriesFromAst(body.ExportEntries) - for _, ee := range exportEntries { - if ee.moduleRequest == "" { // technically nil - if ie, ok := findImportByLocalName(importEntries, ee.localName); !ok { - localExportEntries = append(localExportEntries, ee) - } else { - if ie.importName == "*" { - localExportEntries = append(localExportEntries, ee) - } else { - indirectExportEntries = append(indirectExportEntries, exportEntry{ - moduleRequest: ie.moduleRequest, - importName: ie.importName, - exportName: ee.exportName, - }) - } - } - } else { - if ee.importName == "*" && ee.exportName == "" { - starExportEntries = append(starExportEntries, ee) - } else { - indirectExportEntries = append(indirectExportEntries, ee) - } - } - } - - s := &SourceTextModuleRecord{ - // realm isn't implement - // environment is undefined - // namespace is undefined - requestedModules: requestedModules, - // hostDefined TODO - body: body, - // Context empty - // importMeta empty - importEntries: importEntries, - localExportEntries: localExportEntries, - indirectExportEntries: indirectExportEntries, - starExportEntries: starExportEntries, - - hostResolveImportedModule: resolveModule, - } - - names := s.getExportedNamesWithotStars() // we use this as the other one loops but wee need to early errors here - sort.Strings(names) - for i := 1; i < len(names); i++ { - if names[i] == names[i-1] { - return nil, &CompilerSyntaxError{ - CompilerError: CompilerError{ - Message: fmt.Sprintf("Duplicate export name %s", names[i]), - }, - } - } - // TODO other checks - } - - return s, nil -} - -func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { - exportedNames := make([]string, 0, len(module.localExportEntries)+len(module.indirectExportEntries)) - for _, e := range module.localExportEntries { - exportedNames = append(exportedNames, e.exportName) - } - for _, e := range module.indirectExportEntries { - exportedNames = append(exportedNames, e.exportName) - } - return exportedNames -} - -func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...ModuleRecord) []string { - for _, el := range exportStarSet { - if el == module { // better check - // TODO assert - return nil - } - } - exportStarSet = append(exportStarSet, module) - var exportedNames []string - for _, e := range module.localExportEntries { - exportedNames = append(exportedNames, e.exportName) - } - for _, e := range module.indirectExportEntries { - exportedNames = append(exportedNames, e.exportName) - } - for _, e := range module.starExportEntries { - requestedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) - if err != nil { - panic(err) - } - starNames := requestedModule.GetExportedNames(exportStarSet...) - - for _, n := range starNames { - if n != "default" { - // TODO check if n i exportedNames and don't include it - exportedNames = append(exportedNames, n) - } - } - } - - return exportedNames -} - -func (module *SourceTextModuleRecord) InitializeEnvironment() (err error) { - c := newCompiler() - defer func() { - if x := recover(); x != nil { - switch x1 := x.(type) { - case *CompilerSyntaxError: - err = x1 - default: - panic(x) - } - } - }() - - c.compileModule(module) - module.p = c.p - return -} - -type ResolveSetElement struct { - Module ModuleRecord - ExportName string -} - -type ResolvedBinding struct { - Module ModuleRecord - BindingName string -} - -// GetModuleInstance returns an instance of an already instanciated module. -// If the ModuleRecord was not instanciated at this time it will return nil -func (r *Runtime) GetModuleInstance(m ModuleRecord) ModuleInstance { - return r.modules[m] -} - -func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) { - // TODO this whole algorithm can likely be used for not source module records a well - if exportName == "" { - panic("wat") - } - for _, r := range resolveset { - if r.Module == module && exportName == r.ExportName { // TODO better - return nil, false - } - } - resolveset = append(resolveset, ResolveSetElement{Module: module, ExportName: exportName}) - for _, e := range module.localExportEntries { - if exportName == e.exportName { - // ii. ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. - return &ResolvedBinding{ - Module: module, - BindingName: e.localName, - }, false - } - } - - for _, e := range module.indirectExportEntries { - if exportName == e.exportName { - importedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) - if err != nil { - panic(err) // TODO return err - } - if e.importName == "*" { - // 2. 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: "*namespace*" }. - return &ResolvedBinding{ - Module: importedModule, - BindingName: "*namespace*", - }, false - } else { - return importedModule.ResolveExport(e.importName, resolveset...) - } - } - } - if exportName == "default" { - // This actually should've been caught above, but as it didn't it actually makes it s so the `default` export - // doesn't resolve anything that is `export * ...` - return nil, false - } - var starResolution *ResolvedBinding - - for _, e := range module.starExportEntries { - importedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) - if err != nil { - panic(err) // TODO return err - } - resolution, ambiguous := importedModule.ResolveExport(exportName, resolveset...) - if ambiguous { - return nil, true - } - if resolution != nil { - if starResolution == nil { - starResolution = resolution - } else if resolution.Module != starResolution.Module || resolution.BindingName != starResolution.BindingName { - return nil, true - } - } - } - return starResolution, false -} - -func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInstance, error) { - mi := &SourceTextModuleInstance{ - moduleRecord: module, - exportGetters: make(map[string]func() Value), - } - rt.modules[module] = mi - // TODO figure a better way - _, err := rt.RunProgram(mi.moduleRecord.p) - return mi, err -} - -func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { - return rt.CyclicModuleRecordEvaluate(module, module.hostResolveImportedModule) -} - -func (module *SourceTextModuleRecord) Link() error { - c := newCompiler() - c.hostResolveImportedModule = module.hostResolveImportedModule - return c.CyclicModuleRecordConcreteLink(module) -} - -func (module *SourceTextModuleRecord) RequestedModules() []string { - return module.requestedModules -} - func (r *Runtime) GetActiveScriptOrModule() interface{} { // have some better type if r.vm.prg != nil && r.vm.prg.scriptOrModule != nil { return r.vm.prg.scriptOrModule diff --git a/modules_sourcetext.go b/modules_sourcetext.go new file mode 100644 index 00000000..cce6a68e --- /dev/null +++ b/modules_sourcetext.go @@ -0,0 +1,532 @@ +package goja + +import ( + "fmt" + "sort" + + "github.com/dop251/goja/ast" + "github.com/dop251/goja/parser" +) + +var ( + _ CyclicModuleRecord = &SourceTextModuleRecord{} + _ CyclicModuleInstance = &SourceTextModuleInstance{} +) + +type SourceTextModuleInstance struct { + moduleRecord *SourceTextModuleRecord + // TODO figure out omething less idiotic + exportGetters map[string]func() Value + context *context // hacks haxx + stack valueStack +} + +func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (CyclicModuleInstance, error) { + _, err := rt.continueRunProgram(s.moduleRecord.p, s.context, s.stack) + return s, err +} + +func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { + getter, ok := s.exportGetters[name] + if !ok { // let's not panic in case somebody asks for a binding that isn't exported + return nil + } + return getter() +} + +type SourceTextModuleRecord struct { + body *ast.Program + p *Program + // context + // importmeta + requestedModules []string + importEntries []importEntry + localExportEntries []exportEntry + indirectExportEntries []exportEntry + starExportEntries []exportEntry + + hostResolveImportedModule HostResolveImportedModuleFunc +} + +type importEntry struct { + moduleRequest string + importName string + localName string + offset int +} + +type exportEntry struct { + exportName string + moduleRequest string + importName string + localName string + + // not standard + lex bool +} + +func importEntriesFromAst(declarations []*ast.ImportDeclaration) ([]importEntry, error) { + var result []importEntry + names := make(map[string]struct{}, len(declarations)) + for _, importDeclarion := range declarations { + importClause := importDeclarion.ImportClause + if importDeclarion.FromClause == nil { + continue // no entry in this case + } + moduleRequest := importDeclarion.FromClause.ModuleSpecifier.String() + if named := importClause.NamedImports; named != nil { + for _, el := range named.ImportsList { + localName := el.Alias.String() + if localName == "" { + localName = el.IdentifierName.String() + } + if _, ok := names[localName]; ok { + return nil, fmt.Errorf("duplicate bounded name %s", localName) + } + names[localName] = struct{}{} + result = append(result, importEntry{ + moduleRequest: moduleRequest, + importName: el.IdentifierName.String(), + localName: localName, + offset: int(importDeclarion.Idx0()), + }) + } + } + if def := importClause.ImportedDefaultBinding; def != nil { + localName := def.Name.String() + if _, ok := names[localName]; ok { + return nil, fmt.Errorf("duplicate bounded name %s", localName) + } + names[localName] = struct{}{} + result = append(result, importEntry{ + moduleRequest: moduleRequest, + importName: "default", + localName: localName, + offset: int(importDeclarion.Idx0()), + }) + } + if namespace := importClause.NameSpaceImport; namespace != nil { + localName := namespace.ImportedBinding.String() + if _, ok := names[localName]; ok { + return nil, fmt.Errorf("duplicate bounded name %s", localName) + } + names[localName] = struct{}{} + result = append(result, importEntry{ + moduleRequest: moduleRequest, + importName: "*", + localName: namespace.ImportedBinding.String(), + offset: int(importDeclarion.Idx0()), + }) + } + } + return result, nil +} + +func exportEntriesFromAst(declarations []*ast.ExportDeclaration) []exportEntry { + var result []exportEntry + for _, exportDeclaration := range declarations { + if exportDeclaration.ExportFromClause != nil { + exportFromClause := exportDeclaration.ExportFromClause + if namedExports := exportFromClause.NamedExports; namedExports != nil { + for _, spec := range namedExports.ExportsList { + result = append(result, exportEntry{ + localName: spec.IdentifierName.String(), + exportName: spec.Alias.String(), + }) + } + } else if exportFromClause.IsWildcard { + if from := exportDeclaration.FromClause; from != nil { + result = append(result, exportEntry{ + exportName: exportFromClause.Alias.String(), + importName: "*", + moduleRequest: from.ModuleSpecifier.String(), + }) + } else { + result = append(result, exportEntry{ + exportName: exportFromClause.Alias.String(), + importName: "*", + }) + } + } else { + panic("wat") + } + } else if variableDeclaration := exportDeclaration.Variable; variableDeclaration != nil { + for _, l := range variableDeclaration.List { + id, ok := l.Target.(*ast.Identifier) + if !ok { + panic("target wasn;t identifier") + } + result = append(result, exportEntry{ + localName: id.Name.String(), + exportName: id.Name.String(), + lex: false, + }) + + } + } else if LexicalDeclaration := exportDeclaration.LexicalDeclaration; LexicalDeclaration != nil { + for _, l := range LexicalDeclaration.List { + + id, ok := l.Target.(*ast.Identifier) + if !ok { + panic("target wasn;t identifier") + } + result = append(result, exportEntry{ + localName: id.Name.String(), + exportName: id.Name.String(), + lex: true, + }) + + } + } else if hoistable := exportDeclaration.HoistableDeclaration; hoistable != nil { + localName := "default" + exportName := "default" + if hoistable.FunctionDeclaration != nil { + if hoistable.FunctionDeclaration.Function.Name != nil { + localName = string(hoistable.FunctionDeclaration.Function.Name.Name.String()) + } + } + if !exportDeclaration.IsDefault { + exportName = localName + } + result = append(result, exportEntry{ + localName: localName, + exportName: exportName, + lex: true, + }) + } else if fromClause := exportDeclaration.FromClause; fromClause != nil { + if namedExports := exportDeclaration.NamedExports; namedExports != nil { + for _, spec := range namedExports.ExportsList { + alias := spec.IdentifierName.String() + if spec.Alias.String() != "" { // TODO fix + alias = spec.Alias.String() + } + result = append(result, exportEntry{ + importName: spec.IdentifierName.String(), + exportName: alias, + moduleRequest: fromClause.ModuleSpecifier.String(), + }) + } + } else { + panic("wat") + } + } else if namedExports := exportDeclaration.NamedExports; namedExports != nil { + for _, spec := range namedExports.ExportsList { + alias := spec.IdentifierName.String() + if spec.Alias.String() != "" { // TODO fix + alias = spec.Alias.String() + } + result = append(result, exportEntry{ + localName: spec.IdentifierName.String(), + exportName: alias, + }) + } + } else if exportDeclaration.AssignExpression != nil { + result = append(result, exportEntry{ + exportName: "default", + localName: "default", + lex: true, + }) + } else if exportDeclaration.ClassDeclaration != nil { + cls := exportDeclaration.ClassDeclaration.Class + if exportDeclaration.IsDefault { + result = append(result, exportEntry{ + exportName: "default", + localName: "default", + lex: true, + }) + } else { + result = append(result, exportEntry{ + exportName: cls.Name.Name.String(), + localName: cls.Name.Name.String(), + lex: true, + }) + } + } else { + panic("wat") + } + } + return result +} + +func requestedModulesFromAst(statements []ast.Statement) []string { + var result []string + for _, st := range statements { + switch imp := st.(type) { + case *ast.ImportDeclaration: + if imp.FromClause != nil { + result = append(result, imp.FromClause.ModuleSpecifier.String()) + } else { + result = append(result, imp.ModuleSpecifier.String()) + } + case *ast.ExportDeclaration: + if imp.FromClause != nil { + result = append(result, imp.FromClause.ModuleSpecifier.String()) + } + } + } + return result +} + +func findImportByLocalName(importEntries []importEntry, name string) (importEntry, bool) { + for _, i := range importEntries { + if i.localName == name { + return i, true + } + } + + return importEntry{}, false +} + +// This should probably be part of Parse +// TODO arguments to this need fixing +func ParseModule(name, sourceText string, resolveModule HostResolveImportedModuleFunc, opts ...parser.Option) (*SourceTextModuleRecord, error) { + // TODO asserts + opts = append(opts, parser.IsModule) + body, err := Parse(name, sourceText, opts...) + _ = body + if err != nil { + return nil, err + } + return ModuleFromAST(body, resolveModule) +} + +func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFunc) (*SourceTextModuleRecord, error) { + requestedModules := requestedModulesFromAst(body.Body) + importEntries, err := importEntriesFromAst(body.ImportEntries) + if err != nil { + // TODO create a separate error type + return nil, &CompilerSyntaxError{CompilerError: CompilerError{ + Message: err.Error(), + }} + } + // 6. Let importedBoundNames be ImportedLocalNames(importEntries). + // ^ is skipped as we don't need it. + + var indirectExportEntries []exportEntry + var localExportEntries []exportEntry + var starExportEntries []exportEntry + exportEntries := exportEntriesFromAst(body.ExportEntries) + for _, ee := range exportEntries { + if ee.moduleRequest == "" { // technically nil + if ie, ok := findImportByLocalName(importEntries, ee.localName); !ok { + localExportEntries = append(localExportEntries, ee) + } else { + if ie.importName == "*" { + localExportEntries = append(localExportEntries, ee) + } else { + indirectExportEntries = append(indirectExportEntries, exportEntry{ + moduleRequest: ie.moduleRequest, + importName: ie.importName, + exportName: ee.exportName, + }) + } + } + } else { + if ee.importName == "*" && ee.exportName == "" { + starExportEntries = append(starExportEntries, ee) + } else { + indirectExportEntries = append(indirectExportEntries, ee) + } + } + } + + s := &SourceTextModuleRecord{ + // realm isn't implement + // environment is undefined + // namespace is undefined + requestedModules: requestedModules, + // hostDefined TODO + body: body, + // Context empty + // importMeta empty + importEntries: importEntries, + localExportEntries: localExportEntries, + indirectExportEntries: indirectExportEntries, + starExportEntries: starExportEntries, + + hostResolveImportedModule: resolveModule, + } + + names := s.getExportedNamesWithotStars() // we use this as the other one loops but wee need to early errors here + sort.Strings(names) + for i := 1; i < len(names); i++ { + if names[i] == names[i-1] { + return nil, &CompilerSyntaxError{ + CompilerError: CompilerError{ + Message: fmt.Sprintf("Duplicate export name %s", names[i]), + }, + } + } + // TODO other checks + } + + return s, nil +} + +func (module *SourceTextModuleRecord) getExportedNamesWithotStars() []string { + exportedNames := make([]string, 0, len(module.localExportEntries)+len(module.indirectExportEntries)) + for _, e := range module.localExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + for _, e := range module.indirectExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + return exportedNames +} + +func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...ModuleRecord) []string { + for _, el := range exportStarSet { + if el == module { // better check + // TODO assert + return nil + } + } + exportStarSet = append(exportStarSet, module) + var exportedNames []string + for _, e := range module.localExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + for _, e := range module.indirectExportEntries { + exportedNames = append(exportedNames, e.exportName) + } + for _, e := range module.starExportEntries { + requestedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) + if err != nil { + panic(err) + } + starNames := requestedModule.GetExportedNames(exportStarSet...) + + for _, n := range starNames { + if n != "default" { + // TODO check if n i exportedNames and don't include it + exportedNames = append(exportedNames, n) + } + } + } + + return exportedNames +} + +func (module *SourceTextModuleRecord) InitializeEnvironment() (err error) { + c := newCompiler() + defer func() { + if x := recover(); x != nil { + switch x1 := x.(type) { + case *CompilerSyntaxError: + err = x1 + default: + panic(x) + } + } + }() + + c.compileModule(module) + module.p = c.p + return +} + +type ResolveSetElement struct { + Module ModuleRecord + ExportName string +} + +type ResolvedBinding struct { + Module ModuleRecord + BindingName string +} + +// GetModuleInstance returns an instance of an already instanciated module. +// If the ModuleRecord was not instanciated at this time it will return nil +func (r *Runtime) GetModuleInstance(m ModuleRecord) ModuleInstance { + return r.modules[m] +} + +func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) { + // TODO this whole algorithm can likely be used for not source module records a well + if exportName == "" { + panic("wat") + } + for _, r := range resolveset { + if r.Module == module && exportName == r.ExportName { // TODO better + return nil, false + } + } + resolveset = append(resolveset, ResolveSetElement{Module: module, ExportName: exportName}) + for _, e := range module.localExportEntries { + if exportName == e.exportName { + // ii. ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + return &ResolvedBinding{ + Module: module, + BindingName: e.localName, + }, false + } + } + + for _, e := range module.indirectExportEntries { + if exportName == e.exportName { + importedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) + if err != nil { + panic(err) // TODO return err + } + if e.importName == "*" { + // 2. 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: "*namespace*" }. + return &ResolvedBinding{ + Module: importedModule, + BindingName: "*namespace*", + }, false + } else { + return importedModule.ResolveExport(e.importName, resolveset...) + } + } + } + if exportName == "default" { + // This actually should've been caught above, but as it didn't it actually makes it s so the `default` export + // doesn't resolve anything that is `export * ...` + return nil, false + } + var starResolution *ResolvedBinding + + for _, e := range module.starExportEntries { + importedModule, err := module.hostResolveImportedModule(module, e.moduleRequest) + if err != nil { + panic(err) // TODO return err + } + resolution, ambiguous := importedModule.ResolveExport(exportName, resolveset...) + if ambiguous { + return nil, true + } + if resolution != nil { + if starResolution == nil { + starResolution = resolution + } else if resolution.Module != starResolution.Module || resolution.BindingName != starResolution.BindingName { + return nil, true + } + } + } + return starResolution, false +} + +func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInstance, error) { + mi := &SourceTextModuleInstance{ + moduleRecord: module, + exportGetters: make(map[string]func() Value), + } + rt.modules[module] = mi + // TODO figure a better way + _, err := rt.RunProgram(mi.moduleRecord.p) + return mi, err +} + +func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { + return rt.CyclicModuleRecordEvaluate(module, module.hostResolveImportedModule) +} + +func (module *SourceTextModuleRecord) Link() error { + c := newCompiler() + c.hostResolveImportedModule = module.hostResolveImportedModule + return c.CyclicModuleRecordConcreteLink(module) +} + +func (module *SourceTextModuleRecord) RequestedModules() []string { + return module.requestedModules +} From 4c85e02b47b4979f419192d31c6d0ce5cac99322 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 19 Aug 2022 19:40:51 +0300 Subject: [PATCH 105/124] Add support tla/async evaluation of modules This does not include any kind of top level async support for text modules as that is blocked on async/await which isn't yet implemented. But it does add the support for async evaluation of modules. --- modules.go | 238 ++++++++++++++++++++++++++++++------ modules_integration_test.go | 45 ++++--- modules_sourcetext.go | 8 +- modules_test.go | 5 +- tc39_test.go | 12 +- 5 files changed, 248 insertions(+), 60 deletions(-) diff --git a/modules.go b/modules.go index f085b434..e8dafa8c 100644 --- a/modules.go +++ b/modules.go @@ -14,7 +14,7 @@ type ModuleRecord interface { GetExportedNames(resolveset ...ModuleRecord) []string ResolveExport(exportName string, resolveset ...ResolveSetElement) (*ResolvedBinding, bool) Link() error - Evaluate(*Runtime) (ModuleInstance, error) + Evaluate(*Runtime) *Promise } type CyclicModuleRecordStatus uint8 @@ -24,6 +24,7 @@ const ( Linking Linked Evaluating + Evaluating_Async Evaluated ) @@ -40,7 +41,8 @@ type ( } CyclicModuleInstance interface { ModuleInstance - ExecuteModule(*Runtime) (CyclicModuleInstance, error) + HasTLA() bool + ExecuteModule(rt *Runtime, res, rej func(interface{})) (CyclicModuleInstance, error) } ) @@ -119,68 +121,105 @@ func (c *compiler) innerModuleLinking(state *linkState, m ModuleRecord, stack *[ } type evaluationState struct { - status map[ModuleInstance]CyclicModuleRecordStatus - dfsIndex map[ModuleInstance]uint - dfsAncestorIndex map[ModuleInstance]uint + status map[ModuleInstance]CyclicModuleRecordStatus + dfsIndex map[ModuleInstance]uint + dfsAncestorIndex map[ModuleInstance]uint + pendingAsyncDependancies map[ModuleInstance]uint + cycleRoot map[ModuleInstance]CyclicModuleInstance + asyncEvaluation map[CyclicModuleInstance]bool + asyncParentModules map[CyclicModuleInstance][]CyclicModuleInstance + evaluationError map[CyclicModuleInstance]error + topLevelCapability map[CyclicModuleRecord]*promiseCapability } func newEvaluationState() *evaluationState { return &evaluationState{ - status: make(map[ModuleInstance]CyclicModuleRecordStatus), - dfsIndex: make(map[ModuleInstance]uint), - dfsAncestorIndex: make(map[ModuleInstance]uint), + status: make(map[ModuleInstance]CyclicModuleRecordStatus), + dfsIndex: make(map[ModuleInstance]uint), + dfsAncestorIndex: make(map[ModuleInstance]uint), + pendingAsyncDependancies: make(map[ModuleInstance]uint), + cycleRoot: make(map[ModuleInstance]CyclicModuleInstance), + asyncEvaluation: make(map[CyclicModuleInstance]bool), + asyncParentModules: make(map[CyclicModuleInstance][]CyclicModuleInstance), + evaluationError: make(map[CyclicModuleInstance]error), + topLevelCapability: make(map[CyclicModuleRecord]*promiseCapability), } } -func (r *Runtime) CyclicModuleRecordEvaluate(c ModuleRecord, resolve HostResolveImportedModuleFunc, -) (mi ModuleInstance, err error) { +// TODO have resolve as part of runtime +func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostResolveImportedModuleFunc, +) *Promise { if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) } + // TODO implement all the promise stuff stackInstance := []CyclicModuleInstance{} - if mi, _, err = r.innerModuleEvaluation(newEvaluationState(), c, &stackInstance, 0, resolve); err != nil { - return nil, err - } - return mi, nil + state := newEvaluationState() + capability := r.newPromiseCapability(r.global.Promise) + state.topLevelCapability[c] = capability + // TODO fix abrupt result + _, err := r.innerModuleEvaluation(state, c, &stackInstance, 0, resolve) + if err != nil { + for _, m := range stackInstance { + state.status[m] = Evaluated + state.evaluationError[m] = err + } + capability.reject(r.ToValue(err)) + + } else { + if !state.asyncEvaluation[r.modules[c].(CyclicModuleInstance)] { + state.topLevelCapability[c].resolve(_undefined) + } + } + // TODO handle completion + return state.topLevelCapability[c].promise.Export().(*Promise) } func (r *Runtime) innerModuleEvaluation( state *evaluationState, m ModuleRecord, stack *[]CyclicModuleInstance, index uint, resolve HostResolveImportedModuleFunc, -) (mi ModuleInstance, idx uint, err error) { +) (idx uint, err error) { if len(*stack) > 100000 { panic("too deep dependancy stack of 100000") } var cr CyclicModuleRecord var ok bool var c CyclicModuleInstance + // TODO use only `c` instead `mi` after the initial part + var mi ModuleInstance if cr, ok = m.(CyclicModuleRecord); !ok { - mi, err = m.Evaluate(r) - r.modules[m] = mi - return mi, index, err - } else { - mi, ok = r.modules[m] - if ok { - return mi, index, nil + p := m.Evaluate(r) + if p.state == PromiseStateRejected { + return index, p.Result().Export().(error) } - c, err = cr.Instantiate(r) - if err != nil { - return nil, index, err - } - - mi = c - r.modules[m] = c + r.modules[m] = p.Result().Export().(ModuleInstance) // TODO fix this cast ... somehow + return index, nil + } + mi, ok = r.modules[m] + if ok { + return index, nil + } + c, err = cr.Instantiate(r) + if err != nil { + // state.evaluationError[cr] = err + // TODO handle this somehow - maybe just panic + return index, err } + + mi = c + r.modules[m] = c if status := state.status[mi]; status == Evaluated { - return nil, index, nil - } else if status == Evaluating { - return nil, index, nil + return index, nil + } else if status == Evaluating || status == Evaluating_Async { + // maybe check evaluation error + return index, nil } state.status[mi] = Evaluating state.dfsIndex[mi] = index state.dfsAncestorIndex[mi] = index + state.pendingAsyncDependancies[mi] = 0 index++ *stack = append(*stack, c) @@ -188,37 +227,156 @@ func (r *Runtime) innerModuleEvaluation( for _, required := range cr.RequestedModules() { requiredModule, err = resolve(m, required) if err != nil { - return nil, 0, err + state.evaluationError[c] = err + return index, err } var requiredInstance ModuleInstance - requiredInstance, index, err = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) + index, err = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) if err != nil { - return nil, 0, err + return index, err } if requiredC, ok := requiredInstance.(CyclicModuleInstance); ok { if state.status[requiredC] == Evaluating { if ancestorIndex := state.dfsAncestorIndex[c]; state.dfsAncestorIndex[requiredC] > ancestorIndex { state.dfsAncestorIndex[requiredC] = ancestorIndex } + } else { + requiredC = state.cycleRoot[requiredC] + // check stuff + } + if state.asyncEvaluation[requiredC] { + state.pendingAsyncDependancies[mi]++ + state.asyncParentModules[requiredC] = append(state.asyncParentModules[requiredC], c) } } } - mi, err = c.ExecuteModule(r) - if err != nil { - return nil, 0, err + if state.pendingAsyncDependancies[mi] > 0 || c.HasTLA() { + state.asyncEvaluation[c] = true + if state.pendingAsyncDependancies[c] == 0 { + r.executeAsyncModule(state, c) + } + } else { + mi, err = c.ExecuteModule(r, nil, nil) + if err != nil { + // state.evaluationError[c] = err + return index, err + } } if state.dfsAncestorIndex[c] == state.dfsIndex[c] { for i := len(*stack) - 1; i >= 0; i-- { requiredModuleInstance := (*stack)[i] *stack = (*stack)[:i] - state.status[requiredModuleInstance] = Evaluated + if !state.asyncEvaluation[requiredModuleInstance] { + state.status[requiredModuleInstance] = Evaluated + } else { + state.status[requiredModuleInstance] = Evaluating_Async + } + state.cycleRoot[requiredModuleInstance] = c if requiredModuleInstance == c { break } } } - return mi, index, nil + return index, nil +} + +func (r *Runtime) executeAsyncModule(state *evaluationState, c CyclicModuleInstance) { + // implement https://262.ecma-international.org/13.0/#sec-execute-async-module + // TODO likely wrong + p, res, rej := r.NewPromise() + r.performPromiseThen(p, r.ToValue(func() { + r.asyncModuleExecutionFulfilled(state, c) + }), r.ToValue(func(err error) { + r.asyncModuleExecutionRejected(state, c, err) + }), nil) + c.ExecuteModule(r, res, rej) +} + +func (r *Runtime) asyncModuleExecutionFulfilled(state *evaluationState, c CyclicModuleInstance) { + if state.status[c] == Evaluated { + return + } + state.asyncEvaluation[c] = false + // TODO fix this + for m, i := range r.modules { + if i == c { + if cap := state.topLevelCapability[m.(CyclicModuleRecord)]; cap != nil { + cap.resolve(_undefined) + } + break + } + } + execList := make([]CyclicModuleInstance, 0) + r.gatherAvailableAncestors(state, c, &execList) + // TODO sort? per when the modules got their AsyncEvaluation set ... somehow + for _, m := range execList { + if state.status[m] == Evaluated { + continue + } + if m.HasTLA() { + r.executeAsyncModule(state, m) + } else { + result, err := m.ExecuteModule(r, nil, nil) + if err != nil { + r.asyncModuleExecutionRejected(state, m, err) + continue + } + state.status[m] = Evaluated + if cap := state.topLevelCapability[r.findModuleRecord(c).(CyclicModuleRecord)]; cap != nil { + // TODO having the module instances going through Values and back is likely not a *great* idea + cap.resolve(r.ToValue(result)) + } + } + } +} + +func (r *Runtime) gatherAvailableAncestors(state *evaluationState, c CyclicModuleInstance, execList *[]CyclicModuleInstance) { + contains := func(m CyclicModuleInstance) bool { + for _, l := range *execList { + if l == m { + return true + } + } + return false + } + for _, m := range state.asyncParentModules[c] { + if contains(m) || state.evaluationError[m] != nil { + continue + } + state.pendingAsyncDependancies[m]-- + if state.pendingAsyncDependancies[m] == 0 { + *execList = append(*execList, m) + if !m.HasTLA() { + r.gatherAvailableAncestors(state, m, execList) + } + } + } +} + +func (r *Runtime) asyncModuleExecutionRejected(state *evaluationState, c CyclicModuleInstance, err error) { + if state.status[c] == Evaluated { + return + } + state.evaluationError[c] = err + state.status[c] = Evaluated + for _, m := range state.asyncParentModules[c] { + r.asyncModuleExecutionRejected(state, m, err) + } + // TODO handle top level capabiltiy better + if cap := state.topLevelCapability[r.findModuleRecord(c).(CyclicModuleRecord)]; cap != nil { + cap.reject(r.ToValue(err)) + } +} + +// TODO fix this whole thing +func (r *Runtime) findModuleRecord(i ModuleInstance) ModuleRecord { + for m, mi := range r.modules { + if mi == i { + return m + } + } + panic("this should never happen") } func (r *Runtime) GetActiveScriptOrModule() interface{} { // have some better type diff --git a/modules_integration_test.go b/modules_integration_test.go index 552d4c63..45f818b6 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -127,8 +127,9 @@ func TestNotSourceModulesBigTest(t *testing.T) { t.Fatalf("got error %s", err) } vm := goja.New() - _, err = vm.CyclicModuleRecordEvaluate(m, resolver.resolve) - if err != nil { + promise := vm.CyclicModuleRecordEvaluate(p, resolver.resolve) + if promise.State() != goja.PromiseStateFulfilled { + err := promise.Result().Export().(error) t.Fatalf("got error %s", err) } if s := vm.GlobalObject().Get("s"); s == nil || !s.ToBoolean() { @@ -218,15 +219,25 @@ func TestNotSourceModulesBigTestDynamicImport(t *testing.T) { if err == nil { err = m.Link() if err == nil { - _, err = vm.CyclicModuleRecordEvaluate(m, resolver.resolve) + var promise *goja.Promise + if c, ok := m.(goja.CyclicModuleRecord); ok { + promise = vm.CyclicModuleRecordEvaluate(c, resolver.resolve) + } else { + promise = m.Evaluate(vm) + } + if promise.State() != goja.PromiseStateFulfilled { + err = promise.Result().Export().(error) + } } } vm.FinalizeDynamicImport(m, promiseCapability, err) } }() }) - _, err = m.Evaluate(vm) - if err != nil { + promise := vm.CyclicModuleRecordEvaluate(p, resolver.resolve) + // TODO fix + if promise.State() != goja.PromiseStateFulfilled { + err := promise.Result().Export().(error) t.Fatalf("got error %s", err) } const timeout = time.Millisecond * 1000 @@ -266,8 +277,10 @@ func (s *simpleModuleImpl) ResolveExport(exportName string, resolveset ...goja.R return nil, false } -func (s *simpleModuleImpl) Evaluate(rt *goja.Runtime) (goja.ModuleInstance, error) { - return &simpleModuleInstanceImpl{rt: rt}, nil +func (s *simpleModuleImpl) Evaluate(rt *goja.Runtime) *goja.Promise { + p, res, _ := rt.NewPromise() + res(&simpleModuleInstanceImpl{rt: rt}) + return p } func (s *simpleModuleImpl) GetExportedNames(records ...goja.ModuleRecord) []string { @@ -311,8 +324,8 @@ func (s *cyclicModuleImpl) Link() error { return nil } -func (s *cyclicModuleImpl) Evaluate(rt *goja.Runtime) (goja.ModuleInstance, error) { - return nil, nil +func (s *cyclicModuleImpl) Evaluate(rt *goja.Runtime) *goja.Promise { + panic("this should never be called") } func (s *cyclicModuleImpl) ResolveExport(exportName string, resolveset ...goja.ResolveSetElement) (*goja.ResolvedBinding, bool) { @@ -346,10 +359,13 @@ type cyclicModuleInstanceImpl struct { module *cyclicModuleImpl } -func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime) (goja.CyclicModuleInstance, error) { +func (s *cyclicModuleInstanceImpl) HasTLA() bool { + return false +} + +func (si *cyclicModuleInstanceImpl) ExecuteModule(rt *goja.Runtime, res, rej func(interface{})) (goja.CyclicModuleInstance, error) { si.rt = rt - // TODO others - return nil, nil + return si, nil } func (si *cyclicModuleInstanceImpl) GetBindingValue(exportName string) goja.Value { @@ -406,8 +422,9 @@ func TestSourceMetaImport(t *testing.T) { }, } }) - _, err = m.Evaluate(vm) - if err != nil { + promise := m.Evaluate(vm) + if promise.State() != goja.PromiseStateFulfilled { + err := promise.Result().Export().(error) t.Fatalf("got error %s", err) } } diff --git a/modules_sourcetext.go b/modules_sourcetext.go index cce6a68e..151e6fa6 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -21,7 +21,7 @@ type SourceTextModuleInstance struct { stack valueStack } -func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime) (CyclicModuleInstance, error) { +func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(interface{})) (CyclicModuleInstance, error) { _, err := rt.continueRunProgram(s.moduleRecord.p, s.context, s.stack) return s, err } @@ -34,6 +34,10 @@ func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { return getter() } +func (s *SourceTextModuleInstance) HasTLA() bool { + return false // TODO implement when TLA is added +} + type SourceTextModuleRecord struct { body *ast.Program p *Program @@ -517,7 +521,7 @@ func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInst return mi, err } -func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) (ModuleInstance, error) { +func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) *Promise { return rt.CyclicModuleRecordEvaluate(module, module.hostResolveImportedModule) } diff --git a/modules_test.go b/modules_test.go index 685697b4..72bdef4b 100644 --- a/modules_test.go +++ b/modules_test.go @@ -136,8 +136,9 @@ globalThis.s = b() if err != nil { t.Fatalf("got error %s", err) } - _, err = m.Evaluate(vm) - if err != nil { + promise := m.Evaluate(vm) + if promise.state != PromiseStateFulfilled { + err = promise.Result().Export().(error) t.Fatalf("got error %s", err) } v := vm.Get("s") diff --git a/tc39_test.go b/tc39_test.go index bef17fdd..8b1ec0ee 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -765,7 +765,11 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. if err == nil { err = m.Link() if err == nil { - _, err = m.Evaluate(vm) + promise := m.Evaluate(vm) + // TODO fix + if promise.state != PromiseStateFulfilled { + err = promise.Result().Export().(error) + } } } vm.FinalizeDynamicImport(m, pcap, err) @@ -1012,7 +1016,11 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R } early = false - _, err = m.Evaluate(vm) + promise := m.Evaluate(vm) + // TODO fix + if promise.state != PromiseStateFulfilled { + err = promise.Result().Export().(error) + } return } From 5f81441173723ebb181873312ffaa98604cdd2b0 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 22 Aug 2022 15:01:21 +0300 Subject: [PATCH 106/124] fixup! Add support tla/async evaluation of modules --- modules.go | 22 +++++++++------------- modules_integration_test.go | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/modules.go b/modules.go index e8dafa8c..41ae2412 100644 --- a/modules.go +++ b/modules.go @@ -187,8 +187,6 @@ func (r *Runtime) innerModuleEvaluation( var cr CyclicModuleRecord var ok bool var c CyclicModuleInstance - // TODO use only `c` instead `mi` after the initial part - var mi ModuleInstance if cr, ok = m.(CyclicModuleRecord); !ok { p := m.Evaluate(r) if p.state == PromiseStateRejected { @@ -197,8 +195,7 @@ func (r *Runtime) innerModuleEvaluation( r.modules[m] = p.Result().Export().(ModuleInstance) // TODO fix this cast ... somehow return index, nil } - mi, ok = r.modules[m] - if ok { + if _, ok = r.modules[m]; ok { return index, nil } c, err = cr.Instantiate(r) @@ -208,18 +205,17 @@ func (r *Runtime) innerModuleEvaluation( return index, err } - mi = c r.modules[m] = c - if status := state.status[mi]; status == Evaluated { + if status := state.status[c]; status == Evaluated { return index, nil } else if status == Evaluating || status == Evaluating_Async { // maybe check evaluation error return index, nil } - state.status[mi] = Evaluating - state.dfsIndex[mi] = index - state.dfsAncestorIndex[mi] = index - state.pendingAsyncDependancies[mi] = 0 + state.status[c] = Evaluating + state.dfsIndex[c] = index + state.dfsAncestorIndex[c] = index + state.pendingAsyncDependancies[c] = 0 index++ *stack = append(*stack, c) @@ -245,18 +241,18 @@ func (r *Runtime) innerModuleEvaluation( // check stuff } if state.asyncEvaluation[requiredC] { - state.pendingAsyncDependancies[mi]++ + state.pendingAsyncDependancies[c]++ state.asyncParentModules[requiredC] = append(state.asyncParentModules[requiredC], c) } } } - if state.pendingAsyncDependancies[mi] > 0 || c.HasTLA() { + if state.pendingAsyncDependancies[c] > 0 || c.HasTLA() { state.asyncEvaluation[c] = true if state.pendingAsyncDependancies[c] == 0 { r.executeAsyncModule(state, c) } } else { - mi, err = c.ExecuteModule(r, nil, nil) + c, err = c.ExecuteModule(r, nil, nil) if err != nil { // state.evaluationError[c] = err return index, err diff --git a/modules_integration_test.go b/modules_integration_test.go index 45f818b6..c4918e10 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -359,7 +359,7 @@ type cyclicModuleInstanceImpl struct { module *cyclicModuleImpl } -func (s *cyclicModuleInstanceImpl) HasTLA() bool { +func (si *cyclicModuleInstanceImpl) HasTLA() bool { return false } From a1a2dc51e5ff90cdbedc4988d3f2707794677c8d Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 7 Sep 2022 14:41:52 +0300 Subject: [PATCH 107/124] Revert formatting --- builtin_reflect.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin_reflect.go b/builtin_reflect.go index 72c9988b..68a2bb05 100644 --- a/builtin_reflect.go +++ b/builtin_reflect.go @@ -3,8 +3,7 @@ package goja func (r *Runtime) builtin_reflect_apply(call FunctionCall) Value { return r.toCallable(call.Argument(0))(FunctionCall{ This: call.Argument(1), - Arguments: r.createListFromArrayLike(call.Argument(2)), - }) + Arguments: r.createListFromArrayLike(call.Argument(2))}) } func (r *Runtime) toConstructor(v Value) func(args []Value, newTarget *Object) *Object { From ae2a9b970614df7a376c9ffd2b1d866e77df8ee0 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 7 Sep 2022 15:16:43 +0300 Subject: [PATCH 108/124] drop wrong createImmutableBinding --- compiler.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/compiler.go b/compiler.go index 78ad89d2..d7f89b82 100644 --- a/compiler.go +++ b/compiler.go @@ -966,16 +966,16 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if err != nil { panic(fmt.Errorf("previously resolved module returned error %w", err)) } - if in.importName == "*" { - c.createImmutableBinding(unistring.NewFromString(in.localName), true) - } else { + if in.importName != "*" { resolution, ambiguous := importedModule.ResolveExport(in.importName) if resolution == nil || ambiguous { c.compileAmbiguousImport(unistring.NewFromString(in.importName)) continue } - c.createImmutableBinding(unistring.NewFromString(in.localName), true) } + b, _ := c.scope.bindName(unistring.NewFromString(in.localName)) + b.inStash = true + b.isConst = true } funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) @@ -1352,13 +1352,6 @@ func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { } } -func (c *compiler) createImmutableBinding(name unistring.String, isConst bool) *binding { - b, _ := c.scope.bindName(name) - b.inStash = true - b.isConst = isConst - return b -} - func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding { if name == "let" { c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") From 5c05a992391a0baf44d5845b82435e9bb1c9b4f5 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 29 Mar 2023 17:21:49 +0300 Subject: [PATCH 109/124] fixup! Merge commit '33bff8fdda616056a9eb9b42853e12e9feafc8f9' into modules --- parser/parser.go | 3 +++ tc39_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/parser/parser.go b/parser/parser.go index 68def1c0..1994b60f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -216,6 +216,9 @@ func (self *_parser) parse() (*ast.Program, error) { self.openScope() defer self.closeScope() self.next() + if self.opts.module { + self.scope.allowAwait = true + } program := self.parseProgram() if false { self.errors.Sort() diff --git a/tc39_test.go b/tc39_test.go index ddc87587..d8a964ac 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -316,6 +316,8 @@ func init() { "test/language/eval-code/direct/async-gen-", + "test/language/module-code/export-default-asyncgenerator-", + // generators "test/language/eval-code/direct/gen-", "test/built-ins/GeneratorFunction/", From f5eb8e626068ee675d575356b292651ae7fb5cf5 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 29 Mar 2023 17:32:23 +0300 Subject: [PATCH 110/124] fixup! Merge commit 'd4bf6fde1b86bc185110897884ac99981f08a3c6' into modules --- parser/statement.go | 24 ++++++++++++++++++++++++ tc39_test.go | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/parser/statement.go b/parser/statement.go index 3b528f0e..199c9b36 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -1078,6 +1078,15 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { Idx: idx, LexicalDeclaration: self.parseLexicalDeclaration(self.token), } + case token.ASYNC: + return &ast.ExportDeclaration{ + Idx: idx, + HoistableDeclaration: &ast.HoistableDeclaration{ + FunctionDeclaration: &ast.FunctionDeclaration{ + Function: self.parseMaybeAsyncFunction(true), + }, + }, + } case token.FUNCTION: return &ast.ExportDeclaration{ Idx: idx, @@ -1102,6 +1111,21 @@ func (self *_parser) parseExportDeclaration() *ast.ExportDeclaration { var exp *ast.ExportDeclaration switch self.token { + case token.ASYNC: + f := self.parseMaybeAsyncFunction(false) + if f.Name == nil { + f.Name = &ast.Identifier{Name: unistring.String("default"), Idx: f.Idx0()} + } + exp = &ast.ExportDeclaration{ + Idx: idx, + HoistableDeclaration: &ast.HoistableDeclaration{ + FunctionDeclaration: &ast.FunctionDeclaration{ + Function: f, + IsDefault: true, + }, + }, + IsDefault: true, + } case token.FUNCTION: f := self.parseFunction(false, false, idx) if f.Name == nil { diff --git a/tc39_test.go b/tc39_test.go index d8a964ac..0bffa77d 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -196,6 +196,7 @@ var ( // async iterator "test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true, + "test/language/expressions/dynamic-import/for-await-resolution-and-error.js": true, // legacy number literals "test/language/literals/numeric/non-octal-decimal-integer.js": true, @@ -240,6 +241,9 @@ var ( "test/language/module-code/export-expname-from-as-unpaired-surrogate.js": true, "test/language/module-code/export-expname-unpaired-surrogate.js": true, "test/language/module-code/export-expname-string-binding.js": true, + + // top level duip names + "test/language/module-code/early-dup-top-function.js": true, } featuresBlackList = []string{ @@ -345,6 +349,8 @@ func init() { "test/language/expressions/dynamic-import/assignment-expression/yield-", "test/language/module-code/export-default-generator-", + "test/language/expressions/dynamic-import/namespace/await-ns-get-nested-namespace-props-nrml.js", + // BigInt "test/built-ins/TypedArrayConstructors/BigUint64Array/", "test/built-ins/TypedArrayConstructors/BigInt64Array/", From 798660289973d27a27c8122ee73b75e26713e115 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 12 Sep 2023 16:18:58 +0300 Subject: [PATCH 111/124] Use AsyncFunc to implement modules getting rid of old hack --- builtin_promise.go | 9 +- compiler.go | 245 +++++++++++++++++++++--------------------- compiler_expr.go | 10 ++ compiler_stmt.go | 3 +- func.go | 5 + modules.go | 74 +++++++------ modules_sourcetext.go | 35 ++++-- modules_test.go | 128 +++++++++++++++++++--- runtime.go | 68 ++++-------- tc39_test.go | 26 +++-- vm.go | 87 ++++++++++----- 11 files changed, 422 insertions(+), 268 deletions(-) diff --git a/builtin_promise.go b/builtin_promise.go index 96dd2f5f..165d2e1f 100644 --- a/builtin_promise.go +++ b/builtin_promise.go @@ -1,12 +1,15 @@ package goja import ( - "github.com/dop251/goja/unistring" "reflect" + + "github.com/dop251/goja/unistring" ) -type PromiseState int -type PromiseRejectionOperation int +type ( + PromiseState int + PromiseRejectionOperation int +) type promiseReactionType int diff --git a/compiler.go b/compiler.go index 88abbbe2..c7b48d1a 100644 --- a/compiler.go +++ b/compiler.go @@ -918,7 +918,7 @@ found: s.bindings = s.bindings[:l] } -func (c *compiler) compileModule(module *SourceTextModuleRecord) { +func (c *compiler) compileModule(module *SourceTextModuleRecord) *compiledModule { oldModule := c.module c.module = module oldResolve := c.hostResolveImportedModule @@ -930,26 +930,31 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { in := module.body c.p.scriptOrModule = module c.p.src = in.File - strict := true - inGlobal := false - eval := false c.newScope() scope := c.scope scope.dynamic = true - scope.eval = eval - if !strict && len(in.Body) > 0 { - strict = c.isStrict(in.Body) != nil + scope.strict = true + c.newBlockScope() + scope = c.scope + scope.funcType = funcModule + // scope.variable = true + c.emit(&enterFunc{ + funcType: funcModule, + }) + c.block = &block{ + outer: c.block, + typ: blockScope, + + // needResult: true, } - scope.strict = strict - ownVarScope := eval && strict || true - ownLexScope := !inGlobal || eval - if ownVarScope { - c.newBlockScope() - scope = c.scope - scope.funcType = funcRegular - scope.variable = true + enter := &enterFuncBody{ + funcType: funcModule, + extensible: true, + adjustStack: true, } + + c.emit(enter) for _, in := range module.indirectExportEntries { v, ambiguous := module.ResolveExport(in.exportName) if v == nil || ambiguous { @@ -958,143 +963,133 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { } for _, in := range module.importEntries { - importedModule, err := c.hostResolveImportedModule(module, in.moduleRequest) - if err != nil { - panic(fmt.Errorf("previously resolved module returned error %w", err)) - } - if in.importName != "*" { - resolution, ambiguous := importedModule.ResolveExport(in.importName) - if resolution == nil || ambiguous { - c.compileAmbiguousImport(unistring.NewFromString(in.importName)) - continue - } - } - b, _ := c.scope.bindName(unistring.NewFromString(in.localName)) - b.inStash = true - b.isConst = true + c.compileImportEntry(in) } funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) - numFuncs := len(scope.bindings) - if inGlobal && !ownVarScope { - if numFuncs == len(funcs) { - c.compileFunctionsGlobalAllUnique(funcs) - } else { - c.compileFunctionsGlobal(funcs) - } - } c.compileDeclList(in.DeclarationList, false) - numVars := len(scope.bindings) - numFuncs vars := make([]unistring.String, len(scope.bindings)) for i, b := range scope.bindings { vars[i] = b.name } - if len(vars) > 0 && !ownVarScope && ownLexScope { - if inGlobal { - c.emit(&bindGlobal{ - vars: vars[numFuncs:], - funcs: vars[:numFuncs], - deletable: eval, - }) - } else { - c.emit(&bindVars{names: vars, deletable: eval}) - } - } - var enter *enterBlock - if c.compileLexicalDeclarations(in.Body, ownVarScope || !ownLexScope) { - if ownLexScope { - c.block = &block{ - outer: c.block, - typ: blockScope, - needResult: true, - } - enter = &enterBlock{} - c.emit(enter) - } + if len(vars) > 0 { + c.emit(&bindVars{names: vars, deletable: false}) } - + c.compileLexicalDeclarations(in.Body, true) for _, exp := range in.Body { if imp, ok := exp.(*ast.ImportDeclaration); ok { c.compileImportDeclaration(imp) } } for _, entry := range module.localExportEntries { - name := unistring.NewFromString(entry.localName) - b, ok := scope.boundNames[name] - if !ok { - if entry.localName != "default" { - // TODO fix index - c.throwSyntaxError(0, "exporting unknown binding: %q", name) - } - b, _ = scope.bindName(name) - } + c.compileLocalExportEntry(entry) + } + for _, entry := range module.indirectExportEntries { + c.compileIndirectExportEntry(entry) + } - b.inStash = true - b.markAccessPoint() + c.compileFunctions(funcs) - exportName := unistring.NewFromString(entry.localName) - callback := func(vm *vm, getter func() Value) { - vm.r.modules[module].(*SourceTextModuleInstance).exportGetters[exportName.String()] = getter - } + c.emit(&loadModulePromise{moduleCore: module}) + c.emit(await) // this to stop us execute once after we initialize globals + c.emit(pop) - if entry.lex || !scope.boundNames[exportName].isVar { - c.emit(exportLex{callback: callback}) - } else { - c.emit(export{callback: callback}) - } + c.compileStatements(in.Body, true) + c.emit(loadUndef) + c.emit(ret) + c.updateEnterBlock(&enter.enterBlock) + c.leaveScopeBlock(&enter.enterBlock) + c.popScope() + + scope.finaliseVarAlloc(0) + m := &newModule{ + moduleCore: module, + newAsyncFunc: newAsyncFunc{ + newFunc: newFunc{ + prg: c.p, + name: unistring.String(in.File.Name()), + source: in.File.Source(), + length: 0, + strict: true, + }, + }, } - for _, entry := range module.indirectExportEntries { - otherModule, err := c.hostResolveImportedModule(c.module, entry.moduleRequest) - if err != nil { - panic(fmt.Errorf("previously resolved module returned error %w", err)) - } - if entry.importName == "*" { - continue + c.p = &Program{ + src: in.File, + scriptOrModule: m, + } + c.emit(_loadUndef{}, m, call(0), &setModulePromise{moduleCore: module}) + return &compiledModule{} +} + +func (c *compiler) compileImportEntry(in importEntry) { + importedModule, err := c.hostResolveImportedModule(c.module, in.moduleRequest) + if err != nil { + panic(fmt.Errorf("previously resolved module returned error %w", err)) + } + if in.importName != "*" { + resolution, ambiguous := importedModule.ResolveExport(in.importName) + if resolution == nil || ambiguous { + c.compileAmbiguousImport(unistring.NewFromString(in.importName)) + return } - b, ambiguous := otherModule.ResolveExport(entry.importName) - if ambiguous || b == nil { - c.compileAmbiguousImport(unistring.NewFromString(entry.importName)) - continue + } + b, _ := c.scope.bindName(unistring.NewFromString(in.localName)) + b.inStash = true + b.isConst = true +} + +func (c *compiler) compileLocalExportEntry(entry exportEntry) { + name := unistring.NewFromString(entry.localName) + b, ok := c.scope.boundNames[name] + if !ok { + if entry.localName != "default" { + // TODO fix index + c.throwSyntaxError(0, "exporting unknown binding: %q", name) } + b, _ = c.scope.bindName(name) + } - exportName := unistring.NewFromString(entry.exportName).String() - importName := unistring.NewFromString(b.BindingName).String() - c.emit(exportIndirect{callback: func(vm *vm) { - m := vm.r.modules[module] - m2 := vm.r.modules[b.Module] - m.(*SourceTextModuleInstance).exportGetters[exportName] = func() Value { - return m2.GetBindingValue(importName) - } - }}) + b.inStash = true + b.markAccessPoint() + + exportName := unistring.NewFromString(entry.localName) + module := c.module + callback := func(vm *vm, getter func() Value) { + vm.r.modules[module].(*SourceTextModuleInstance).exportGetters[exportName.String()] = getter } - if len(scope.bindings) > 0 && !ownLexScope { - var lets, consts []unistring.String - for _, b := range c.scope.bindings[numFuncs+numVars:] { - if b.isConst { - consts = append(consts, b.name) - } else { - lets = append(lets, b.name) - } - } - c.emit(&bindGlobal{ - vars: vars[numFuncs:], - funcs: vars[:numFuncs], - lets: lets, - consts: consts, - }) + + if entry.lex || !c.scope.boundNames[exportName].isVar { + c.emit(exportLex{callback: callback}) + } else { + c.emit(export{callback: callback}) } - if !inGlobal || ownVarScope { - c.compileFunctions(funcs) +} + +func (c *compiler) compileIndirectExportEntry(entry exportEntry) { + otherModule, err := c.hostResolveImportedModule(c.module, entry.moduleRequest) + if err != nil { + panic(fmt.Errorf("previously resolved module returned error %w", err)) } - // TODO figure something better 😬 - c.emit(notReallyYield) // this to stop us execute once after we initialize globals - c.compileStatements(in.Body, true) - if enter != nil { - c.leaveScopeBlock(enter) - c.popScope() + if entry.importName == "*" { + return + } + b, ambiguous := otherModule.ResolveExport(entry.importName) + if ambiguous || b == nil { + c.compileAmbiguousImport(unistring.NewFromString(entry.importName)) + return } - scope.finaliseVarAlloc(0) + exportName := unistring.NewFromString(entry.exportName).String() + importName := unistring.NewFromString(b.BindingName).String() + module := c.module + c.emit(exportIndirect{callback: func(vm *vm) { + m := vm.r.modules[module] + m2 := vm.r.modules[b.Module] + m.(*SourceTextModuleInstance).exportGetters[exportName] = func() Value { + return m2.GetBindingValue(importName) + } + }}) } func (c *compiler) compile(in *ast.Program, strict, inGlobal bool, evalVm *vm) { diff --git a/compiler_expr.go b/compiler_expr.go index e311ad0e..2c28667d 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -134,8 +134,18 @@ const ( funcClsInit funcCtor funcDerivedCtor + funcModule ) +type compiledModule struct { + baseCompiledExpr + name *ast.Identifier + body []ast.Statement + source string + declarationList []*ast.VariableDeclaration + functionsList []*ast.VariableDeclaration +} + type compiledFunctionLiteral struct { baseCompiledExpr name *ast.Identifier diff --git a/compiler_stmt.go b/compiler_stmt.go index 0bf5f745..fe5bec2b 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -929,7 +929,8 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { identifier := unistring.NewFromString(value.BindingName).String() localB.getIndirect = func(vm *vm) Value { m := vm.r.modules[value.Module] - return m.GetBindingValue(identifier) + v := m.GetBindingValue(identifier) + return v } } } diff --git a/func.go b/func.go index 9040a531..c2ed7139 100644 --- a/func.go +++ b/func.go @@ -696,6 +696,7 @@ func (ar *asyncRunner) onRejected(call FunctionCall) Value { } func (ar *asyncRunner) step(res Value, done bool, ex *Exception) { + // fmt.Printf("step(%v, %v, %v)\n", res, done, ex) r := ar.f.runtime if done || ex != nil { if ex == nil { @@ -765,9 +766,12 @@ func (g *generator) step() (res Value, resultType resultType, ex *Exception) { } res = g.vm.pop() if ym, ok := res.(*yieldMarker); ok { + // fmt.Println("here") resultType = ym.resultType g.ctx = execCtx{} g.vm.pc = -g.vm.pc + 1 + // fmt.Println(res) + // fmt.Println(g.vm.stack) if res != yieldEmpty { res = g.vm.pop() } else { @@ -777,6 +781,7 @@ func (g *generator) step() (res Value, resultType resultType, ex *Exception) { g.vm.sp = g.vm.sb - 1 g.vm.callStack = g.vm.callStack[:len(g.vm.callStack)-1] // remove the frame with pc == -2, as ret would do } + // fmt.Println(res) return } diff --git a/modules.go b/modules.go index 8ce77976..ec8dcc37 100644 --- a/modules.go +++ b/modules.go @@ -128,7 +128,7 @@ type evaluationState struct { cycleRoot map[ModuleInstance]CyclicModuleInstance asyncEvaluation map[CyclicModuleInstance]bool asyncParentModules map[CyclicModuleInstance][]CyclicModuleInstance - evaluationError map[CyclicModuleInstance]error + evaluationError map[CyclicModuleInstance]interface{} topLevelCapability map[CyclicModuleRecord]*promiseCapability } @@ -141,7 +141,7 @@ func newEvaluationState() *evaluationState { cycleRoot: make(map[ModuleInstance]CyclicModuleInstance), asyncEvaluation: make(map[CyclicModuleInstance]bool), asyncParentModules: make(map[CyclicModuleInstance][]CyclicModuleInstance), - evaluationError: make(map[CyclicModuleInstance]error), + evaluationError: make(map[CyclicModuleInstance]interface{}), topLevelCapability: make(map[CyclicModuleRecord]*promiseCapability), } } @@ -165,8 +165,7 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostR state.status[m] = Evaluated state.evaluationError[m] = err } - capability.reject(r.ToValue(err)) - + capability.reject(r.interfaceErrorToValue(err)) } else { if !state.asyncEvaluation[r.modules[c].(CyclicModuleInstance)] { state.topLevelCapability[c].resolve(_undefined) @@ -180,7 +179,7 @@ func (r *Runtime) innerModuleEvaluation( state *evaluationState, m ModuleRecord, stack *[]CyclicModuleInstance, index uint, resolve HostResolveImportedModuleFunc, -) (idx uint, err error) { +) (idx uint, ex error) { if len(*stack) > 100000 { panic("too deep dependancy stack of 100000") } @@ -198,11 +197,11 @@ func (r *Runtime) innerModuleEvaluation( if _, ok = r.modules[m]; ok { return index, nil } - c, err = cr.Instantiate(r) - if err != nil { + c, ex = cr.Instantiate(r) + if ex != nil { // state.evaluationError[cr] = err // TODO handle this somehow - maybe just panic - return index, err + return index, ex } r.modules[m] = c @@ -221,15 +220,15 @@ func (r *Runtime) innerModuleEvaluation( *stack = append(*stack, c) var requiredModule ModuleRecord for _, required := range cr.RequestedModules() { - requiredModule, err = resolve(m, required) - if err != nil { - state.evaluationError[c] = err - return index, err + requiredModule, ex = resolve(m, required) + if ex != nil { + state.evaluationError[c] = ex + return index, ex } var requiredInstance ModuleInstance - index, err = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) - if err != nil { - return index, err + index, ex = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) + if ex != nil { + return index, ex } if requiredC, ok := requiredInstance.(CyclicModuleInstance); ok { if state.status[requiredC] == Evaluating { @@ -252,10 +251,10 @@ func (r *Runtime) innerModuleEvaluation( r.executeAsyncModule(state, c) } } else { - c, err = c.ExecuteModule(r, nil, nil) - if err != nil { + c, ex = c.ExecuteModule(r, nil, nil) + if ex != nil { // state.evaluationError[c] = err - return index, err + return index, ex } } @@ -350,18 +349,18 @@ func (r *Runtime) gatherAvailableAncestors(state *evaluationState, c CyclicModul } } -func (r *Runtime) asyncModuleExecutionRejected(state *evaluationState, c CyclicModuleInstance, err error) { +func (r *Runtime) asyncModuleExecutionRejected(state *evaluationState, c CyclicModuleInstance, ex interface{}) { if state.status[c] == Evaluated { return } - state.evaluationError[c] = err + state.evaluationError[c] = ex state.status[c] = Evaluated for _, m := range state.asyncParentModules[c] { - r.asyncModuleExecutionRejected(state, m, err) + r.asyncModuleExecutionRejected(state, m, ex) } // TODO handle top level capabiltiy better if cap := state.topLevelCapability[r.findModuleRecord(c).(CyclicModuleRecord)]; cap != nil { - cap.reject(r.ToValue(err)) + cap.reject(r.interfaceErrorToValue(ex)) } } @@ -437,19 +436,30 @@ func (r *Runtime) SetImportModuleDynamically(callback ImportModuleDynamicallyCal // TODO figure out the arguments func (r *Runtime) FinalizeDynamicImport(m ModuleRecord, pcap interface{}, err interface{}) { + // fmt.Println("FinalizeDynamicImport", m, pcap, err) p := pcap.(*promiseCapability) if err != nil { - switch x1 := err.(type) { - case *Exception: - p.reject(x1.val) - case *CompilerSyntaxError: - p.reject(r.builtin_new(r.global.SyntaxError, []Value{newStringValue(x1.Error())})) - case *CompilerReferenceError: - p.reject(r.newError(r.global.ReferenceError, x1.Message)) - default: - p.reject(r.ToValue(err)) - } + p.reject(r.interfaceErrorToValue(err)) return } + // fmt.Println("resolve") p.resolve(r.NamespaceObjectFor(m)) } + +func (r *Runtime) interfaceErrorToValue(err interface{}) Value { + switch x1 := err.(type) { + case *Exception: + return x1.val + case *CompilerSyntaxError: + return r.builtin_new(r.global.SyntaxError, []Value{newStringValue(x1.Error())}) + case *CompilerReferenceError: + return r.newError(r.global.ReferenceError, x1.Message) + case *Object: + if o, ok := x1.self.(*objectGoReflect); ok { + // TODO just not have this + return o.origValue.Interface().(*Exception).Value() + break + } + } + return r.ToValue(err) +} diff --git a/modules_sourcetext.go b/modules_sourcetext.go index 151e6fa6..d6242318 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -17,13 +17,27 @@ type SourceTextModuleInstance struct { moduleRecord *SourceTextModuleRecord // TODO figure out omething less idiotic exportGetters map[string]func() Value - context *context // hacks haxx - stack valueStack + pcap *promiseCapability + asyncPromise *Promise } func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(interface{})) (CyclicModuleInstance, error) { - _, err := rt.continueRunProgram(s.moduleRecord.p, s.context, s.stack) - return s, err + ex := rt.runWrapped(func() { s.pcap.resolve(_undefined) }) + if ex != nil { + return nil, ex + } + // TODO fix + promise := s.asyncPromise + switch promise.state { + case PromiseStateFulfilled: + return s, nil + case PromiseStateRejected: + return nil, rt.vm.exceptionFromValue(promise.result) + case PromiseStatePending: + fallthrough + default: + panic("this should not happen") + } } func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { @@ -424,7 +438,7 @@ func (module *SourceTextModuleRecord) InitializeEnvironment() (err error) { } }() - c.compileModule(module) + _ = c.compileModule(module) module.p = c.p return } @@ -514,11 +528,16 @@ func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInst mi := &SourceTextModuleInstance{ moduleRecord: module, exportGetters: make(map[string]func() Value), + pcap: rt.newPromiseCapability(rt.global.Promise), } rt.modules[module] = mi - // TODO figure a better way - _, err := rt.RunProgram(mi.moduleRecord.p) - return mi, err + _, ex := rt.RunProgram(module.p) + if ex != nil { + mi.pcap.reject(rt.ToValue(ex)) + return nil, ex + } + + return mi, nil } func (module *SourceTextModuleRecord) Evaluate(rt *Runtime) *Promise { diff --git a/modules_test.go b/modules_test.go index 72bdef4b..5d46dc76 100644 --- a/modules_test.go +++ b/modules_test.go @@ -2,7 +2,10 @@ package goja import ( "fmt" + "io/fs" + "sync" "testing" + "testing/fstest" ) func TestSimpleModule(t *testing.T) { @@ -12,8 +15,9 @@ func TestSimpleModule(t *testing.T) { err error } type testCase struct { - a string - b string + fs fs.FS + a string + b string } testCases := map[string]testCase{ @@ -41,7 +45,7 @@ globalThis.s = b() globalThis.s = b() `, b: `export let b = "something"; - export function s (){ + export function s(){ globalThis.p() b = function() {globalThis.p(); return 5 }; }`, @@ -61,9 +65,10 @@ globalThis.s = b() }, "default export arrow": { a: `import b from "dep.js"; -globalThis.s = b() + globalThis.p(); +globalThis.s = b(); `, - b: `export default () => {globalThis.p(); return 5 };`, + b: `globalThis.p(); export default () => {globalThis.p(); return 5 };`, }, "default export with as": { a: `import b from "dep.js"; @@ -79,14 +84,53 @@ globalThis.s = b() b: `import { a } from "a.js"; globalThis.s = a();`, }, + "dynamic import": { + a: ` + globalThis.p(); +import("dep.js").then((imported) => { + globalThis.p() + globalThis.s = imported.default(); +});`, + b: `export default function() {globalThis.p(); return 5;}`, + }, + "dynamic import error": { + a: ` do { + import('dep.js').catch(error => { + if (error.name == "SyntaxError") { +globalThis.s = 5; + } + }); +} while (false); +`, + b: ` +import { x } from "0-fixture.js"; + `, + fs: &fstest.MapFS{ + "0-fixture.js": &fstest.MapFile{ + Data: []byte(` + export * from "1-fixture.js"; + export * from "2-fixture.js"; + `), + }, + "1-fixture.js": &fstest.MapFile{ + Data: []byte(`export var x`), + }, + "2-fixture.js": &fstest.MapFile{ + Data: []byte(`export var x`), + }, + }, + }, } for name, cases := range testCases { - a, b := cases.a, cases.b + cases := cases t.Run(name, func(t *testing.T) { t.Parallel() + mu := sync.Mutex{} cache := make(map[string]cacheElement) var hostResolveImportedModule func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) hostResolveImportedModule = func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) { + mu.Lock() + defer mu.Unlock() k, ok := cache[specifier] if ok { return k.m, k.err @@ -94,11 +138,15 @@ globalThis.s = b() var src string switch specifier { case "a.js": - src = a + src = cases.a case "dep.js": - src = b + src = cases.b default: - panic(specifier) + b, err := fs.ReadFile(cases.fs, specifier) + if err != nil { + panic(specifier) + } + src = string(b) } p, err := ParseModule(specifier, src, hostResolveImportedModule) if err != nil { @@ -109,19 +157,33 @@ globalThis.s = b() return p, nil } + linked := make(map[ModuleRecord]error) + linkMu := new(sync.Mutex) + link := func(m ModuleRecord) error { + linkMu.Lock() + defer linkMu.Unlock() + if err, ok := linked[m]; ok { + return err + } + err := m.Link() + linked[m] = err + return err + } + m, err := hostResolveImportedModule(nil, "a.js") if err != nil { t.Fatalf("got error %s", err) } p := m.(*SourceTextModuleRecord) - err = p.Link() + err = link(p) if err != nil { t.Fatalf("got error %s", err) } for i := 0; i < 10; i++ { i := i + m := m t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() var err error @@ -129,15 +191,55 @@ globalThis.s = b() vm.Set("p", vm.ToValue(func() { // fmt.Println("p called") })) - vm.Set("l", func() { + vm.Set("l", func(v Value) { + fmt.Printf("%+v\n", v) fmt.Println("l called") - fmt.Printf("iter stack ; %+v", vm.vm.iterStack) + fmt.Printf("iter stack ; %+v\n", vm.vm.iterStack) }) if err != nil { t.Fatalf("got error %s", err) } - promise := m.Evaluate(vm) + eventLoopQueue := make(chan func(), 2) // the most basic and likely buggy event loop + vm.SetImportModuleDynamically(func(referencingScriptOrModule interface{}, specifierValue Value, pcap interface{}) { + specifier := specifierValue.String() + + eventLoopQueue <- func() { + ex := vm.runWrapped(func() { + m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) + var ex interface{} + if err == nil { + err = link(m) + if err != nil { + ex = err + } else { + promise := m.Evaluate(vm) + if promise.state == PromiseStateRejected { + ex = promise.Result() + } + } + } + vm.FinalizeDynamicImport(m, pcap, ex) + }) + if ex != nil { + vm.FinalizeDynamicImport(m, pcap, ex) + } + } + }) + var promise *Promise + eventLoopQueue <- func() { + promise = m.Evaluate(vm) + } + outer: + for { + select { + case fn := <-eventLoopQueue: + fn() + default: + break outer + } + } if promise.state != PromiseStateFulfilled { + t.Fatalf("got %+v", promise.Result().Export()) err = promise.Result().Export().(error) t.Fatalf("got error %s", err) } diff --git a/runtime.go b/runtime.go index e5f4b046..6dca1f65 100644 --- a/runtime.go +++ b/runtime.go @@ -609,6 +609,16 @@ func (r *Runtime) newAsyncFunc(name unistring.String, length int, strict bool) ( return } +func (r *Runtime) newModule(name unistring.String, pcap *promiseCapability) (f *asyncFuncObject) { + f = &asyncFuncObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, true) + f.class = classFunction + f.prototype = r.getAsyncFunctionPrototype() + f.val.self = f + f.init(name, intToValue(0)) + return +} + func (r *Runtime) newGeneratorFunc(name unistring.String, length int, strict bool) (f *generatorFuncObject) { f = &generatorFuncObject{} r.initBaseJsFunction(&f.baseJsFuncObject, strict) @@ -1532,53 +1542,6 @@ func (r *Runtime) RunProgram(p *Program) (result Value, err error) { return } -// RunProgram executes a pre-compiled (see Compile()) code in the global context. -func (r *Runtime) continueRunProgram(_ *Program, context *context, stack valueStack) (result Value, err error) { - defer func() { - if x := recover(); x != nil { - if ex, ok := x.(*uncatchableException); ok { - err = *ex - if len(r.vm.callStack) == 0 { - r.leaveAbrupt() - } - } else { - panic(x) - } - } - }() - vm := r.vm - recursive := false - if len(vm.callStack) > 0 { - recursive = true - vm.pushCtx() - vm.stash = &r.global.stash - vm.sb = vm.sp - 1 - } - vm.result = _undefined - // sb := vm.sb - vm.restoreCtx(context) - vm.stack = stack - // vm.sb = sb - // fmt.Println("continue sb ", vm.sb, vm.callStack) - // fmt.Println("stack at continue", vm.stack) - ex := vm.runTry() - if ex == nil { - result = r.vm.result - } else { - err = ex - } - if recursive { - vm.popCtx() - vm.pc = 0 - vm.clearStack() - } else { - vm.stack = nil - vm.prg = nil - r.leave() - } - return -} - // CaptureCallStack appends the current call stack frames to the stack slice (which may be nil) up to the specified depth. // The most recent frame will be the first one. // If depth <= 0 or more than the number of available frames, returns the entire stack. @@ -2578,6 +2541,17 @@ func (r *Runtime) runWrapped(f func()) (err error) { if len(r.vm.callStack) == 0 { r.leave() } else { + /* + fmt.Printf("code: ") + if r.vm.prg != nil { + for _, code := range r.vm.prg.code { + fmt.Printf("{%T: %#v},", code, code) + } + } else { + fmt.Print("no code") + } + fmt.Print("\n") + */ r.vm.clearStack() } return diff --git a/tc39_test.go b/tc39_test.go index 41539e95..ee755349 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -506,18 +506,25 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) eventLoopQueue <- func() { - defer vm.RunString("") // haxx // maybe have leave in ffinalize :?!?! - if err == nil { - err = m.Link() + ex := vm.runWrapped(func() { + var ex interface{} + ex = err if err == nil { - promise := m.Evaluate(vm) - // TODO fix - if promise.state != PromiseStateFulfilled { - err = promise.Result().Export().(error) + err = m.Link() + if err != nil { + ex = err + } else { + promise := m.Evaluate(vm) + if promise.state == PromiseStateRejected { + ex = promise.Result() + } } } + vm.FinalizeDynamicImport(m, pcap, ex) + }) + if ex != nil { + vm.FinalizeDynamicImport(m, pcap, ex) } - vm.FinalizeDynamicImport(m, pcap, err) } }() } @@ -762,9 +769,8 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R early = false promise := m.Evaluate(vm) - // TODO fix if promise.state != PromiseStateFulfilled { - err = promise.Result().Export().(error) + err = vm.vm.exceptionFromValue(promise.Result()) } return } diff --git a/vm.go b/vm.go index 54f4bbe5..56e76b63 100644 --- a/vm.go +++ b/vm.go @@ -312,7 +312,6 @@ type vm struct { pc int stack valueStack sp, sb, args int - oldsp int // haxx stash *stash privEnv *privateEnv @@ -576,17 +575,18 @@ func (vm *vm) run() { if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { break } + pc := vm.pc + if pc < 0 || pc >= len(vm.prg.code) { + break + } /* fmt.Printf("code: ") for _, code := range vm.prg.code[vm.pc:] { fmt.Printf("{%T: %#v},", code, code) } - fmt.Print("\n") + fmt.Println() + fmt.Printf("running: %T: %#v\n", vm.prg.code[pc], vm.prg.code[pc]) //*/ - pc := vm.pc - if pc < 0 || pc >= len(vm.prg.code) { - break - } vm.prg.code[pc].exec(vm) } @@ -1003,7 +1003,11 @@ func (l loadStackLex) exec(vm *vm) { } else { p = &vm.stack[vm.sb+vm.args+int(l)] } - // fmt.Println(vm.stack, vm.sb, vm.args, l, p, *p) + // fmt.Println(vm.stack[1:], vm.sb, vm.args, l) + // fmt.Println("*p=", *p) + if l == 1 { + // debug.PrintStack() + } if *p == nil { vm.throw(errAccessBeforeInit) return @@ -1615,26 +1619,6 @@ func (_shr) exec(vm *vm) { vm.pc++ } -type _notReallyYield struct{} - -var notReallyYield _notReallyYield - -func (_notReallyYield) exec(vm *vm) { - vm.pc++ - mi := vm.r.modules[vm.r.GetActiveScriptOrModule().(ModuleRecord)].(*SourceTextModuleInstance) - if vm.sp > vm.oldsp { - toCopy := vm.stack[vm.oldsp:] - mi.stack = make(valueStack, len(toCopy)) - _ = copy(mi.stack, toCopy) - // fmt.Println("yield sb ", vm.sb, vm.args, mi.stack) - } - // fmt.Println("stack at yield", vm.stack) - context := &context{} - vm.saveCtx(context) - mi.context = context - vm.pc = -1 -} - type jump int32 func (j jump) exec(vm *vm) { @@ -3278,6 +3262,7 @@ func (n loadDynamic) exec(vm *vm) { if val == nil { val = vm.r.globalObject.self.getStr(name, nil) if val == nil { + // fmt.Println("here") vm.throw(vm.r.newReferenceError(name)) return } @@ -3458,11 +3443,15 @@ func (numargs call) exec(vm *vm) { n := int(numargs) v := vm.stack[vm.sp-n-1] // callee obj := vm.toCallee(v) + obj.self.vmCall(vm, n) } func (vm *vm) clearStack() { sp := vm.sp + if sp > len(vm.stack) { + time.Sleep(time.Second) + } stackTail := vm.stack[sp:] for i := range stackTail { stackTail[i] = nil @@ -3693,11 +3682,15 @@ func (e *enterFuncBody) exec(vm *vm) { } } sp := vm.sp + // // fmt.Println("sp", sp) if e.adjustStack { sp -= vm.args } + // // fmt.Println("sp after", sp) nsp := sp + int(e.stackSize) + // // fmt.Println("nsp after", nsp) if e.stackSize > 0 { + // // fmt.Println("expand") vm.stack.expand(nsp - 1) vv := vm.stack[sp:nsp] for i := range vv { @@ -3807,6 +3800,41 @@ func (n *newAsyncFunc) exec(vm *vm) { vm.pc++ } +type loadModulePromise struct { + moduleCore ModuleRecord +} + +func (n *loadModulePromise) exec(vm *vm) { + mi := vm.r.modules[n.moduleCore].(*SourceTextModuleInstance) + vm.push(mi.pcap.promise) + vm.pc++ +} + +type newModule struct { + newAsyncFunc + moduleCore ModuleRecord +} + +func (n *newModule) exec(vm *vm) { + obj := vm.r.newAsyncFunc(n.name, n.length, n.strict) + obj.prg = n.prg + obj.stash = vm.stash + obj.privEnv = vm.privEnv + obj.src = n.source + vm.push(obj.val) + vm.pc++ +} + +type setModulePromise struct { + moduleCore ModuleRecord +} + +func (n *setModulePromise) exec(vm *vm) { + mi := vm.r.modules[n.moduleCore].(*SourceTextModuleInstance) + mi.asyncPromise = vm.pop().Export().(*Promise) + vm.pc++ +} + type newGeneratorFunc struct { newFunc } @@ -3875,7 +3903,7 @@ func getFuncObject(v Value) *Object { } return o } - if v == _undefined { + if v == _undefined || v == _null { return nil } panic(typeError("Value is not an Object")) @@ -4659,7 +4687,7 @@ func (_loadDynamicImport) exec(vm *vm) { pcap := vm.r.newPromiseCapability(vm.r.global.Promise) var specifierStr String - err := vm.r.runWrapped(func() { + err := vm.r.runWrapped(func() { // TODO use try specifierStr = specifier.toString() }) if err != nil { @@ -5732,6 +5760,7 @@ func (r *getPrivateRefId) exec(vm *vm) { } func (y *yieldMarker) exec(vm *vm) { + // // fmt.Println("values", vm.stash.values) vm.pc = -vm.pc // this will terminate the run loop vm.push(y) // marker so the caller knows it's a yield, not a return } From ec99affb41e2b6e9a8fbc4a108011d93849bc268 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 28 Sep 2023 17:01:32 +0300 Subject: [PATCH 112/124] Remove dead code --- modules.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules.go b/modules.go index f2061819..501f954e 100644 --- a/modules.go +++ b/modules.go @@ -458,7 +458,6 @@ func (r *Runtime) interfaceErrorToValue(err interface{}) Value { if o, ok := x1.self.(*objectGoReflect); ok { // TODO just not have this return o.origValue.Interface().(*Exception).Value() - break } } return r.ToValue(err) From 530c10601a5434ec55e290e6e225e2d7a1ce202c Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 3 Oct 2023 11:46:57 +0300 Subject: [PATCH 113/124] TLA Support Includes also some other fixes and simplifications partly due to looking at ECMAScript 14 specification where more stuff are .. specified --- ast/node.go | 2 + compiler.go | 7 +- func.go | 7 +- modules.go | 104 +++++++++++++++++++++------ modules_integration_test.go | 20 +----- modules_sourcetext.go | 72 +++++++++++++++---- modules_test.go | 16 +---- parser/expression.go | 11 +++ parser/scope.go | 1 + parser/statement.go | 7 +- runtime.go | 13 +--- tc39_test.go | 138 ++++++++++++++++++++---------------- vm.go | 12 ++-- 13 files changed, 258 insertions(+), 152 deletions(-) diff --git a/ast/node.go b/ast/node.go index 2afada95..f6db4daf 100644 --- a/ast/node.go +++ b/ast/node.go @@ -707,6 +707,8 @@ type Program struct { ImportEntries []*ImportDeclaration ExportEntries []*ExportDeclaration + HasTLA bool + File *file.File } diff --git a/compiler.go b/compiler.go index b29ac357..434e77bc 100644 --- a/compiler.go +++ b/compiler.go @@ -165,8 +165,11 @@ func (b *binding) emitGetAt(pos int) { } func (b *binding) emitGetP() { - if b.isVar && !b.isArg { - // no-op + if b.getIndirect != nil { + b.markAccessPoint() + b.scope.c.emit(loadIndirect(b.getIndirect), pop) + } else if b.isVar && !b.isArg { + // noop } else { // make sure TDZ is checked b.markAccessPoint() diff --git a/func.go b/func.go index e39d0706..54db6198 100644 --- a/func.go +++ b/func.go @@ -32,6 +32,7 @@ var ( yieldDelegate = &yieldMarker{resultType: resultYieldDelegate} yieldDelegateRes = &yieldMarker{resultType: resultYieldDelegateRes} yieldEmpty = &yieldMarker{resultType: resultYield} + yieldModuleInit = &yieldMarker{resultType: resultYield} ) // AsyncContextTracker is a handler that allows to track an async execution context to ensure it remains @@ -700,7 +701,7 @@ func (ar *asyncRunner) onRejected(call FunctionCall) Value { } func (ar *asyncRunner) step(res Value, done bool, ex *Exception) { - // fmt.Printf("step(%v, %v, %v)\n", res, done, ex) + // fmt.Printf("(%v) -> step(%v, %v, %v)\n", ar.gen.ctx.prg.src.Name(), res, done, ex) r := ar.f.runtime if done || ex != nil { if ex == nil { @@ -722,6 +723,7 @@ func (ar *asyncRunner) step(res Value, done bool, ex *Exception) { handler: &jobCallback{callback: ar.onRejected}, asyncRunner: ar, }) + // fmt.Printf("promise = %#v\n", promise.self) } func (ar *asyncRunner) start(nArgs int) { @@ -770,12 +772,9 @@ func (g *generator) step() (res Value, resultType resultType, ex *Exception) { } res = g.vm.pop() if ym, ok := res.(*yieldMarker); ok { - // fmt.Println("here") resultType = ym.resultType g.ctx = execCtx{} g.vm.pc = -g.vm.pc + 1 - // fmt.Println(res) - // fmt.Println(g.vm.stack) if res != yieldEmpty { res = g.vm.pop() } else { diff --git a/modules.go b/modules.go index 501f954e..ca732b49 100644 --- a/modules.go +++ b/modules.go @@ -2,6 +2,7 @@ package goja import ( "errors" + "sort" ) type HostResolveImportedModuleFunc func(referencingScriptOrModule interface{}, specifier string) (ModuleRecord, error) @@ -126,10 +127,12 @@ type evaluationState struct { dfsAncestorIndex map[ModuleInstance]uint pendingAsyncDependancies map[ModuleInstance]uint cycleRoot map[ModuleInstance]CyclicModuleInstance - asyncEvaluation map[CyclicModuleInstance]bool + asyncEvaluation map[CyclicModuleInstance]uint64 asyncParentModules map[CyclicModuleInstance][]CyclicModuleInstance evaluationError map[CyclicModuleInstance]interface{} topLevelCapability map[CyclicModuleRecord]*promiseCapability + + asyncEvaluationCounter uint64 } func newEvaluationState() *evaluationState { @@ -139,7 +142,7 @@ func newEvaluationState() *evaluationState { dfsAncestorIndex: make(map[ModuleInstance]uint), pendingAsyncDependancies: make(map[ModuleInstance]uint), cycleRoot: make(map[ModuleInstance]CyclicModuleInstance), - asyncEvaluation: make(map[CyclicModuleInstance]bool), + asyncEvaluation: make(map[CyclicModuleInstance]uint64), asyncParentModules: make(map[CyclicModuleInstance][]CyclicModuleInstance), evaluationError: make(map[CyclicModuleInstance]interface{}), topLevelCapability: make(map[CyclicModuleRecord]*promiseCapability), @@ -155,9 +158,15 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostR // TODO implement all the promise stuff stackInstance := []CyclicModuleInstance{} - state := newEvaluationState() + if r.evaluationState == nil { + r.evaluationState = newEvaluationState() + } + if cap, ok := r.evaluationState.topLevelCapability[c]; ok { + return cap.promise.Export().(*Promise) + } capability := r.newPromiseCapability(r.getPromise()) - state.topLevelCapability[c] = capability + r.evaluationState.topLevelCapability[c] = capability + state := r.evaluationState // TODO fix abrupt result _, err := r.innerModuleEvaluation(state, c, &stackInstance, 0, resolve) if err != nil { @@ -167,11 +176,13 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostR } capability.reject(r.interfaceErrorToValue(err)) } else { - if !state.asyncEvaluation[r.modules[c].(CyclicModuleInstance)] { + if state.asyncEvaluation[r.modules[c].(CyclicModuleInstance)] == 0 { state.topLevelCapability[c].resolve(_undefined) } } - // TODO handle completion + if len(r.vm.callStack) == 0 { + r.leave() + } return state.topLevelCapability[c].promise.Export().(*Promise) } @@ -225,11 +236,11 @@ func (r *Runtime) innerModuleEvaluation( state.evaluationError[c] = ex return index, ex } - var requiredInstance ModuleInstance index, ex = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) if ex != nil { return index, ex } + requiredInstance := r.GetModuleInstance(requiredModule) if requiredC, ok := requiredInstance.(CyclicModuleInstance); ok { if state.status[requiredC] == Evaluating { if ancestorIndex := state.dfsAncestorIndex[c]; state.dfsAncestorIndex[requiredC] > ancestorIndex { @@ -239,14 +250,15 @@ func (r *Runtime) innerModuleEvaluation( requiredC = state.cycleRoot[requiredC] // check stuff } - if state.asyncEvaluation[requiredC] { + if state.asyncEvaluation[requiredC] != 0 { state.pendingAsyncDependancies[c]++ state.asyncParentModules[requiredC] = append(state.asyncParentModules[requiredC], c) } } } if state.pendingAsyncDependancies[c] > 0 || c.HasTLA() { - state.asyncEvaluation[c] = true + state.asyncEvaluationCounter++ + state.asyncEvaluation[c] = state.asyncEvaluationCounter if state.pendingAsyncDependancies[c] == 0 { r.executeAsyncModule(state, c) } @@ -262,7 +274,7 @@ func (r *Runtime) innerModuleEvaluation( for i := len(*stack) - 1; i >= 0; i-- { requiredModuleInstance := (*stack)[i] *stack = (*stack)[:i] - if !state.asyncEvaluation[requiredModuleInstance] { + if state.asyncEvaluation[requiredModuleInstance] == 0 { state.status[requiredModuleInstance] = Evaluated } else { state.status[requiredModuleInstance] = Evaluating_Async @@ -280,19 +292,23 @@ func (r *Runtime) executeAsyncModule(state *evaluationState, c CyclicModuleInsta // implement https://262.ecma-international.org/13.0/#sec-execute-async-module // TODO likely wrong p, res, rej := r.NewPromise() - r.performPromiseThen(p, r.ToValue(func() { + r.performPromiseThen(p, r.ToValue(func(call FunctionCall) Value { r.asyncModuleExecutionFulfilled(state, c) - }), r.ToValue(func(err error) { + return nil + }), r.ToValue(func(call FunctionCall) Value { + // we use this signature so that goja doesn't try to infer types and wrap them + err := call.Argument(0) r.asyncModuleExecutionRejected(state, c, err) + return nil }), nil) - c.ExecuteModule(r, res, rej) + _, _ = c.ExecuteModule(r, res, rej) } func (r *Runtime) asyncModuleExecutionFulfilled(state *evaluationState, c CyclicModuleInstance) { if state.status[c] == Evaluated { return } - state.asyncEvaluation[c] = false + state.asyncEvaluation[c] = 0 // TODO fix this for m, i := range r.modules { if i == c { @@ -304,6 +320,9 @@ func (r *Runtime) asyncModuleExecutionFulfilled(state *evaluationState, c Cyclic } execList := make([]CyclicModuleInstance, 0) r.gatherAvailableAncestors(state, c, &execList) + sort.Slice(execList, func(i, j int) bool { + return state.asyncEvaluation[execList[i]] < state.asyncEvaluation[execList[j]] + }) // TODO sort? per when the modules got their AsyncEvaluation set ... somehow for _, m := range execList { if state.status[m] == Evaluated { @@ -318,9 +337,9 @@ func (r *Runtime) asyncModuleExecutionFulfilled(state *evaluationState, c Cyclic continue } state.status[m] = Evaluated - if cap := state.topLevelCapability[r.findModuleRecord(c).(CyclicModuleRecord)]; cap != nil { + if cap := state.topLevelCapability[r.findModuleRecord(result).(CyclicModuleRecord)]; cap != nil { // TODO having the module instances going through Values and back is likely not a *great* idea - cap.resolve(r.ToValue(result)) + cap.resolve(r.ToValue(_undefined)) } } } @@ -434,18 +453,57 @@ func (r *Runtime) SetImportModuleDynamically(callback ImportModuleDynamicallyCal r.importModuleDynamically = callback } -// TODO figure out the arguments -func (r *Runtime) FinalizeDynamicImport(m ModuleRecord, pcap interface{}, err interface{}) { - // fmt.Println("FinalizeDynamicImport", m, pcap, err) - p := pcap.(*promiseCapability) +// TODO figure out whether Result should be an Option thing :shrug: +func (r *Runtime) FinishLoadingImportModule(referrer interface{}, specifier Value, payload interface{}, result ModuleRecord, err interface{}) { + // https://262.ecma-international.org/14.0/#sec-FinishLoadingImportedModule + if err == nil { + // a. a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then + // i. i. Assert: That Record's [[Module]] is result.[[Value]]. + // b. b. Else, append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. + } + // 2. 2. If payload is a GraphLoadingState Record, then + // a. a. Perform ContinueModuleLoading(payload, result). + // 3. 3. Else, + // a. a. Perform ContinueDynamicImport(payload, result). + r.continueDynamicImport(payload.(*promiseCapability), result, err) // TODO better type inferance +} + +func (r *Runtime) continueDynamicImport(promiseCapability *promiseCapability, result ModuleRecord, err interface{}) { + // https://262.ecma-international.org/14.0/#sec-ContinueDynamicImport if err != nil { - p.reject(r.interfaceErrorToValue(err)) + promiseCapability.reject(r.interfaceErrorToValue(err)) return } - // fmt.Println("resolve") - p.resolve(r.NamespaceObjectFor(m)) + // 2. 2. Let module be moduleCompletion.[[Value]]. + module := result + // 3. 3. Let loadPromise be module.LoadRequestedModules(). + loadPromise := r.promiseResolve(r.getPromise(), _undefined) // TODO fix + + rejectionClosure := r.ToValue(func(call FunctionCall) Value { + promiseCapability.reject(call.Argument(0)) + return nil + }) + linkAndEvaluateClosure := r.ToValue(func(call FunctionCall) Value { + // a. a. Let link be Completion(module.Link()). + err := module.Link() + if err != nil { + promiseCapability.reject(r.interfaceErrorToValue(err)) + return nil + } + evaluationPromise := module.Evaluate(r) + onFullfill := r.ToValue(func(call FunctionCall) Value { + namespace := r.NamespaceObjectFor(module) + promiseCapability.resolve(namespace) + return nil + }) + r.performPromiseThen(evaluationPromise, onFullfill, rejectionClosure, nil) + return nil + }) + + r.performPromiseThen(loadPromise.Export().(*Promise), linkAndEvaluateClosure, rejectionClosure, nil) } +// Drop this or make it more func (r *Runtime) interfaceErrorToValue(err interface{}) Value { switch x1 := err.(type) { case *Exception: diff --git a/modules_integration_test.go b/modules_integration_test.go index c4918e10..59d131ae 100644 --- a/modules_integration_test.go +++ b/modules_integration_test.go @@ -215,22 +215,8 @@ func TestNotSourceModulesBigTestDynamicImport(t *testing.T) { m, err := resolver.resolve(referencingScriptOrModule, specifier) eventLoopQueue <- func() { - defer vm.RunString("") // haxx // maybe have leave in ffinalize :?!?! - if err == nil { - err = m.Link() - if err == nil { - var promise *goja.Promise - if c, ok := m.(goja.CyclicModuleRecord); ok { - promise = vm.CyclicModuleRecordEvaluate(c, resolver.resolve) - } else { - promise = m.Evaluate(vm) - } - if promise.State() != goja.PromiseStateFulfilled { - err = promise.Result().Export().(error) - } - } - } - vm.FinalizeDynamicImport(m, promiseCapability, err) + defer vm.RunString("") // FIXME haxx // the specification kind of doesn't have a solutioo for htis it seems + vm.FinishLoadingImportModule(referencingScriptOrModule, specifierValue, promiseCapability, m, err) } }() }) @@ -325,7 +311,7 @@ func (s *cyclicModuleImpl) Link() error { } func (s *cyclicModuleImpl) Evaluate(rt *goja.Runtime) *goja.Promise { - panic("this should never be called") + return rt.CyclicModuleRecordEvaluate(s, s.resolve) } func (s *cyclicModuleImpl) ResolveExport(exportName string, resolveset ...goja.ResolveSetElement) (*goja.ResolvedBinding, bool) { diff --git a/modules_sourcetext.go b/modules_sourcetext.go index aad11c3b..87d0df2f 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -22,22 +22,61 @@ type SourceTextModuleInstance struct { } func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(interface{})) (CyclicModuleInstance, error) { - ex := rt.runWrapped(func() { s.pcap.resolve(_undefined) }) - if ex != nil { - return nil, ex + // fmt.Println("ExecuteModule", s.moduleRecord.p.src.Name(), s.HasTLA()) + //s.pcap.resolve(_undefined) + //* + // THis actually should just continue the execution instead of moving it off. + // Unfortunately this requires access to the asyncRunner + promiseP := s.pcap.promise.self.(*Promise) + // fmt.Println(promiseP.fulfillReactions) + // fmt.Println(promiseP) + if len(promiseP.fulfillReactions) == 1 { + // FIXME figure out how to do this ... better + promiseP.fulfill(_undefined) + rt.leave() + // ar := s.asyncRunner + // ar := promiseP.fulfillReactions[0].asyncRunner + // fmt.Println(ar) + // _ = ar.onFulfilled(FunctionCall{Arguments: []Value{_undefined}}) + } else { + // fmt.Println("bad", s.moduleRecord.p.src.Name()) + // debug.PrintStack() } - // TODO fix + //*/ + promise := s.asyncPromise - switch promise.state { - case PromiseStateFulfilled: - return s, nil - case PromiseStateRejected: - return nil, rt.vm.exceptionFromValue(promise.result) - case PromiseStatePending: - fallthrough - default: - panic("this should not happen") + if !s.HasTLA() { + if res != nil { + panic("wat") + } + switch s.asyncPromise.state { + case PromiseStateFulfilled: + return s, nil + case PromiseStateRejected: + return nil, rt.vm.exceptionFromValue(promise.result) + case PromiseStatePending: + // TODO !??!? + panic("wat now") + return s, nil + default: + panic("Somehow promise from a module execution is in invalid state") + } + } + if res == nil { + panic("bad") + return nil, nil } + rt.performPromiseThen(s.asyncPromise, rt.ToValue(func(call FunctionCall) Value { + // fmt.Println("!!!!res") + res(call.Argument(0)) + return nil + }), rt.ToValue(func(call FunctionCall) Value { + v := call.Argument(0) + // fmt.Printf("rej %#v\n", v) + rej(v) + return nil + }), nil) + return nil, nil } func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { @@ -49,7 +88,7 @@ func (s *SourceTextModuleInstance) GetBindingValue(name string) Value { } func (s *SourceTextModuleInstance) HasTLA() bool { - return false // TODO implement when TLA is added + return s.moduleRecord.hasTLA // TODO implement when TLA is added } type SourceTextModuleRecord struct { @@ -57,6 +96,7 @@ type SourceTextModuleRecord struct { p *Program // context // importmeta + hasTLA bool requestedModules []string importEntries []importEntry localExportEntries []exportEntry @@ -352,6 +392,7 @@ func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFun // realm isn't implement // environment is undefined // namespace is undefined + hasTLA: body.HasTLA, requestedModules: requestedModules, // hostDefined TODO body: body, @@ -525,13 +566,16 @@ func (module *SourceTextModuleRecord) ResolveExport(exportName string, resolvese } func (module *SourceTextModuleRecord) Instantiate(rt *Runtime) (CyclicModuleInstance, error) { + // fmt.Println("Instantiate", module.p.src.Name()) mi := &SourceTextModuleInstance{ moduleRecord: module, exportGetters: make(map[string]func() Value), pcap: rt.newPromiseCapability(rt.getPromise()), } rt.modules[module] = mi + rt.vm.callStack = append(rt.vm.callStack, context{}) _, ex := rt.RunProgram(module.p) + rt.vm.callStack = rt.vm.callStack[:len(rt.vm.callStack)-1] if ex != nil { mi.pcap.reject(rt.ToValue(ex)) return nil, ex diff --git a/modules_test.go b/modules_test.go index 5d46dc76..46c827ea 100644 --- a/modules_test.go +++ b/modules_test.go @@ -206,22 +206,10 @@ import { x } from "0-fixture.js"; eventLoopQueue <- func() { ex := vm.runWrapped(func() { m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) - var ex interface{} - if err == nil { - err = link(m) - if err != nil { - ex = err - } else { - promise := m.Evaluate(vm) - if promise.state == PromiseStateRejected { - ex = promise.Result() - } - } - } - vm.FinalizeDynamicImport(m, pcap, ex) + vm.FinishLoadingImportModule(referencingScriptOrModule, specifierValue, pcap, m, err) }) if ex != nil { - vm.FinalizeDynamicImport(m, pcap, ex) + vm.FinishLoadingImportModule(referencingScriptOrModule, specifierValue, pcap, nil, ex) } } }) diff --git a/parser/expression.go b/parser/expression.go index 916a9d63..a0bcaa43 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -942,6 +942,17 @@ func (self *_parser) parseUnaryExpression() ast.Expression { if self.scope.inFuncParams { self.error(idx, "Illegal await-expression in formal parameters of async function") } + scope := self.scope + for scope != nil { + if scope.inFunction { + break + } + if scope.outer == nil { + scope.hasTLA = true + break + } + scope = scope.outer + } return &ast.AwaitExpression{ Await: idx, Argument: self.parseUnaryExpression(), diff --git a/parser/scope.go b/parser/scope.go index da9f40ad..2853713d 100644 --- a/parser/scope.go +++ b/parser/scope.go @@ -20,6 +20,7 @@ type _scope struct { declarationList []*ast.VariableDeclaration importEntries []*ast.ImportDeclaration exportEntries []*ast.ExportDeclaration + hasTLA bool labels []unistring.String } diff --git a/parser/statement.go b/parser/statement.go index a4fa5b14..ac27ca4a 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -910,7 +910,11 @@ func (self *_parser) parseIfStatement() ast.Statement { func (self *_parser) parseSourceElements() (body []ast.Statement) { for self.token != token.EOF { self.scope.allowLet = true - self.scope.allowImportExport = true + if self.opts.module { + self.scope.allowImportExport = true + self.scope.allowAwait = true + self.scope.inAsync = true + } body = append(body, self.parseStatement()) } @@ -923,6 +927,7 @@ func (self *_parser) parseProgram() *ast.Program { DeclarationList: self.scope.declarationList, ImportEntries: self.scope.importEntries, ExportEntries: self.scope.exportEntries, + HasTLA: self.scope.hasTLA, File: self.file, } self.file.SetSourceMap(self.parseSourceMap()) diff --git a/runtime.go b/runtime.go index 4267bbd7..604ebba0 100644 --- a/runtime.go +++ b/runtime.go @@ -199,6 +199,7 @@ type Runtime struct { getImportMetaProperties func(ModuleRecord) []MetaProperty finalizeImportMeta func(*Object, ModuleRecord) importModuleDynamically ImportModuleDynamicallyCallback + evaluationState *evaluationState jobQueue []func() @@ -554,16 +555,6 @@ func (r *Runtime) newAsyncFunc(name unistring.String, length int, strict bool) ( return } -func (r *Runtime) newModule(name unistring.String, pcap *promiseCapability) (f *asyncFuncObject) { - f = &asyncFuncObject{} - r.initBaseJsFunction(&f.baseJsFuncObject, true) - f.class = classFunction - f.prototype = r.getAsyncFunctionPrototype() - f.val.self = f - f.init(name, intToValue(0)) - return -} - func (r *Runtime) newGeneratorFunc(name unistring.String, length int, strict bool) (f *generatorFuncObject) { f = &generatorFuncObject{} r.initBaseJsFunction(&f.baseJsFuncObject, strict) @@ -1938,6 +1929,8 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value { func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value { return func(call FunctionCall) Value { + // fmt.Println("call=", call) + // fmt.Println("call.Arguments=", call.Arguments) typ := value.Type() nargs := typ.NumIn() var in []reflect.Value diff --git a/tc39_test.go b/tc39_test.go index ee755349..c05fa27c 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -211,6 +211,20 @@ var ( // Skip due to regexp named groups "test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": true, + + // some export syntax taht isn't supported + "test/language/module-code/top-level-await/syntax/export-var-await-expr-this.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-null.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-identifier.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-template-literal.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-regexp.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-new-expr.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-literal-number.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-obj-literal.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-literal-string.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-nested.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-func-expression.js": true, + "test/language/module-code/top-level-await/syntax/export-var-await-expr-array-literal.js": true, } featuresBlackList = []string{ @@ -237,7 +251,6 @@ var ( "ShadowRealm", "SharedArrayBuffer", "error-cause", - "top-level-await", "decorators", "regexp-v-flag", } @@ -456,7 +469,6 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. vm.Set("IgnorableTestError", ignorableTestError) vm.RunProgram(ctx.sabStub) var out []string - eventLoopQueue := make(chan func(), 2) // the most basic and likely buggy event loop async := meta.hasFlag("async") type cacheElement struct { @@ -498,35 +510,14 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. return p, nil } + eventLoopQueue := make(chan func(), 10) dynamicImport := meta.hasFeature("dynamic-import") if dynamicImport { vm.importModuleDynamically = func(referencingScriptOrModule interface{}, specifierValue Value, pcap interface{}) { + // fmt.Printf("import(%s, %s, %s)\n", referencingScriptOrModule, specifierValue, pcap) specifier := specifierValue.String() - go func() { - m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) - - eventLoopQueue <- func() { - ex := vm.runWrapped(func() { - var ex interface{} - ex = err - if err == nil { - err = m.Link() - if err != nil { - ex = err - } else { - promise := m.Evaluate(vm) - if promise.state == PromiseStateRejected { - ex = promise.Result() - } - } - } - vm.FinalizeDynamicImport(m, pcap, ex) - }) - if ex != nil { - vm.FinalizeDynamicImport(m, pcap, ex) - } - } - }() + m, err := hostResolveImportedModule(referencingScriptOrModule, specifier) + vm.FinishLoadingImportModule(referencingScriptOrModule, specifierValue, pcap, m, err) } } if async { @@ -543,12 +534,53 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. var err error var early bool + var asyncError <-chan error if meta.hasFlag("module") { - err, early = ctx.runTC39Module(name, src, meta.Includes, vm, hostResolveImportedModule) + err, early, asyncError = ctx.runTC39Module(name, src, meta.Includes, vm, hostResolveImportedModule) } else { err, early = ctx.runTC39Script(name, src, meta.Includes, vm) } + if vm.vm.sp != 0 { + t.Fatalf("sp: %d", vm.vm.sp) + } + + if l := len(vm.vm.iterStack); l > 0 { + t.Fatalf("iter stack is not empty: %d", l) + } + if async && err == nil { + for { + complete := false + for _, line := range out { + if strings.HasPrefix(line, "Test262:AsyncTestFailure:") { + t.Fatal(line) + } else if line == "Test262:AsyncTestComplete" { + complete = true + } + } + if complete { + break + } + for _, line := range out { + t.Log(line) + } + select { + case fn := <-eventLoopQueue: + fn() + case <-time.After(time.Millisecond * 5000): + t.Fatal("nothing happened in 5s :(") + + } + } + if asyncError != nil { + select { + case err = <-asyncError: + case <-time.After(time.Millisecond * 5000): + t.Fatal("nothing happened in 5s :(") + } + } + } + if err != nil { if meta.Negative.Type == "" { if err, ok := err.(*Exception); ok { @@ -602,38 +634,6 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. t.Fatalf("%s: Expected error: %v", name, err) } } - - if vm.vm.sp != 0 { - t.Fatalf("sp: %d", vm.vm.sp) - } - - if l := len(vm.vm.iterStack); l > 0 { - t.Fatalf("iter stack is not empty: %d", l) - } - if async { - for { - complete := false - for _, line := range out { - if strings.HasPrefix(line, "Test262:AsyncTestFailure:") { - t.Fatal(line) - } else if line == "Test262:AsyncTestComplete" { - complete = true - } - } - if complete { - return - } - for _, line := range out { - t.Log(line) - } - select { - case fn := <-eventLoopQueue: - fn() - case <-time.After(time.Millisecond * 5000): - t.Fatal("nothing happened in 5s :(") - } - } - } } func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { @@ -738,7 +738,7 @@ func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error { return err } -func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *Runtime, hostResolveImportedModule HostResolveImportedModuleFunc) (err error, early bool) { +func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *Runtime, hostResolveImportedModule HostResolveImportedModuleFunc) (err error, early bool, asyncError chan error) { early = true err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm) if err != nil { @@ -769,8 +769,22 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R early = false promise := m.Evaluate(vm) - if promise.state != PromiseStateFulfilled { + + asyncError = make(chan error, 1) + if promise.state == PromiseStateRejected { err = vm.vm.exceptionFromValue(promise.Result()) + } else if promise.state == PromiseStatePending { + vm.performPromiseThen(promise, vm.ToValue(func(_ FunctionCall) Value { + close(asyncError) + return nil + }), vm.ToValue(func(call FunctionCall) Value { + asyncError <- vm.vm.exceptionFromValue(call.Argument(0)) + close(asyncError) + return nil + }), nil) + } else { + // TODO ?!?! + close(asyncError) } return } diff --git a/vm.go b/vm.go index 80fa3439..ff8c84e2 100644 --- a/vm.go +++ b/vm.go @@ -834,6 +834,8 @@ func (vm *vm) runTry() (ex *Exception) { func (vm *vm) runTryInner() (ex *Exception) { defer func() { if x := recover(); x != nil { + // fmt.Printf("x=%#v\n", x) + // debug.PrintStack() ex = vm.handleThrow(x) } }() @@ -4687,7 +4689,7 @@ func (_loadDynamicImport) exec(vm *vm) { pcap := vm.r.newPromiseCapability(vm.r.getPromise()) var specifierStr String - err := vm.r.runWrapped(func() { // TODO use try + err := vm.r.try(func() { specifierStr = specifier.toString() }) if err != nil { @@ -5650,10 +5652,10 @@ func (vm *vm) exceptionFromValue(x interface{}) *Exception { /* if vm.prg != nil { vm.prg.dumpCode(log.Printf) - } - log.Print("Stack: ", string(debug.Stack())) - panic(fmt.Errorf("Panic at %d: %v", vm.pc, x)) - */ + } + log.Print("Stack: ", string(debug.Stack())) + panic(fmt.Errorf("Panic at %d: %v", vm.pc, x)) + //*/ return nil } if ex.stack == nil { From 25072282749345be29fc268b57c978aaa4cbb275 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 5 Oct 2023 17:33:23 +0300 Subject: [PATCH 114/124] Fix go vet --- modules_sourcetext.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules_sourcetext.go b/modules_sourcetext.go index 87d0df2f..452e14ca 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -47,7 +47,7 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(inte promise := s.asyncPromise if !s.HasTLA() { if res != nil { - panic("wat") + panic("goja bug where a not async module was executed as async on") } switch s.asyncPromise.state { case PromiseStateFulfilled: @@ -56,15 +56,13 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(inte return nil, rt.vm.exceptionFromValue(promise.result) case PromiseStatePending: // TODO !??!? - panic("wat now") - return s, nil + panic("goja bug where an sync module was not executed synchronously") default: panic("Somehow promise from a module execution is in invalid state") } } if res == nil { - panic("bad") - return nil, nil + panic("goja bug where an async module was not executed as async") } rt.performPromiseThen(s.asyncPromise, rt.ToValue(func(call FunctionCall) Value { // fmt.Println("!!!!res") From 789cda2fbde4d426c251b8b06c303a7c52dd4e18 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 5 Oct 2023 17:40:22 +0300 Subject: [PATCH 115/124] Fix staticcheck and some cleanup --- compiler.go | 4 ++-- compiler_expr.go | 9 --------- func.go | 1 - modules.go | 11 ++++++----- modules_sourcetext.go | 6 +----- vm.go | 5 ----- 6 files changed, 9 insertions(+), 27 deletions(-) diff --git a/compiler.go b/compiler.go index 434e77bc..17467e74 100644 --- a/compiler.go +++ b/compiler.go @@ -921,7 +921,7 @@ found: s.bindings = s.bindings[:l] } -func (c *compiler) compileModule(module *SourceTextModuleRecord) *compiledModule { +func (c *compiler) compileModule(module *SourceTextModuleRecord) { oldModule := c.module c.module = module oldResolve := c.hostResolveImportedModule @@ -1022,7 +1022,7 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) *compiledModule scriptOrModule: m, } c.emit(_loadUndef{}, m, call(0), &setModulePromise{moduleCore: module}) - return &compiledModule{} + return } func (c *compiler) compileImportEntry(in importEntry) { diff --git a/compiler_expr.go b/compiler_expr.go index 2c28667d..fa849c9b 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -137,15 +137,6 @@ const ( funcModule ) -type compiledModule struct { - baseCompiledExpr - name *ast.Identifier - body []ast.Statement - source string - declarationList []*ast.VariableDeclaration - functionsList []*ast.VariableDeclaration -} - type compiledFunctionLiteral struct { baseCompiledExpr name *ast.Identifier diff --git a/func.go b/func.go index 54db6198..32e207e3 100644 --- a/func.go +++ b/func.go @@ -32,7 +32,6 @@ var ( yieldDelegate = &yieldMarker{resultType: resultYieldDelegate} yieldDelegateRes = &yieldMarker{resultType: resultYieldDelegateRes} yieldEmpty = &yieldMarker{resultType: resultYield} - yieldModuleInit = &yieldMarker{resultType: resultYield} ) // AsyncContextTracker is a handler that allows to track an async execution context to ensure it remains diff --git a/modules.go b/modules.go index ca732b49..98845948 100644 --- a/modules.go +++ b/modules.go @@ -456,11 +456,12 @@ func (r *Runtime) SetImportModuleDynamically(callback ImportModuleDynamicallyCal // TODO figure out whether Result should be an Option thing :shrug: func (r *Runtime) FinishLoadingImportModule(referrer interface{}, specifier Value, payload interface{}, result ModuleRecord, err interface{}) { // https://262.ecma-international.org/14.0/#sec-FinishLoadingImportedModule - if err == nil { - // a. a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then - // i. i. Assert: That Record's [[Module]] is result.[[Value]]. - // b. b. Else, append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. - } + // if err == nil { + // a. a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then + // i. i. Assert: That Record's [[Module]] is result.[[Value]]. + // b. b. Else, append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. + + // } // 2. 2. If payload is a GraphLoadingState Record, then // a. a. Perform ContinueModuleLoading(payload, result). // 3. 3. Else, diff --git a/modules_sourcetext.go b/modules_sourcetext.go index 452e14ca..43cfbe04 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -38,11 +38,7 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(inte // ar := promiseP.fulfillReactions[0].asyncRunner // fmt.Println(ar) // _ = ar.onFulfilled(FunctionCall{Arguments: []Value{_undefined}}) - } else { - // fmt.Println("bad", s.moduleRecord.p.src.Name()) - // debug.PrintStack() } - //*/ promise := s.asyncPromise if !s.HasTLA() { @@ -477,7 +473,7 @@ func (module *SourceTextModuleRecord) InitializeEnvironment() (err error) { } }() - _ = c.compileModule(module) + c.compileModule(module) module.p = c.p return } diff --git a/vm.go b/vm.go index ff8c84e2..04e4ff78 100644 --- a/vm.go +++ b/vm.go @@ -1005,11 +1005,6 @@ func (l loadStackLex) exec(vm *vm) { } else { p = &vm.stack[vm.sb+vm.args+int(l)] } - // fmt.Println(vm.stack[1:], vm.sb, vm.args, l) - // fmt.Println("*p=", *p) - if l == 1 { - // debug.PrintStack() - } if *p == nil { vm.throw(errAccessBeforeInit) return From a059c528acb2f3424a9e48f14a700c4ef61f53bc Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 6 Oct 2023 11:10:31 +0300 Subject: [PATCH 116/124] Fix old parser test :facepalm: --- parser/statement.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parser/statement.go b/parser/statement.go index ac27ca4a..d069e9b7 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -98,13 +98,13 @@ func (self *_parser) parseStatement() ast.Statement { case token.TRY: return self.parseTryStatement() case token.EXPORT: - if !allowImportExport { - self.error(self.idx, "export only allowed in global scope") + if !self.opts.module { + self.error(self.idx, "export not supported in script") self.next() return &ast.BadStatement{From: self.idx, To: self.idx + 1} } - if !self.opts.module { - self.error(self.idx, "export not supported in script") + if !allowImportExport { + self.error(self.idx, "export only allowed in global scope") self.next() return &ast.BadStatement{From: self.idx, To: self.idx + 1} } From bf5368ff56ad0603b0306dd3c3e08b5c1840cd56 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 6 Oct 2023 11:13:12 +0300 Subject: [PATCH 117/124] more staticcheck fixes --- compiler.go | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler.go b/compiler.go index 17467e74..b6f8a1dd 100644 --- a/compiler.go +++ b/compiler.go @@ -1022,7 +1022,6 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { scriptOrModule: m, } c.emit(_loadUndef{}, m, call(0), &setModulePromise{moduleCore: module}) - return } func (c *compiler) compileImportEntry(in importEntry) { From a0920e6cfa271fba7bfa8c898360442e03646498 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 26 Oct 2023 12:00:35 +0300 Subject: [PATCH 118/124] Refactor modules_test.go to be more easy to work with --- modules_test.go | 197 +++++++++++++++++++++--------------------------- 1 file changed, 85 insertions(+), 112 deletions(-) diff --git a/modules_test.go b/modules_test.go index 46c827ea..2191119b 100644 --- a/modules_test.go +++ b/modules_test.go @@ -2,10 +2,8 @@ package goja import ( "fmt" - "io/fs" "sync" "testing" - "testing/fstest" ) func TestSimpleModule(t *testing.T) { @@ -14,111 +12,109 @@ func TestSimpleModule(t *testing.T) { m ModuleRecord err error } - type testCase struct { - fs fs.FS - a string - b string - } - testCases := map[string]testCase{ + testCases := map[string]map[string]string{ "function export": { - a: `import { b } from "dep.js"; -globalThis.s = b() -`, - b: `export function b() {globalThis.p(); return 5 };`, + "a.js": ` + import { b } from "dep.js"; + globalThis.s = b() + `, + "dep.js": `export function b() { return 5 };`, }, "let export": { - a: `import { b } from "dep.js"; -globalThis.s = b() -`, - b: `export let b = function() {globalThis.p(); return 5 };`, + "a.js": ` + import { b } from "dep.js"; + globalThis.s = b() + `, + "dep.js": `export let b = function() {return 5 };`, }, "const export": { - a: `import { b } from "dep.js"; -globalThis.s = b() -`, - b: `export const b = function() {globalThis.p(); return 5 };`, + "a.js": ` + import { b } from "dep.js"; + globalThis.s = b() + `, + "dep.js": `export const b = function() { return 5 };`, }, "let export with update": { - a: `import { s , b} from "dep.js"; - s() -globalThis.s = b() -`, - b: `export let b = "something"; - export function s(){ - globalThis.p() - b = function() {globalThis.p(); return 5 }; - }`, + "a.js": ` + import { s , b} from "dep.js"; + s() + globalThis.s = b() + `, + "dep.js": ` + export let b = "something"; + export function s(){ + b = function() { + return 5; + }; + }`, }, "default export": { - a: `import b from "dep.js"; -globalThis.s = b() -`, - b: `export default function() {globalThis.p(); return 5 };`, + "a.js": ` + import b from "dep.js"; + globalThis.s = b() + `, + "dep.js": `export default function() { return 5 };`, }, "default loop": { - a: `import b from "a.js"; -export default function() {return 5;}; -globalThis.s = b() -`, - b: ``, + "a.js": ` + import b from "a.js"; + export default function() {return 5;}; + globalThis.s = b() + `, }, "default export arrow": { - a: `import b from "dep.js"; - globalThis.p(); -globalThis.s = b(); -`, - b: `globalThis.p(); export default () => {globalThis.p(); return 5 };`, + "a.js": ` + import b from "dep.js"; + globalThis.s = b(); + `, + "dep.js": `export default () => {return 5 };`, }, "default export with as": { - a: `import b from "dep.js"; -globalThis.s = b() -`, - b: `function f() {return 5;}; - export { f as default };`, + "a.js": ` + import b from "dep.js"; + globalThis.s = b() + `, + "dep.js": ` + function f() {return 5;}; + export { f as default }; + `, }, "export usage before evaluation as": { - a: `import "dep.js"; - export function a() {return 5;} -`, - b: `import { a } from "a.js"; - globalThis.s = a();`, + "a.js": ` + import "dep.js"; + export function a() { return 5; } + `, + "dep.js": ` + import { a } from "a.js"; + globalThis.s = a(); + `, }, "dynamic import": { - a: ` - globalThis.p(); -import("dep.js").then((imported) => { - globalThis.p() - globalThis.s = imported.default(); -});`, - b: `export default function() {globalThis.p(); return 5;}`, + "a.js": ` + import("dep.js").then((imported) => { + globalThis.s = imported.default(); + }); + `, + "dep.js": `export default function() { return 5; }`, }, "dynamic import error": { - a: ` do { - import('dep.js').catch(error => { - if (error.name == "SyntaxError") { -globalThis.s = 5; - } - }); -} while (false); -`, - b: ` -import { x } from "0-fixture.js"; + "a.js": ` + do { + import('dep.js').catch(error => { + if (error.name == "SyntaxError") { + globalThis.s = 5; + } + }); + } while (false); `, - fs: &fstest.MapFS{ - "0-fixture.js": &fstest.MapFile{ - Data: []byte(` + "dep.js": `import { x } from "0-fixture.js";`, + "0-fixture.js": ` export * from "1-fixture.js"; - export * from "2-fixture.js"; - `), - }, - "1-fixture.js": &fstest.MapFile{ - Data: []byte(`export var x`), - }, - "2-fixture.js": &fstest.MapFile{ - Data: []byte(`export var x`), - }, - }, + export * from "2-fixture.js"; + `, + "1-fixture.js": `export var x`, + "2-fixture.js": `export var x`, }, } for name, cases := range testCases { @@ -135,19 +131,8 @@ import { x } from "0-fixture.js"; if ok { return k.m, k.err } - var src string - switch specifier { - case "a.js": - src = cases.a - case "dep.js": - src = cases.b - default: - b, err := fs.ReadFile(cases.fs, specifier) - if err != nil { - panic(specifier) - } - src = string(b) - } + + src := string(cases[specifier]) p, err := ParseModule(specifier, src, hostResolveImportedModule) if err != nil { cache[specifier] = cacheElement{err: err} @@ -186,19 +171,7 @@ import { x } from "0-fixture.js"; m := m t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { t.Parallel() - var err error vm := New() - vm.Set("p", vm.ToValue(func() { - // fmt.Println("p called") - })) - vm.Set("l", func(v Value) { - fmt.Printf("%+v\n", v) - fmt.Println("l called") - fmt.Printf("iter stack ; %+v\n", vm.vm.iterStack) - }) - if err != nil { - t.Fatalf("got error %s", err) - } eventLoopQueue := make(chan func(), 2) // the most basic and likely buggy event loop vm.SetImportModuleDynamically(func(referencingScriptOrModule interface{}, specifierValue Value, pcap interface{}) { specifier := specifierValue.String() @@ -214,9 +187,8 @@ import { x } from "0-fixture.js"; } }) var promise *Promise - eventLoopQueue <- func() { - promise = m.Evaluate(vm) - } + eventLoopQueue <- func() { promise = m.Evaluate(vm) } + outer: for { select { @@ -226,9 +198,10 @@ import { x } from "0-fixture.js"; break outer } } + if promise.state != PromiseStateFulfilled { t.Fatalf("got %+v", promise.Result().Export()) - err = promise.Result().Export().(error) + err := promise.Result().Export().(error) t.Fatalf("got error %s", err) } v := vm.Get("s") From 1b8ab0d5a61cee3a05914b94a76b53dd8ffe7318 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 8 Nov 2023 15:10:54 +0200 Subject: [PATCH 119/124] Support 'eval' with imported identifiers --- compiler.go | 32 +++++++++++++----- compiler_stmt.go | 5 +++ modules_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++- vm.go | 23 ++++++++++++- 4 files changed, 135 insertions(+), 11 deletions(-) diff --git a/compiler.go b/compiler.go index c871db95..df0ea263 100644 --- a/compiler.go +++ b/compiler.go @@ -30,8 +30,9 @@ const ( maskVar = 1 << 30 maskDeletable = 1 << 29 maskStrict = maskDeletable + maskIndirect = 1 << 28 - maskTyp = maskConst | maskVar | maskDeletable + maskTyp = maskConst | maskVar | maskDeletable | maskIndirect ) type varType byte @@ -687,6 +688,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { switch i := (*ap).(type) { case loadStack: *ap = loadStash(idx) + case initIndirect: + *ap = initIndirect{idx: idx, getter: i.getter} case export: *ap = export{ idx: idx, @@ -894,6 +897,9 @@ func (s *scope) makeNamesMap() map[unistring.String]uint32 { if b.isVar { idx |= maskVar } + if b.getIndirect != nil { + idx |= maskIndirect + } names[b.name] = idx } return names @@ -954,13 +960,12 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { // needResult: true, } - enter := &enterFuncBody{ + var enter *enterBlock + c.emit(&enterFuncBody{ funcType: funcModule, extensible: true, adjustStack: true, - } - - c.emit(enter) + }) for _, in := range module.indirectExportEntries { v, ambiguous := module.ResolveExport(in.exportName) if v == nil || ambiguous { @@ -981,7 +986,15 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { if len(vars) > 0 { c.emit(&bindVars{names: vars, deletable: false}) } - c.compileLexicalDeclarations(in.Body, true) + if c.compileLexicalDeclarations(in.Body, true) { + c.block = &block{ + outer: c.block, + typ: blockScope, + needResult: true, + } + enter = &enterBlock{} + c.emit(enter) + } for _, exp := range in.Body { if imp, ok := exp.(*ast.ImportDeclaration); ok { c.compileImportDeclaration(imp) @@ -1003,9 +1016,10 @@ func (c *compiler) compileModule(module *SourceTextModuleRecord) { c.compileStatements(in.Body, true) c.emit(loadUndef) c.emit(ret) - c.updateEnterBlock(&enter.enterBlock) - c.leaveScopeBlock(&enter.enterBlock) - c.popScope() + if enter != nil { + c.leaveScopeBlock(enter) + c.popScope() + } scope.finaliseVarAlloc(0) m := &newModule{ diff --git a/compiler_stmt.go b/compiler_stmt.go index fe5bec2b..e280f254 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -900,6 +900,9 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { m := vm.r.modules[value.Module] return m.GetBindingValue(identifier) } + localB.markAccessPoint() + c.emit(initIndirect{getter: localB.getIndirect}) + } } @@ -932,6 +935,8 @@ func (c *compiler) compileImportDeclaration(expr *ast.ImportDeclaration) { v := m.GetBindingValue(identifier) return v } + localB.markAccessPoint() + c.emit(initIndirect{getter: localB.getIndirect}) } } } diff --git a/modules_test.go b/modules_test.go index 2191119b..d3d386ce 100644 --- a/modules_test.go +++ b/modules_test.go @@ -21,6 +21,13 @@ func TestSimpleModule(t *testing.T) { `, "dep.js": `export function b() { return 5 };`, }, + "function export eval": { + "a.js": ` + import { b } from "dep.js"; + eval("globalThis.s = b()"); + `, + "dep.js": `export function b() { return 5 };`, + }, "let export": { "a.js": ` import { b } from "dep.js"; @@ -28,6 +35,13 @@ func TestSimpleModule(t *testing.T) { `, "dep.js": `export let b = function() {return 5 };`, }, + "let export eval": { + "a.js": ` + import { b } from "dep.js"; + eval("globalThis.s = b()"); + `, + "dep.js": `export let b = function() {return 5 };`, + }, "const export": { "a.js": ` import { b } from "dep.js"; @@ -35,9 +49,16 @@ func TestSimpleModule(t *testing.T) { `, "dep.js": `export const b = function() { return 5 };`, }, + "const export eval": { + "a.js": ` + import { b } from "dep.js"; + eval("globalThis.s = b()") + `, + "dep.js": `export const b = function() { return 5 };`, + }, "let export with update": { "a.js": ` - import { s , b} from "dep.js"; + import { s, b } from "dep.js"; s() globalThis.s = b() `, @@ -49,6 +70,20 @@ func TestSimpleModule(t *testing.T) { }; }`, }, + "let export with update eval": { + "a.js": ` + import { s, b } from "dep.js"; + s(); + eval("globalThis.s = b();"); + `, + "dep.js": ` + export let b = "something"; + export function s(){ + b = function() { + return 5; + }; + }`, + }, "default export": { "a.js": ` import b from "dep.js"; @@ -56,6 +91,13 @@ func TestSimpleModule(t *testing.T) { `, "dep.js": `export default function() { return 5 };`, }, + "default export eval": { + "a.js": ` + import b from "dep.js"; + eval("globalThis.s = b()"); + `, + "dep.js": `export default function() { return 5 };`, + }, "default loop": { "a.js": ` import b from "a.js"; @@ -63,6 +105,13 @@ func TestSimpleModule(t *testing.T) { globalThis.s = b() `, }, + "default loop eval": { + "a.js": ` + import b from "a.js"; + export default function() {return 5;}; + eval("globalThis.s = b()"); + `, + }, "default export arrow": { "a.js": ` import b from "dep.js"; @@ -70,6 +119,13 @@ func TestSimpleModule(t *testing.T) { `, "dep.js": `export default () => {return 5 };`, }, + "default export arrow eval": { + "a.js": ` + import b from "dep.js"; + eval("globalThis.s = b();") + `, + "dep.js": `export default () => {return 5 };`, + }, "default export with as": { "a.js": ` import b from "dep.js"; @@ -80,6 +136,16 @@ func TestSimpleModule(t *testing.T) { export { f as default }; `, }, + "default export with as eval": { + "a.js": ` + import b from "dep.js"; + eval("globalThis.s = b()") + `, + "dep.js": ` + function f() {return 5;}; + export { f as default }; + `, + }, "export usage before evaluation as": { "a.js": ` import "dep.js"; @@ -90,6 +156,16 @@ func TestSimpleModule(t *testing.T) { globalThis.s = a(); `, }, + "export usage before evaluation as eval": { + "a.js": ` + import "dep.js"; + export function a() { return 5; } + `, + "dep.js": ` + import { a } from "a.js"; + eval("globalThis.s = a()"); + `, + }, "dynamic import": { "a.js": ` import("dep.js").then((imported) => { @@ -98,6 +174,14 @@ func TestSimpleModule(t *testing.T) { `, "dep.js": `export default function() { return 5; }`, }, + "dynamic import eval": { + "a.js": ` + import("dep.js").then((imported) => { + eval("globalThis.s = imported.default()"); + }); + `, + "dep.js": `export default function() { return 5; }`, + }, "dynamic import error": { "a.js": ` do { diff --git a/vm.go b/vm.go index 04e4ff78..e25effda 100644 --- a/vm.go +++ b/vm.go @@ -27,6 +27,7 @@ type stash struct { obj *Object outer *stash + vm *vm // If this is a top-level function stash, sets the type of the function. If set, dynamic var declarations // created by direct eval go here. @@ -456,6 +457,10 @@ func (s *stash) getByName(name unistring.String) (v Value, exists bool) { } else { v = _undefined } + } else if idx&maskIndirect != 0 { + var f func(*vm) Value + _ = s.vm.r.ExportTo(v, &f) + v = f(s.vm) } return v, true } @@ -542,6 +547,7 @@ func (s *stash) deleteBinding(name unistring.String) { func (vm *vm) newStash() { vm.stash = &stash{ outer: vm.stash, + vm: vm, // TODO fix } vm.stashAllocs++ } @@ -2676,6 +2682,22 @@ func (s initStash) exec(vm *vm) { vm.initLocal(int(s)) } +type initIndirect struct { + idx uint32 + getter func(vm *vm) Value +} + +func (s initIndirect) exec(vm *vm) { + level := int(s.idx) >> 24 + idx := uint32(s.idx & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + stash.initByIdx(idx, vm.r.ToValue(s.getter)) + vm.pc++ +} + type initStashP uint32 func (s initStashP) exec(vm *vm) { @@ -3259,7 +3281,6 @@ func (n loadDynamic) exec(vm *vm) { if val == nil { val = vm.r.globalObject.self.getStr(name, nil) if val == nil { - // fmt.Println("here") vm.throw(vm.r.newReferenceError(name)) return } From 2ecad8a7570a89a2a7e3d8094818ac628e7c3969 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 13 Nov 2023 16:29:26 +0200 Subject: [PATCH 120/124] Fix errors and exceptions around modules and imports --- modules.go | 76 +++++++++++++++++++------------------------ modules_sourcetext.go | 3 +- tc39_test.go | 5 ++- vm.go | 38 ++++++++++++---------- 4 files changed, 60 insertions(+), 62 deletions(-) diff --git a/modules.go b/modules.go index 98845948..ed8baa87 100644 --- a/modules.go +++ b/modules.go @@ -150,8 +150,7 @@ func newEvaluationState() *evaluationState { } // TODO have resolve as part of runtime -func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostResolveImportedModuleFunc, -) *Promise { +func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostResolveImportedModuleFunc) *Promise { if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) } @@ -174,7 +173,8 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostR state.status[m] = Evaluated state.evaluationError[m] = err } - capability.reject(r.interfaceErrorToValue(err)) + + capability.reject(r.ToValue(err)) } else { if state.asyncEvaluation[r.modules[c].(CyclicModuleInstance)] == 0 { state.topLevelCapability[c].resolve(_undefined) @@ -190,7 +190,7 @@ func (r *Runtime) innerModuleEvaluation( state *evaluationState, m ModuleRecord, stack *[]CyclicModuleInstance, index uint, resolve HostResolveImportedModuleFunc, -) (idx uint, ex error) { +) (idx uint, err error) { if len(*stack) > 100000 { panic("too deep dependancy stack of 100000") } @@ -208,11 +208,11 @@ func (r *Runtime) innerModuleEvaluation( if _, ok = r.modules[m]; ok { return index, nil } - c, ex = cr.Instantiate(r) - if ex != nil { + c, err = cr.Instantiate(r) + if err != nil { // state.evaluationError[cr] = err // TODO handle this somehow - maybe just panic - return index, ex + return index, err } r.modules[m] = c @@ -231,14 +231,14 @@ func (r *Runtime) innerModuleEvaluation( *stack = append(*stack, c) var requiredModule ModuleRecord for _, required := range cr.RequestedModules() { - requiredModule, ex = resolve(m, required) - if ex != nil { - state.evaluationError[c] = ex - return index, ex + requiredModule, err = resolve(m, required) + if err != nil { + state.evaluationError[c] = err + return index, err } - index, ex = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) - if ex != nil { - return index, ex + index, err = r.innerModuleEvaluation(state, requiredModule, stack, index, resolve) + if err != nil { + return index, err } requiredInstance := r.GetModuleInstance(requiredModule) if requiredC, ok := requiredInstance.(CyclicModuleInstance); ok { @@ -263,10 +263,10 @@ func (r *Runtime) innerModuleEvaluation( r.executeAsyncModule(state, c) } } else { - c, ex = c.ExecuteModule(r, nil, nil) - if ex != nil { - // state.evaluationError[c] = err - return index, ex + c, err = c.ExecuteModule(r, nil, nil) + if err != nil { + state.evaluationError[c] = err + return index, err } } @@ -290,7 +290,6 @@ func (r *Runtime) innerModuleEvaluation( func (r *Runtime) executeAsyncModule(state *evaluationState, c CyclicModuleInstance) { // implement https://262.ecma-international.org/13.0/#sec-execute-async-module - // TODO likely wrong p, res, rej := r.NewPromise() r.performPromiseThen(p, r.ToValue(func(call FunctionCall) Value { r.asyncModuleExecutionFulfilled(state, c) @@ -323,7 +322,6 @@ func (r *Runtime) asyncModuleExecutionFulfilled(state *evaluationState, c Cyclic sort.Slice(execList, func(i, j int) bool { return state.asyncEvaluation[execList[i]] < state.asyncEvaluation[execList[j]] }) - // TODO sort? per when the modules got their AsyncEvaluation set ... somehow for _, m := range execList { if state.status[m] == Evaluated { continue @@ -339,7 +337,7 @@ func (r *Runtime) asyncModuleExecutionFulfilled(state *evaluationState, c Cyclic state.status[m] = Evaluated if cap := state.topLevelCapability[r.findModuleRecord(result).(CyclicModuleRecord)]; cap != nil { // TODO having the module instances going through Values and back is likely not a *great* idea - cap.resolve(r.ToValue(_undefined)) + cap.resolve(_undefined) } } } @@ -379,7 +377,7 @@ func (r *Runtime) asyncModuleExecutionRejected(state *evaluationState, c CyclicM } // TODO handle top level capabiltiy better if cap := state.topLevelCapability[r.findModuleRecord(c).(CyclicModuleRecord)]; cap != nil { - cap.reject(r.interfaceErrorToValue(ex)) + cap.reject(r.ToValue(ex)) } } @@ -472,7 +470,7 @@ func (r *Runtime) FinishLoadingImportModule(referrer interface{}, specifier Valu func (r *Runtime) continueDynamicImport(promiseCapability *promiseCapability, result ModuleRecord, err interface{}) { // https://262.ecma-international.org/14.0/#sec-ContinueDynamicImport if err != nil { - promiseCapability.reject(r.interfaceErrorToValue(err)) + promiseCapability.reject(r.ToValue(err)) return } // 2. 2. Let module be moduleCompletion.[[Value]]. @@ -488,7 +486,19 @@ func (r *Runtime) continueDynamicImport(promiseCapability *promiseCapability, re // a. a. Let link be Completion(module.Link()). err := module.Link() if err != nil { - promiseCapability.reject(r.interfaceErrorToValue(err)) + if err != nil { + switch x1 := err.(type) { + case *CompilerSyntaxError: + err = &Exception{ + val: r.builtin_new(r.getSyntaxError(), []Value{newStringValue(x1.Error())}), + } + case *CompilerReferenceError: + err = &Exception{ + val: r.newError(r.getReferenceError(), x1.Message), + } // TODO proper message + } + } + promiseCapability.reject(r.ToValue(err)) return nil } evaluationPromise := module.Evaluate(r) @@ -503,21 +513,3 @@ func (r *Runtime) continueDynamicImport(promiseCapability *promiseCapability, re r.performPromiseThen(loadPromise.Export().(*Promise), linkAndEvaluateClosure, rejectionClosure, nil) } - -// Drop this or make it more -func (r *Runtime) interfaceErrorToValue(err interface{}) Value { - switch x1 := err.(type) { - case *Exception: - return x1.val - case *CompilerSyntaxError: - return r.builtin_new(r.getSyntaxError(), []Value{newStringValue(x1.Error())}) - case *CompilerReferenceError: - return r.newError(r.getReferenceError(), x1.Message) - case *Object: - if o, ok := x1.self.(*objectGoReflect); ok { - // TODO just not have this - return o.origValue.Interface().(*Exception).Value() - } - } - return r.ToValue(err) -} diff --git a/modules_sourcetext.go b/modules_sourcetext.go index 43cfbe04..f87e4568 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -66,8 +66,7 @@ func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(inte return nil }), rt.ToValue(func(call FunctionCall) Value { v := call.Argument(0) - // fmt.Printf("rej %#v\n", v) - rej(v) + rej(rt.vm.exceptionFromValue(v)) return nil }), nil) return nil, nil diff --git a/tc39_test.go b/tc39_test.go index c05fa27c..5f64e07a 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -772,7 +772,10 @@ func (ctx *tc39TestCtx) runTC39Module(name, src string, includes []string, vm *R asyncError = make(chan error, 1) if promise.state == PromiseStateRejected { - err = vm.vm.exceptionFromValue(promise.Result()) + ex := vm.ExportTo(promise.Result(), &err) + if ex != nil { + panic(ex) + } } else if promise.state == PromiseStatePending { vm.performPromiseThen(promise, vm.ToValue(func(_ FunctionCall) Value { close(asyncError) diff --git a/vm.go b/vm.go index e25effda..a5372821 100644 --- a/vm.go +++ b/vm.go @@ -3,6 +3,7 @@ package goja import ( "fmt" "math" + "reflect" "strconv" "strings" "sync" @@ -840,8 +841,6 @@ func (vm *vm) runTry() (ex *Exception) { func (vm *vm) runTryInner() (ex *Exception) { defer func() { if x := recover(); x != nil { - // fmt.Printf("x=%#v\n", x) - // debug.PrintStack() ex = vm.handleThrow(x) } }() @@ -3704,11 +3703,8 @@ func (e *enterFuncBody) exec(vm *vm) { if e.adjustStack { sp -= vm.args } - // // fmt.Println("sp after", sp) nsp := sp + int(e.stackSize) - // // fmt.Println("nsp after", nsp) if e.stackSize > 0 { - // // fmt.Println("expand") vm.stack.expand(nsp - 1) vv := vm.stack[sp:nsp] for i := range vv { @@ -4714,12 +4710,27 @@ func (_loadDynamicImport) exec(vm *vm) { } else { pcap.reject(vm.r.ToValue(err)) } + return pcap.promise + } + if vm.r.importModuleDynamically == nil { + pcap.reject(asciiString("dynamic modules not enabled in the host program")) } else { - if vm.r.importModuleDynamically == nil { - pcap.reject(asciiString("dynamic modules not enabled in the host program")) - } else { - vm.r.importModuleDynamically(t, specifierStr, pcap) - } + pcapInput := vm.r.newPromiseCapability(vm.r.getPromise()) + onFullfill := vm.r.ToValue(func(call FunctionCall) Value { + pcap.resolve(call.Argument(0)) + return nil + }) + rejectionClosure := vm.r.ToValue(func(call FunctionCall) Value { + v := call.Argument(0) + if v.ExportType() == reflect.TypeOf(&Exception{}) { + v = v.Export().(*Exception).Value() + } + + pcap.reject(v) + return nil + }) + vm.r.performPromiseThen(pcapInput.promise.Export().(*Promise), onFullfill, rejectionClosure, nil) + vm.r.importModuleDynamically(t, specifierStr, pcapInput) } return pcap.promise })) @@ -5665,13 +5676,6 @@ func (vm *vm) exceptionFromValue(x interface{}) *Exception { val: vm.r.newError(vm.r.getSyntaxError(), string(x1)), } default: - /* - if vm.prg != nil { - vm.prg.dumpCode(log.Printf) - } - log.Print("Stack: ", string(debug.Stack())) - panic(fmt.Errorf("Panic at %d: %v", vm.pc, x)) - //*/ return nil } if ex.stack == nil { From 5bcf41454cc46158a012b0924dbfa982e335336f Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 14 Nov 2023 16:15:46 +0200 Subject: [PATCH 121/124] small fix --- compiler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler.go b/compiler.go index df0ea263..2d7153c2 100644 --- a/compiler.go +++ b/compiler.go @@ -1104,9 +1104,8 @@ func (c *compiler) compileIndirectExportEntry(entry exportEntry) { module := c.module c.emit(exportIndirect{callback: func(vm *vm) { m := vm.r.modules[module] - m2 := vm.r.modules[b.Module] m.(*SourceTextModuleInstance).exportGetters[exportName] = func() Value { - return m2.GetBindingValue(importName) + return vm.r.modules[b.Module].GetBindingValue(importName) } }}) } From 7aaf816c37209093e8f8ecfe821dfe5f39cec6b9 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 15 Nov 2023 19:26:54 +0200 Subject: [PATCH 122/124] Make certains SourceModules InitalizeEnvironement --- modules_sourcetext.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/modules_sourcetext.go b/modules_sourcetext.go index f87e4568..1b18e5c3 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -3,6 +3,7 @@ package goja import ( "fmt" "sort" + "sync" "github.com/dop251/goja/ast" "github.com/dop251/goja/parser" @@ -97,6 +98,8 @@ type SourceTextModuleRecord struct { starExportEntries []exportEntry hostResolveImportedModule HostResolveImportedModuleFunc + + once *sync.Once } type importEntry struct { @@ -397,6 +400,7 @@ func ModuleFromAST(body *ast.Program, resolveModule HostResolveImportedModuleFun starExportEntries: starExportEntries, hostResolveImportedModule: resolveModule, + once: &sync.Once{}, } names := s.getExportedNamesWithotStars() // we use this as the other one loops but wee need to early errors here @@ -460,20 +464,22 @@ func (module *SourceTextModuleRecord) GetExportedNames(exportStarSet ...ModuleRe } func (module *SourceTextModuleRecord) InitializeEnvironment() (err error) { - c := newCompiler() - defer func() { - if x := recover(); x != nil { - switch x1 := x.(type) { - case *CompilerSyntaxError: - err = x1 - default: - panic(x) + module.once.Do(func() { + c := newCompiler() + defer func() { + if x := recover(); x != nil { + switch x1 := x.(type) { + case *CompilerSyntaxError: + err = x1 + default: + panic(x) + } } - } - }() + }() - c.compileModule(module) - module.p = c.p + c.compileModule(module) + module.p = c.p + }) return } From 178b2c369c0ba4e641c4ab26bc5662724b132e3a Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Nov 2023 12:17:53 +0200 Subject: [PATCH 123/124] Reduce diff size ast/node.go: reduce diff func.go: revert to master parser/lexer.go: revert to master vm.go: revert some parts runtime.go: revert print stuff tc39_test.go: comment on dumpcode builtin_promise.go: formatting changes --- ast/node.go | 90 +++++++++++++++++++++++----------------------- builtin_promise.go | 9 ++--- func.go | 3 -- parser/lexer.go | 3 ++ runtime.go | 15 ++------ vm.go | 17 ++++----- 6 files changed, 61 insertions(+), 76 deletions(-) diff --git a/ast/node.go b/ast/node.go index f6db4daf..29d4a10e 100644 --- a/ast/node.go +++ b/ast/node.go @@ -307,38 +307,39 @@ type ( // _expressionNode -func (*ArrayLiteral) _expressionNode() {} -func (*AssignExpression) _expressionNode() {} -func (*YieldExpression) _expressionNode() {} -func (*AwaitExpression) _expressionNode() {} -func (*BadExpression) _expressionNode() {} -func (*BinaryExpression) _expressionNode() {} -func (*BooleanLiteral) _expressionNode() {} -func (*BracketExpression) _expressionNode() {} -func (*CallExpression) _expressionNode() {} -func (*ConditionalExpression) _expressionNode() {} -func (*DotExpression) _expressionNode() {} -func (*PrivateDotExpression) _expressionNode() {} -func (*FunctionLiteral) _expressionNode() {} -func (*ClassLiteral) _expressionNode() {} -func (*ArrowFunctionLiteral) _expressionNode() {} -func (*Identifier) _expressionNode() {} -func (*NewExpression) _expressionNode() {} -func (*NullLiteral) _expressionNode() {} -func (*NumberLiteral) _expressionNode() {} -func (*ObjectLiteral) _expressionNode() {} -func (*RegExpLiteral) _expressionNode() {} -func (*SequenceExpression) _expressionNode() {} -func (*StringLiteral) _expressionNode() {} -func (*TemplateLiteral) _expressionNode() {} -func (*ThisExpression) _expressionNode() {} -func (*SuperExpression) _expressionNode() {} +func (*ArrayLiteral) _expressionNode() {} +func (*AssignExpression) _expressionNode() {} +func (*YieldExpression) _expressionNode() {} +func (*AwaitExpression) _expressionNode() {} +func (*BadExpression) _expressionNode() {} +func (*BinaryExpression) _expressionNode() {} +func (*BooleanLiteral) _expressionNode() {} +func (*BracketExpression) _expressionNode() {} +func (*CallExpression) _expressionNode() {} +func (*ConditionalExpression) _expressionNode() {} +func (*DotExpression) _expressionNode() {} +func (*PrivateDotExpression) _expressionNode() {} +func (*FunctionLiteral) _expressionNode() {} +func (*ClassLiteral) _expressionNode() {} +func (*ArrowFunctionLiteral) _expressionNode() {} +func (*Identifier) _expressionNode() {} +func (*NewExpression) _expressionNode() {} +func (*NullLiteral) _expressionNode() {} +func (*NumberLiteral) _expressionNode() {} +func (*ObjectLiteral) _expressionNode() {} +func (*RegExpLiteral) _expressionNode() {} +func (*SequenceExpression) _expressionNode() {} +func (*StringLiteral) _expressionNode() {} +func (*TemplateLiteral) _expressionNode() {} +func (*ThisExpression) _expressionNode() {} +func (*SuperExpression) _expressionNode() {} +func (*UnaryExpression) _expressionNode() {} +func (*MetaProperty) _expressionNode() {} +func (*ObjectPattern) _expressionNode() {} +func (*ArrayPattern) _expressionNode() {} +func (*Binding) _expressionNode() {} + func (*DynamicImportExpression) _expressionNode() {} -func (*UnaryExpression) _expressionNode() {} -func (*MetaProperty) _expressionNode() {} -func (*ObjectPattern) _expressionNode() {} -func (*ArrayPattern) _expressionNode() {} -func (*Binding) _expressionNode() {} func (*PropertyShort) _expressionNode() {} func (*PropertyKeyed) _expressionNode() {} @@ -829,19 +830,18 @@ func (self *NewExpression) Idx1() file.Idx { return self.Callee.Idx1() } } -func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null" -func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace + 1 } -func (self *ObjectPattern) Idx1() file.Idx { return self.RightBrace + 1 } -func (self *ParameterList) Idx1() file.Idx { return self.Closing + 1 } -func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() } -func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *TemplateElement) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } -func (self *TemplateLiteral) Idx1() file.Idx { return self.CloseQuote + 1 } -func (self *ThisExpression) Idx1() file.Idx { return self.Idx + 4 } -func (self *SuperExpression) Idx1() file.Idx { return self.Idx + 5 } -func (self *DynamicImportExpression) Idx1() file.Idx { return self.Idx + 6 } +func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null" +func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ObjectPattern) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ParameterList) Idx1() file.Idx { return self.Closing + 1 } +func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() } +func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *TemplateElement) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *TemplateLiteral) Idx1() file.Idx { return self.CloseQuote + 1 } +func (self *ThisExpression) Idx1() file.Idx { return self.Idx + 4 } +func (self *SuperExpression) Idx1() file.Idx { return self.Idx + 5 } func (self *UnaryExpression) Idx1() file.Idx { if self.Postfix { return self.Operand.Idx1() + 2 // ++ -- @@ -853,6 +853,8 @@ func (self *MetaProperty) Idx1() file.Idx { return self.Property.Idx1() } +func (self *DynamicImportExpression) Idx1() file.Idx { return self.Idx + 6 } + func (self *BadStatement) Idx1() file.Idx { return self.To } func (self *BlockStatement) Idx1() file.Idx { return self.RightBrace + 1 } func (self *BranchStatement) Idx1() file.Idx { diff --git a/builtin_promise.go b/builtin_promise.go index 7eefd280..d51f27d1 100644 --- a/builtin_promise.go +++ b/builtin_promise.go @@ -1,15 +1,12 @@ package goja import ( - "reflect" - "github.com/dop251/goja/unistring" + "reflect" ) -type ( - PromiseState int - PromiseRejectionOperation int -) +type PromiseState int +type PromiseRejectionOperation int type promiseReactionType int diff --git a/func.go b/func.go index 32e207e3..c0469569 100644 --- a/func.go +++ b/func.go @@ -700,7 +700,6 @@ func (ar *asyncRunner) onRejected(call FunctionCall) Value { } func (ar *asyncRunner) step(res Value, done bool, ex *Exception) { - // fmt.Printf("(%v) -> step(%v, %v, %v)\n", ar.gen.ctx.prg.src.Name(), res, done, ex) r := ar.f.runtime if done || ex != nil { if ex == nil { @@ -722,7 +721,6 @@ func (ar *asyncRunner) step(res Value, done bool, ex *Exception) { handler: &jobCallback{callback: ar.onRejected}, asyncRunner: ar, }) - // fmt.Printf("promise = %#v\n", promise.self) } func (ar *asyncRunner) start(nArgs int) { @@ -783,7 +781,6 @@ func (g *generator) step() (res Value, resultType resultType, ex *Exception) { g.vm.sp = g.vm.sb - 1 g.vm.callStack = g.vm.callStack[:len(g.vm.callStack)-1] // remove the frame with pc == -2, as ret would do } - // fmt.Println(res) return } diff --git a/parser/lexer.go b/parser/lexer.go index a866cf4c..68d56d20 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -222,6 +222,7 @@ func (self *_parser) peek() token.Token { } func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unistring.String, idx file.Idx) { + self.implicitSemicolon = false for { @@ -638,6 +639,7 @@ func (self *_parser) scanMantissa(base int) { } func (self *_parser) scanEscape(quote rune) (int, bool) { + var length, base uint32 chr := self.chr switch chr { @@ -1114,6 +1116,7 @@ func parseStringLiteral(literal string, length int, unicode, strict bool) (unist } func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) { + offset := self.chrOffset tkn := token.NUMBER diff --git a/runtime.go b/runtime.go index ea19d10f..331f8347 100644 --- a/runtime.go +++ b/runtime.go @@ -753,6 +753,7 @@ func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, name unistring.St } func (r *Runtime) newWrappedFunc(value reflect.Value) *Object { + v := &Object{runtime: r} f := &wrappedFuncObject{ @@ -1374,6 +1375,7 @@ func (r *Runtime) RunString(str string) (Value, error) { // RunScript executes the given string in the global context. func (r *Runtime) RunScript(name, src string) (Value, error) { p, err := r.compile(name, src, false, true, nil) + if err != nil { return nil, err } @@ -1941,8 +1943,6 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value { func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value { return func(call FunctionCall) Value { - // fmt.Println("call=", call) - // fmt.Println("call.Arguments=", call.Arguments) typ := value.Type() nargs := typ.NumIn() var in []reflect.Value @@ -2459,17 +2459,6 @@ func (r *Runtime) runWrapped(f func()) (err error) { if len(r.vm.callStack) == 0 { r.leave() } else { - /* - fmt.Printf("code: ") - if r.vm.prg != nil { - for _, code := range r.vm.prg.code { - fmt.Printf("{%T: %#v},", code, code) - } - } else { - fmt.Print("no code") - } - fmt.Print("\n") - */ r.vm.clearStack() } return diff --git a/vm.go b/vm.go index a5372821..00ef5a09 100644 --- a/vm.go +++ b/vm.go @@ -586,14 +586,6 @@ func (vm *vm) run() { if pc < 0 || pc >= len(vm.prg.code) { break } - /* - fmt.Printf("code: ") - for _, code := range vm.prg.code[vm.pc:] { - fmt.Printf("{%T: %#v},", code, code) - } - fmt.Println() - fmt.Printf("running: %T: %#v\n", vm.prg.code[pc], vm.prg.code[pc]) - //*/ vm.prg.code[pc].exec(vm) } @@ -3699,7 +3691,6 @@ func (e *enterFuncBody) exec(vm *vm) { } } sp := vm.sp - // // fmt.Println("sp", sp) if e.adjustStack { sp -= vm.args } @@ -5676,6 +5667,13 @@ func (vm *vm) exceptionFromValue(x interface{}) *Exception { val: vm.r.newError(vm.r.getSyntaxError(), string(x1)), } default: + /* + if vm.prg != nil { + vm.prg.dumpCode(log.Printf) + } + log.Print("Stack: ", string(debug.Stack())) + panic(fmt.Errorf("Panic at %d: %v", vm.pc, x)) + */ return nil } if ex.stack == nil { @@ -5782,7 +5780,6 @@ func (r *getPrivateRefId) exec(vm *vm) { } func (y *yieldMarker) exec(vm *vm) { - // // fmt.Println("values", vm.stash.values) vm.pc = -vm.pc // this will terminate the run loop vm.push(y) // marker so the caller knows it's a yield, not a return } From 08f562ee86d0595a6a487fe291ab5cd7ed421b12 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 12 Dec 2023 16:46:16 +0200 Subject: [PATCH 124/124] small fixes --- compiler.go | 3 +-- modules.go | 4 +--- modules_namespace.go | 2 +- modules_sourcetext.go | 16 ++-------------- tc39_test.go | 2 +- 5 files changed, 6 insertions(+), 21 deletions(-) diff --git a/compiler.go b/compiler.go index 2d7153c2..71a97839 100644 --- a/compiler.go +++ b/compiler.go @@ -4,10 +4,9 @@ import ( "fmt" "sort" - "github.com/dop251/goja/token" - "github.com/dop251/goja/ast" "github.com/dop251/goja/file" + "github.com/dop251/goja/token" "github.com/dop251/goja/unistring" ) diff --git a/modules.go b/modules.go index ed8baa87..24fd519c 100644 --- a/modules.go +++ b/modules.go @@ -154,7 +154,6 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostR if r.modules == nil { r.modules = make(map[ModuleRecord]ModuleInstance) } - // TODO implement all the promise stuff stackInstance := []CyclicModuleInstance{} if r.evaluationState == nil { @@ -166,7 +165,6 @@ func (r *Runtime) CyclicModuleRecordEvaluate(c CyclicModuleRecord, resolve HostR capability := r.newPromiseCapability(r.getPromise()) r.evaluationState.topLevelCapability[c] = capability state := r.evaluationState - // TODO fix abrupt result _, err := r.innerModuleEvaluation(state, c, &stackInstance, 0, resolve) if err != nil { for _, m := range stackInstance { @@ -412,7 +410,7 @@ func (r *Runtime) getImportMetaFor(m ModuleRecord) *Object { return o } o := r.NewObject() - o.SetPrototype(nil) + _ = o.SetPrototype(nil) var properties []MetaProperty if r.getImportMetaProperties != nil { diff --git a/modules_namespace.go b/modules_namespace.go index 11c85dbb..a6dd170a 100644 --- a/modules_namespace.go +++ b/modules_namespace.go @@ -56,7 +56,7 @@ func (no *namespaceObject) stringKeys(all bool, accum []Value) []Value { } accum = append(accum, stringValueFromRaw(name)) } - // TODO optimize thsi + // TODO optimize this sort.Slice(accum, func(i, j int) bool { return accum[i].String() < accum[j].String() }) diff --git a/modules_sourcetext.go b/modules_sourcetext.go index 1b18e5c3..f78cdb4b 100644 --- a/modules_sourcetext.go +++ b/modules_sourcetext.go @@ -23,22 +23,10 @@ type SourceTextModuleInstance struct { } func (s *SourceTextModuleInstance) ExecuteModule(rt *Runtime, res, rej func(interface{})) (CyclicModuleInstance, error) { - // fmt.Println("ExecuteModule", s.moduleRecord.p.src.Name(), s.HasTLA()) - //s.pcap.resolve(_undefined) - //* - // THis actually should just continue the execution instead of moving it off. - // Unfortunately this requires access to the asyncRunner promiseP := s.pcap.promise.self.(*Promise) - // fmt.Println(promiseP.fulfillReactions) - // fmt.Println(promiseP) if len(promiseP.fulfillReactions) == 1 { - // FIXME figure out how to do this ... better - promiseP.fulfill(_undefined) - rt.leave() - // ar := s.asyncRunner - // ar := promiseP.fulfillReactions[0].asyncRunner - // fmt.Println(ar) - // _ = ar.onFulfilled(FunctionCall{Arguments: []Value{_undefined}}) + ar := promiseP.fulfillReactions[0].asyncRunner + _ = ar.onFulfilled(FunctionCall{Arguments: []Value{_undefined}}) } promise := s.asyncPromise diff --git a/tc39_test.go b/tc39_test.go index 5f64e07a..eec43148 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -630,7 +630,7 @@ func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing. } } else { if meta.Negative.Type != "" { - // vm.vm.prg.dumpCode(t.Logf) + vm.vm.prg.dumpCode(t.Logf) t.Fatalf("%s: Expected error: %v", name, err) } }