Skip to content

Commit

Permalink
Allow multiple imports at once, fix enums
Browse files Browse the repository at this point in the history
  • Loading branch information
MikMuellerDev committed Apr 19, 2023
1 parent e78ed7c commit c2e6c08
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 155 deletions.
4 changes: 3 additions & 1 deletion grammar.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ LetStmt = 'let' , identifier , '=' , Expression , ';' ;
in the current scope, `as` can be used to import the target function under a different specified
name.
*)
ImportStmt = 'import' , identifier , [ 'as' , identifier ] , 'from' , identifier , ';' ;
ImportStmt = 'import' , ( identifier
| '{' , identifier , { ',' , identifier } , [ ',' ] , '}' ) , 'from' , identifier
, ';' ;
(*
A <BreakStmt> is used to stop a loop entirely. It can optionally be followed by an expression that
Expand Down
119 changes: 62 additions & 57 deletions homescript/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,8 @@ func (self *Analyzer) visitImportStatement(node ImportStmt) (Result, *errors.Err
}
}

actualImport := node.Function
if node.RewriteAs != nil {
actualImport = *node.RewriteAs
}

// Check if the function can be imported
moduleCode, filename, exists, proceed, err := self.executor.ResolveModule(node.FromModule)
moduleCode, filename, exists, shouldProceed, err := self.executor.ResolveModule(node.FromModule)
if err != nil {
self.issue(
node.Range,
Expand All @@ -511,64 +506,74 @@ func (self *Analyzer) visitImportStatement(node ImportStmt) (Result, *errors.Err
return Result{}, nil
}

// Check if the function conflicts with existing values
value := self.getVar(actualImport)
function := makeFn(&actualImport, node.Range)
if !shouldProceed {
for _, imported := range node.Functions {
function := makeFn(&imported, node.Range)

// Only report this non-critical error
if value != nil {
self.issue(node.Range,
fmt.Sprintf("the name '%s' is already present in the current scope", actualImport),
errors.ImportError,
)
return Result{}, nil
} else {
// Push a dummy function into the current scope
self.addVar(actualImport, function, node.Range)
// Add the funtion to the list of imported functions to avoid analysis
self.getScope().importedFunctions = append(self.getScope().importedFunctions, actualImport)
}

if !proceed {
return Result{Value: &function}, nil
}
diagnostics, _, rootScope := Analyze(
self.executor,
moduleCode,
make(map[string]Value),
self.moduleStack,
node.FromModule,
filename,
)
moduleErrors := 0
firstErrMessage := ""
for _, diagnostic := range diagnostics {
if diagnostic.Severity == Error {
moduleErrors++
if firstErrMessage == "" {
firstErrMessage = diagnostic.Message
}
// Push a dummy function into the current scope
self.addVar(imported, function, node.Range)
// Add the funtion to the list of imported functions to avoid analysis
self.getScope().importedFunctions = append(self.getScope().importedFunctions, imported)
}
}
if moduleErrors > 0 {
self.issue(
node.Range,
fmt.Sprintf("target module contains %d error(s): %s", moduleErrors, firstErrMessage),
errors.ImportError,
)
return Result{}, nil
}
functionValue, found := rootScope[node.Function]
if !found {
self.issue(
node.Range,
fmt.Sprintf("no function named '%s' found in module '%s'", node.Function, node.FromModule),
errors.ImportError,

for _, imported := range node.Functions {
value := self.getVar(imported)
function := makeFn(&imported, node.Range)

// Only report this non-critical error
if value != nil {
self.issue(node.Range,
fmt.Sprintf("the name '%s' is already present in the current scope", imported),
errors.ImportError,
)
return Result{}, nil
} else {
// Push a dummy function into the current scope
self.addVar(imported, function, node.Range)
// Add the funtion to the list of imported functions to avoid analysis
self.getScope().importedFunctions = append(self.getScope().importedFunctions, imported)
}

diagnostics, _, rootScope := Analyze(
self.executor,
moduleCode,
make(map[string]Value),
self.moduleStack,
node.FromModule,
filename,
)
return Result{}, nil
moduleErrors := 0
firstErrMessage := ""
for _, diagnostic := range diagnostics {
if diagnostic.Severity == Error {
moduleErrors++
if firstErrMessage == "" {
firstErrMessage = diagnostic.Message
}
}
}
if moduleErrors > 0 {
self.issue(
node.Range,
fmt.Sprintf("target module contains %d error(s): %s", moduleErrors, firstErrMessage),
errors.ImportError,
)
return Result{}, nil
}
_, found := rootScope[imported]
if !found {
self.issue(
node.Range,
fmt.Sprintf("no function named `%s` found in module `%s`", imported, node.FromModule),
errors.ImportError,
)
return Result{}, nil
}
}

return Result{Value: functionValue}, nil
return Result{}, nil
}

func (self *Analyzer) visitBreakStatement(node BreakStmt) (Result, *errors.Error) {
Expand Down
72 changes: 39 additions & 33 deletions homescript/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,38 +278,37 @@ func (self *Interpreter) visitImportStatement(node ImportStmt) (Result, *int, *e
}
}
// Resolve the function to be imported
function, rootScope, err := self.ResolveModule(
functions, rootScope, err := self.ResolveModule(
node.Span(),
node.FromModule,
node.Function,
node.Functions,
)
if err != nil {
return Result{}, nil, err
}
actualImport := node.Function
if node.RewriteAs != nil {
actualImport = *node.RewriteAs
}

// Check if the function conflicts with existing values
value := self.getVar(actualImport)
if value != nil {
return Result{}, nil, errors.NewError(
node.Span(),
fmt.Sprintf("the name '%s' is already present in the current scope", actualImport),
errors.ImportError,
)
}
for _, function := range functions {
// Check if the function conflicts with existing values
value := self.getVar(*(*function).(ValueFunction).Identifier)
if value != nil {
return Result{}, nil, errors.NewError(
node.Span(),
fmt.Sprintf("the name `%s` is already present in the current scope", *(*function).(ValueFunction).Identifier),
errors.ImportError,
)
}

// Push the function into the current scope
self.addVar(actualImport, function)
// Push the function into the current scope
self.addVar(*(*function).(ValueFunction).Identifier, function)

// Migrate scope from import
for key, value := range rootScope {
self.addVar(fmt.Sprintf("%s::%s", node.FromModule, key), value)
// Migrate scope from import
for key, value := range rootScope {
self.addVar(fmt.Sprintf("%s::%s", node.FromModule, key), value)
}
}

return Result{Value: function}, nil, nil
null := makeNull(node.Span())
return Result{Value: &null}, nil, nil
}

func (self *Interpreter) visitBreakStatement(node BreakStmt) (Result, *int, *errors.Error) {
Expand Down Expand Up @@ -1579,11 +1578,11 @@ func (self *Interpreter) getVar(key string) *Value {
return nil
}

// Resolves a function imported by an 'import' statement
// Resolves functions imported by an 'import' statement
// The builtin just has the task of providing the target module code
// This function then runs the target module code and returns the value of the target function (analyzes the root scope)
// This function then runs the target module code and returns the values of the target functionsj (analyzes the root scope)
// If the target module contains top level code, it is also executed
func (self Interpreter) ResolveModule(span errors.Span, module string, function string) (*Value, map[string]*Value, *errors.Error) {
func (self Interpreter) ResolveModule(span errors.Span, module string, functions []string) ([]*Value, map[string]*Value, *errors.Error) {
moduleCode, filename, found, _, err := self.executor.ResolveModule(module)
if err != nil {
return nil, nil, errors.NewError(
Expand All @@ -1609,7 +1608,7 @@ func (self Interpreter) ResolveModule(span errors.Span, module string, function
false,
10,
self.moduleStack,
function,
"import",
filename,
)
if len(runErr) > 0 {
Expand All @@ -1627,13 +1626,20 @@ func (self Interpreter) ResolveModule(span errors.Span, module string, function
errors.ImportError,
)
}
functionValue, found := rootScope[function]
if !found {
return nil, nil, errors.NewError(
span,
fmt.Sprintf("no function named '%s' found in module '%s'", function, module),
errors.ImportError,
)

resultFunctions := make([]*Value, 0)
for _, function := range functions {

functionValue, found := rootScope[function]
if !found {
return nil, nil, errors.NewError(
span,
fmt.Sprintf("no function named '%s' found in module '%s'", function, module),
errors.ImportError,
)
}
resultFunctions = append(resultFunctions, functionValue)
}
return functionValue, rootScope, nil

return resultFunctions, rootScope, nil
}
5 changes: 2 additions & 3 deletions homescript/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ func (self LetStmt) Kind() StatementKind { return LetStmtKind }
func (self LetStmt) Span() errors.Span { return self.Range }

type ImportStmt struct {
Function string // import `foo`
RewriteAs *string // as `bar`
FromModule string // from `baz`
Functions []string // import `foo`
FromModule string // from `baz`
Range errors.Span
}

Expand Down
Loading

0 comments on commit c2e6c08

Please sign in to comment.