diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index faf9758..6a0b014 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -1,40 +1,30 @@ name: goreleaser on: - push: - branches: - - "main" - - tags: - - "*" - - pull_request: + push: + tags: + - "*" permissions: - contents: write + contents: write jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: "1.19" - - - name: Tests - run: go test -v ./... - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + + - name: goreleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests-lint.yml b/.github/workflows/tests-lint.yml new file mode 100644 index 0000000..9d4165e --- /dev/null +++ b/.github/workflows/tests-lint.yml @@ -0,0 +1,35 @@ +name: tests-lint + +on: + push: + branches: + - "main" + pull_request: + +permissions: + contents: read + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + + - name: tests + run: go test -v ./... + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: "latest" diff --git a/ast/ast.go b/ast/ast.go index 5f6dd61..36d46a1 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -2,8 +2,10 @@ package ast type Node interface { TokenLiteral() string - String() string } type Statement interface{ Node } -type Expression interface{ Node } +type Expression interface { + Node + String() string +} diff --git a/ast/expressions.go b/ast/expressions.go index c460c68..2a09b64 100644 --- a/ast/expressions.go +++ b/ast/expressions.go @@ -56,14 +56,6 @@ type PrefixExpression struct { } func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } -func (pe *PrefixExpression) String() string { - var out bytes.Buffer - out.WriteString("(") - out.WriteString(pe.Operator) - out.WriteString(pe.Right.String()) - out.WriteString(")") - return out.String() -} type InfixExpression struct { Expression @@ -75,40 +67,6 @@ type InfixExpression struct { } func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } -func (oe *InfixExpression) String() string { - var out bytes.Buffer - out.WriteString("(") - out.WriteString(oe.Left.String()) - out.WriteString(" " + oe.Operator + " ") - out.WriteString(oe.Right.String()) - out.WriteString(")") - return out.String() -} - -type IfExpression struct { - Expression - - Token token.Token // The 'if' token - Condition Expression - ThenBranch Statement - ElseBranch Statement -} - -func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } -func (ie *IfExpression) String() string { - var out bytes.Buffer - out.WriteString("if") - out.WriteString(ie.Condition.String()) - out.WriteString(" ") - out.WriteString(ie.ThenBranch.String()) - - if ie.ElseBranch != nil { - out.WriteString("else ") - out.WriteString(ie.ElseBranch.String()) - } - - return out.String() -} type FunctionLiteral struct { Expression @@ -120,20 +78,7 @@ type FunctionLiteral struct { func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } func (fl *FunctionLiteral) String() string { - var out bytes.Buffer - - params := []string{} - for _, p := range fl.Parameters { - params = append(params, p.String()) - } - - out.WriteString(fl.TokenLiteral()) - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") ") - out.WriteString(fl.Body.String()) - - return out.String() + return "fn" } type CallExpression struct { diff --git a/ast/statements.go b/ast/statements.go index 10426cd..57f9010 100644 --- a/ast/statements.go +++ b/ast/statements.go @@ -18,17 +18,6 @@ func (p *Program) TokenLiteral() string { } } -func (p *Program) String() string { - var out bytes.Buffer - - for _, s := range p.Statements { - out.WriteString(s.String()) - out.WriteString("\n") - } - - return out.String() -} - type LetStatement struct { Statement @@ -99,15 +88,6 @@ type BlockStatement struct { } func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } -func (bs *BlockStatement) String() string { - var out bytes.Buffer - - for _, s := range bs.Statements { - out.WriteString(s.String()) - } - - return out.String() -} type ForStatement struct { Statement @@ -165,3 +145,14 @@ type EchoStatement struct { func (es *EchoStatement) TokenLiteral() string { return es.Token.Literal } func (es *EchoStatement) String() string { return "" } + +type IfStatement struct { + Statement + + Token token.Token // The 'if' token + Condition Expression + ThenBranch Statement + ElseBranch Statement +} + +func (ie *IfStatement) TokenLiteral() string { return ie.Token.Literal } diff --git a/cmd/run.go b/cmd/run.go deleted file mode 100644 index 79b687c..0000000 --- a/cmd/run.go +++ /dev/null @@ -1,67 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "os" - - "github.com/joetifa2003/windlang/evaluator" - "github.com/joetifa2003/windlang/lexer" - "github.com/joetifa2003/windlang/parser" - - "github.com/spf13/cobra" -) - -// runCmd represents the run command -var runCmd = &cobra.Command{ - Use: "run [file]", - Short: "Run a Wind script using Tree Walking interpreter", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 1 { - return fmt.Errorf("requires 1 argument") - } - - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - filePath := args[0] - if filePath == "" { - log.Fatal("File path is required") - } - - file, err := os.ReadFile(filePath) - if err != nil { - log.Fatalln("Could not read file:", err) - return - } - - input := string(file) - lexer := lexer.New(input) - parser := parser.New(lexer, filePath) - program := parser.ParseProgram() - parserErrors := parser.ReportErrors() - if len(parserErrors) > 0 { - for _, err := range parserErrors { - fmt.Println(err) - } - - os.Exit(1) - } - - envManager := evaluator.NewEnvironmentManager() - env, _ := envManager.Get(filePath) - ev := evaluator.New(envManager, filePath) - evaluated, evErr := ev.Eval(program, env, nil) - if evErr != nil { - fmt.Println(evErr.Inspect()) - } - - if evaluated == nil { - return - } - }, -} - -func init() { - rootCmd.AddCommand(runCmd) -} diff --git a/cmd/vm.go b/cmd/vm.go index 56e04ce..3fb41b3 100644 --- a/cmd/vm.go +++ b/cmd/vm.go @@ -52,8 +52,18 @@ var vmCommand = &cobra.Command{ compiler := compiler.NewCompiler() instructions := compiler.Compile(program) - virtualM := vm.NewVM(compiler.Constants) - virtualM.Interpret(instructions) + if debug { + fmt.Println(instructions) + } + + mainFrame := compiler.Frames[0] + virtualM := vm.NewVM(compiler.Constants, + vm.Frame{ + Instructions: instructions, + NumOfLocals: len(mainFrame.Locals), + }, + ) + virtualM.Interpret() }, } diff --git a/compiler/compiler.go b/compiler/compiler.go index 36bf107..88caf57 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1,19 +1,21 @@ package compiler import ( + "fmt" + "github.com/joetifa2003/windlang/ast" "github.com/joetifa2003/windlang/opcode" "github.com/joetifa2003/windlang/value" ) type Compiler struct { - Scopes [][]string Constants []value.Value + Frames []Frame } func NewCompiler() Compiler { return Compiler{ - Scopes: [][]string{}, + Frames: []Frame{NewFrame(nil)}, Constants: []value.Value{}, } } @@ -24,52 +26,39 @@ func (c *Compiler) addConstant(v value.Value) int { return len(c.Constants) - 1 } -func (c *Compiler) beginScope() { - c.Scopes = append(c.Scopes, []string{}) +func (c *Compiler) pushFrame() { + c.Frames = append(c.Frames, NewFrame(c.curFrame())) } -func (c *Compiler) endScope() []string { - lastScope := c.Scopes[len(c.Scopes)-1] - c.Scopes = c.Scopes[:len(c.Scopes)-1] +func (c *Compiler) popFrame() { + c.Frames = c.Frames[:len(c.Frames)-1] +} - return lastScope +func (c *Compiler) curFrame() *Frame { + return &c.Frames[len(c.Frames)-1] } -func (c *Compiler) addToScope(name string) int { - scope := &c.Scopes[len(c.Scopes)-1] - *scope = append(*scope, name) +func (c *Compiler) beginBlock() { + c.curFrame().beginBlock() +} - return len(*scope) - 1 +func (c *Compiler) endBlock() { + c.curFrame().endBlock() } -// returns scope index and the value index inside it -func (c *Compiler) findInScope(name string) (int, int) { - for scopesIndex := len(c.Scopes) - 1; scopesIndex >= 0; scopesIndex-- { - for valueIndex, v := range c.Scopes[scopesIndex] { - if v == name { - return scopesIndex, valueIndex - } - } - } +func (c *Compiler) define(name string) int { + return c.curFrame().define(name) +} - panic("") +// resolve returns (frameOffset) +func (c *Compiler) resolve(name string) Var { + return c.curFrame().resolve(name) } -func (c *Compiler) Compile(node ast.Node) []opcode.OpCode { +func (c *Compiler) Compile(node ast.Node) opcode.Instructions { switch node := node.(type) { case *ast.Program: - var instructions []opcode.OpCode - - c.beginScope() - programInstructions := append(instructions, c.CompileProgram(node.Statements)...) - scope := c.endScope() - - instructions = append(instructions, opcode.OP_BLOCK) - instructions = append(instructions, opcode.OpCode(len(scope))) - instructions = append(instructions, programInstructions...) - instructions = append(instructions, opcode.OP_END_BLOCK) - - return instructions + return c.CompileProgram(node.Statements) case *ast.ExpressionStatement: expr := c.Compile(node.Expression) @@ -108,7 +97,7 @@ func (c *Compiler) Compile(node ast.Node) []opcode.OpCode { return instructions - case *ast.IfExpression: + case *ast.IfStatement: var instructions []opcode.OpCode condition := c.Compile(node.Condition) @@ -146,28 +135,14 @@ func (c *Compiler) Compile(node ast.Node) []opcode.OpCode { case *ast.BlockStatement: var instructions []opcode.OpCode - var bodyInstructions []opcode.OpCode - if node.VarCount != 0 { - c.beginScope() - for _, stmt := range node.Statements { - bodyInstructions = append(bodyInstructions, c.Compile(stmt)...) - } - scope := c.endScope() - - instructions = append(instructions, opcode.OP_BLOCK) - instructions = append(instructions, opcode.OpCode(len(scope))) - instructions = append(instructions, bodyInstructions...) - instructions = append(instructions, opcode.OP_END_BLOCK) - - return instructions - } else { - for _, stmt := range node.Statements { - bodyInstructions = append(bodyInstructions, c.Compile(stmt)...) - } - - return bodyInstructions + c.beginBlock() + for _, stmt := range node.Statements { + instructions = append(instructions, c.Compile(stmt)...) } + c.endBlock() + + return instructions case *ast.WhileStatement: var instructions []opcode.OpCode @@ -188,13 +163,13 @@ func (c *Compiler) Compile(node ast.Node) []opcode.OpCode { var bodyInstructions []opcode.OpCode var instructions []opcode.OpCode - c.beginScope() + c.beginBlock() initializer := c.Compile(node.Initializer) condition := c.Compile(node.Condition) body := c.Compile(node.Body) increment := c.Compile(node.Increment) increment = append(increment, opcode.OP_POP) - scope := c.endScope() + c.endBlock() bodyInstructions = append(bodyInstructions, initializer...) bodyInstructions = append(bodyInstructions, condition...) @@ -205,55 +180,44 @@ func (c *Compiler) Compile(node ast.Node) []opcode.OpCode { bodyInstructions = append(bodyInstructions, opcode.OP_JUMP) bodyInstructions = append(bodyInstructions, opcode.OpCode(-len(body)-len(increment)-len(condition)-3)) - instructions = append(instructions, opcode.OP_BLOCK) - instructions = append(instructions, opcode.OpCode(len(scope))) instructions = append(instructions, bodyInstructions...) - instructions = append(instructions, opcode.OP_END_BLOCK) return instructions case *ast.LetStatement: var instructions []opcode.OpCode + frameOffset := c.define(node.Name.Value) value := c.Compile(node.Value) - index := c.addToScope(node.Name.Value) instructions = append(instructions, value...) instructions = append(instructions, opcode.OP_LET) - instructions = append(instructions, opcode.OpCode(index)) + instructions = append(instructions, opcode.OpCode(frameOffset)) return instructions case *ast.Identifier: - scopeIndex, valueIndex := c.findInScope(node.Value) - return []opcode.OpCode{ - opcode.OP_GET, - opcode.OpCode(valueIndex), - opcode.OpCode(scopeIndex), - } + variable := c.resolve(node.Value) + return variable.Get() case *ast.AssignExpression: var instructions []opcode.OpCode - scopeIndex, valueIndex := c.findInScope(node.Name.TokenLiteral()) + variable := c.resolve(node.Name.TokenLiteral()) + value := c.Compile(node.Value) instructions = append(instructions, value...) - instructions = append(instructions, - opcode.OP_SET, - opcode.OpCode(valueIndex), - opcode.OpCode(scopeIndex), - ) + instructions = append(instructions, variable.Set()...) return instructions - case *ast.PostfixExpression: - var instructions []opcode.OpCode - scopeIndex, valueIndex := c.findInScope(node.Left.TokenLiteral()) - instructions = append(instructions, - opcode.OP_INC, - opcode.OpCode(valueIndex), - opcode.OpCode(scopeIndex), - ) - - return instructions + // case *ast.PostfixExpression: + // var instructions []opcode.OpCode + // frameOffset := c.resolve(node.Left.TokenLiteral()) + // instructions = append(instructions, + // opcode.OP_INC, + // opcode.OpCode(frameOffset), + // ) + // + // return instructions case *ast.EchoStatement: var instructions []opcode.OpCode @@ -277,8 +241,35 @@ func (c *Compiler) Compile(node ast.Node) []opcode.OpCode { return instructions + case *ast.FunctionLiteral: + c.pushFrame() + + for _, param := range node.Parameters { + c.define(param.Value) + } + + bodyInstructions := c.Compile(node.Body) + instructions := []opcode.OpCode{ + opcode.OP_CONST, + opcode.OpCode( + c.addConstant( + value.NewFnValue(bodyInstructions, len(c.curFrame().Locals)), + ), + ), + } + + c.popFrame() + + return instructions + + case *ast.CallExpression: + instructions := c.Compile(node.Function) + instructions = append(instructions, opcode.OP_CALL) + + return instructions + default: - panic("Unimplemented Ast %d") + panic(fmt.Sprintf("Unimplemented Ast %T", node)) } } diff --git a/compiler/frame.go b/compiler/frame.go new file mode 100644 index 0000000..bbf362f --- /dev/null +++ b/compiler/frame.go @@ -0,0 +1,113 @@ +package compiler + +import ( + "github.com/joetifa2003/windlang/opcode" +) + +type VarScope int + +const ( + VarScopeLocal VarScope = iota + VarScopeGlobal + VarScopeFree +) + +type Var struct { + Name string + Index int + Scope VarScope +} + +func (v *Var) Get() []opcode.OpCode { + return opcode.Instructions{opcode.OP_GET, opcode.OpCode(v.Index)} +} + +func (v *Var) Set() opcode.Instructions { + return opcode.Instructions{opcode.OP_SET, opcode.OpCode(v.Index)} +} + +type Frame struct { + Parent *Frame + Instructions []opcode.OpCode + Locals []Var + FreeVars []Var + blocks [][]Var +} + +func NewFrame(parent *Frame) Frame { + return Frame{ + Parent: parent, + Instructions: []opcode.OpCode{}, + Locals: []Var{}, + FreeVars: []Var{}, + blocks: [][]Var{ + {}, + }, + } +} + +func (f *Frame) currentBlock() *[]Var { + return &f.blocks[len(f.blocks)-1] +} + +func (f *Frame) beginBlock() { + f.blocks = append(f.blocks, []Var{}) +} + +func (f *Frame) endBlock() { + f.blocks = f.blocks[:len(f.blocks)-1] +} + +func (f *Frame) define(name string) int { + local := Var{Name: name, Index: len(f.Locals)} + if f.Parent == nil { + local.Scope = VarScopeGlobal + } else { + local.Scope = VarScopeLocal + } + + f.Locals = append(f.Locals, local) + block := f.currentBlock() + *block = append(*block, local) + + return local.Index +} + +func (f *Frame) findLocal(name string) (Var, bool) { + for blockIdx := len(f.blocks) - 1; blockIdx >= 0; blockIdx-- { + for _, v := range f.blocks[blockIdx] { + if v.Name == name { + return v, true + } + } + } + + return Var{}, false +} + +func (f *Frame) defineFree(v Var) Var { + f.FreeVars = append(f.FreeVars, v) + v.Scope = VarScopeFree + v.Index = len(f.FreeVars) - 1 + + return v +} + +func (f *Frame) resolve(name string) Var { + v, ok := f.findLocal(name) + if !ok { + if f.Parent == nil { + panic("cannot resolve variable " + name) + } + + parentV := f.Parent.resolve(name) + + if parentV.Scope == VarScopeGlobal { + return parentV + } + + return f.defineFree(parentV) + } + + return v +} diff --git a/evaluator/builtins.go b/evaluator/builtins.go deleted file mode 100644 index 7eade35..0000000 --- a/evaluator/builtins.go +++ /dev/null @@ -1,68 +0,0 @@ -package evaluator - -import ( - "bufio" - "fmt" - "os" - - "github.com/joetifa2003/windlang/ast" -) - -var builtins = map[string]*GoFunction{ - "println": { - ArgsCount: -1, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - argsString := []interface{}{} - for _, arg := range args { - argsString = append(argsString, arg.Inspect()) - } - - fmt.Println(argsString...) - - return NIL, nil - }, - }, - "print": { - ArgsCount: -1, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - argsString := []interface{}{} - for _, arg := range args { - argsString = append(argsString, arg.Inspect()) - } - - fmt.Print(argsString...) - - return NIL, nil - }, - }, - "string": { - ArgsCount: 1, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - switch arg := args[0].(type) { - case *Integer: - return &String{Value: fmt.Sprintf("%d", arg.Value)}, nil - case *Float: - return &String{Value: fmt.Sprintf("%f", arg.Value)}, nil - } - - return nil, evaluator.newError(node.Token, "argument to `string` not supported") - }, - }, - "input": { - ArgsCount: -1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - if len(args) != 0 { - prompt := args[0] - fmt.Print(prompt.Inspect()) - } - - var input string - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - input = scanner.Text() - - return &String{Value: input}, nil - }, - }, -} diff --git a/evaluator/env.go b/evaluator/env.go deleted file mode 100644 index c19b98e..0000000 --- a/evaluator/env.go +++ /dev/null @@ -1,147 +0,0 @@ -package evaluator - -type Environment struct { - Store map[string]Object - ConstantStore map[string]Object - Outer *Environment - Includes []*Environment - IncludesAliased map[string]*IncludeObject -} - -func NewEnvironment() *Environment { - return &Environment{ - Store: nil, - ConstantStore: nil, - Outer: nil, - Includes: nil, - IncludesAliased: nil, - } -} - -func NewEnclosedEnvironment(outer *Environment) *Environment { - env := NewEnvironment() - env.Outer = outer - - return env -} - -func (e *Environment) Get(name string) (Object, bool) { - if e.Store == nil && e.ConstantStore == nil { - if e.Outer != nil { - return e.Outer.Get(name) - } else { - return e.getIncludes(name) - } - } - - obj, ok := e.ConstantStore[name] - if ok { - return obj, ok - } - - obj, ok = e.Store[name] - if !ok { - if e.Outer != nil { - obj, ok = e.Outer.Get(name) - } else { - return e.getIncludes(name) - } - } - - return obj, ok -} - -func (e *Environment) getIncludes(name string) (Object, bool) { - aliasedInclude, ok := e.IncludesAliased[name] - if ok { - return aliasedInclude, true - } - - for _, include := range e.Includes { - if obj, ok := include.Store[name]; ok { - return obj, ok - } - } - - return nil, false -} - -// Set For assigning -func (e *Environment) Set(name string, val Object) (Object, bool) { - if e.Store == nil { - if e.Outer != nil { - return e.Outer.Set(name, val) - } else { - return nil, false - } - } - - _, ok := e.Store[name] - if !ok { - if e.Outer != nil { - return e.Outer.Set(name, val) - } else { - return nil, false - } - } - - e.Store[name] = val - - return val, true -} - -// Let For local scope variables -func (e *Environment) Let(name string, val Object) Object { - if e.Store == nil { - e.Store = make(map[string]Object) - } - - e.Store[name] = val - - return val -} - -func (e *Environment) LetConstant(name string, val Object) Object { - if e.ConstantStore == nil { - e.ConstantStore = make(map[string]Object) - } - - e.ConstantStore[name] = val - - return val -} - -func (e *Environment) IsConstant(name string) bool { - if e.ConstantStore == nil { - if e.Outer != nil { - return e.Outer.IsConstant(name) - } else { - return false - } - } - - _, ok := e.ConstantStore[name] - if ok { - return true - } else { - if e.Outer != nil { - return e.Outer.IsConstant(name) - } else { - return false - } - } -} - -func (e *Environment) ClearStore() { - for k := range e.Store { - delete(e.Store, k) - } -} - -func (e *Environment) AddAlias(name string, include *IncludeObject) { - if e.IncludesAliased == nil { - e.IncludesAliased = make(map[string]*IncludeObject) - } - - e.IncludesAliased[name] = include -} diff --git a/evaluator/envManager.go b/evaluator/envManager.go deleted file mode 100644 index 456fca0..0000000 --- a/evaluator/envManager.go +++ /dev/null @@ -1,34 +0,0 @@ -package evaluator - -type EnvironmentManager struct { - environments map[string]*Environment // A map of environments for each file -} - -func NewEnvironmentManager() *EnvironmentManager { - return &EnvironmentManager{ - environments: make(map[string]*Environment), - } -} - -// Get returns the environment for the given file name. and whither it's evaluated or not -func (em *EnvironmentManager) Get(fileName string) (*Environment, bool) { - env, ok := GetStdlib(fileName) - if ok { - em.environments[fileName] = env - return env, true - } - - env, ok = em.environments[fileName] - if ok { - return env, true - } - - return em.createFileEnv(fileName), false -} - -func (em *EnvironmentManager) createFileEnv(fileName string) *Environment { - env := NewEnvironment() - em.environments[fileName] = env - - return env -} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go deleted file mode 100644 index 4d6fb4b..0000000 --- a/evaluator/evaluator.go +++ /dev/null @@ -1,851 +0,0 @@ -package evaluator - -import ( - "fmt" - "io/ioutil" - "math" - - "github.com/joetifa2003/windlang/ast" - "github.com/joetifa2003/windlang/lexer" - "github.com/joetifa2003/windlang/parser" - "github.com/joetifa2003/windlang/token" -) - -var ( - NIL = &Nil{} - TRUE = &Boolean{Value: true} - FALSE = &Boolean{Value: false} -) - -type Evaluator struct { - envManager *EnvironmentManager - filePath string -} - -func New(envManager *EnvironmentManager, filePath string) *Evaluator { - return &Evaluator{ - envManager: envManager, - filePath: filePath, - } -} - -// Eval returns the result of the evaluation and potential error -func (e *Evaluator) Eval(node ast.Node, env *Environment, this Object) (Object, *Error) { - switch node := node.(type) { - case *ast.Program: - return e.evalProgram(node.Statements, env, this) - - case *ast.BlockStatement: - return e.evalBlockStatement(node, env, this) - - case *ast.ExpressionStatement: - return e.Eval(node.Expression, env, this) - - case *ast.LetStatement: - return e.evalLetStatement(node, env, this) - - case *ast.ReturnStatement: - return e.evalReturnStatement(node, env, this) - - case *ast.ForStatement: - return e.evalForStatement(node, env, this) - - case *ast.WhileStatement: - return e.evalWhileStatement(node, env, this) - - case *ast.IncludeStatement: - return e.evalIncludeStatement(node, env, this) - - // Expressions - case *ast.IntegerLiteral: - return Integer{Value: node.Value}, nil - - case *ast.FloatLiteral: - return &Float{Value: node.Value}, nil - - case *ast.Boolean: - return boolToBoolObject(node.Value), nil - - case *ast.PrefixExpression: - return e.evalPrefixExpression(node, env, this) - - case *ast.InfixExpression: - return e.evalInfixExpression(node, env, this) - - case *ast.PostfixExpression: - return e.evalPostfixExpression(node, env, this) - - case *ast.IfExpression: - return e.evalIfExpression(node, env, this) - - case *ast.Identifier: - return e.evalIdentifier(node, env, this) - - case *ast.FunctionLiteral: - return &Function{Parameters: node.Parameters, Body: node.Body, Env: env, This: this}, nil - - case *ast.CallExpression: - return e.evalCallExpression(node, env, this) - - case *ast.StringLiteral: - return &String{Value: node.Value}, nil - - case *ast.AssignExpression: - return e.evalAssignExpression(node, env, this) - - case *ast.ArrayLiteral: - return e.evalArrayLiteral(node, env, this) - - case *ast.IndexExpression: - return e.evalIndexExpression(node, env, this) - - case *ast.NilLiteral: - return NIL, nil - - case *ast.HashLiteral: - return e.evalHashLiteral(node, env) - - case *ast.EchoStatement: - val, err := e.Eval(node.Value, env, this) - if err != nil { - return nil, err - } - - fmt.Println(val.Inspect()) - } - - return NIL, nil -} - -func (e *Evaluator) evalCallExpression(node *ast.CallExpression, env *Environment, this Object) (Object, *Error) { - function, err := e.Eval(node.Function, env, this) - if err != nil { - return nil, err - } - - args, err := e.evalExpressions(node.Arguments, env, this) - if err != nil { - return nil, err - } - - return e.applyFunction(node, function, args) -} - -func (e *Evaluator) applyFunction(node *ast.CallExpression, fn Object, args []Object) (Object, *Error) { - switch fn := fn.(type) { - case *Function: - if len(args) != len(fn.Parameters) { - return nil, e.newError(node.Token, "expected %d arg(s) got %d", len(fn.Parameters), len(args)) - } - - extendedEnv := e.extendFunctionEnv(fn, args) - evaluated, err := e.Eval(fn.Body, extendedEnv, fn.This) - if err != nil { - return nil, err - } - - return unwrapReturnValue(evaluated), nil - - case *GoFunction: - if fn.ArgsCount != -1 && len(args) != fn.ArgsCount { - return nil, e.newError(node.Token, "expected %d arg(s) got %d", fn.ArgsCount, len(args)) - } - - for i, t := range fn.ArgsTypes { - if t != Any && t != args[i].Type() { - return nil, e.newError(node.Token, "expected arg %d to be of type %s got %s", i, t, args[i].Type()) - } - } - - return fn.Fn(e, node, args...) - - default: - return nil, e.newError(node.Token, "not a function: %s", fn.Inspect()) - } - -} - -func (e *Evaluator) extendFunctionEnv( - fn *Function, - args []Object, -) *Environment { - env := NewEnclosedEnvironment(fn.Env) - - for paramIdx, param := range fn.Parameters { - env.Let(param.Value, args[paramIdx]) - } - - return env -} - -func unwrapReturnValue(obj Object) Object { - if returnValue, ok := obj.(*ReturnValue); ok { - return returnValue.Value - } - - return obj -} - -func (e *Evaluator) evalLetStatement(node *ast.LetStatement, env *Environment, this Object) (Object, *Error) { - val, err := e.Eval(node.Value, env, this) - if err != nil { - return nil, err - } - - if node.Constant { - env.LetConstant(node.Name.Value, val) - } else { - env.Let(node.Name.Value, val) - } - - return NIL, nil -} - -func (e *Evaluator) evalReturnStatement(node *ast.ReturnStatement, env *Environment, this Object) (Object, *Error) { - val, err := e.Eval(node.ReturnValue, env, this) - if err != nil { - return nil, err - } - - return &ReturnValue{Value: val}, nil -} - -func (e *Evaluator) evalExpressions(exps []ast.Expression, env *Environment, this Object) ([]Object, *Error) { - var result []Object - - for _, exp := range exps { - evaluated, err := e.Eval(exp, env, this) - if err != nil { - return nil, err - } - - result = append(result, evaluated) - } - - return result, nil -} - -func (e *Evaluator) evalProgram(statements []ast.Statement, env *Environment, this Object) (Object, *Error) { - var result Object - var err *Error - - for _, statement := range statements { - result, err = e.Eval(statement, env, this) - if err != nil { - return nil, err - } - - switch result := result.(type) { - case *ReturnValue: - return result.Value, nil - } - } - - return result, nil -} - -func (e *Evaluator) evalBlockStatement(block *ast.BlockStatement, env *Environment, this Object) (Object, *Error) { - enclosedEnv := NewEnclosedEnvironment(env) - - var result Object - var err *Error - - for _, statement := range block.Statements { - result, err = e.Eval(statement, enclosedEnv, this) - if err != nil { - return nil, err - } - - if isReturn(result) { - return result, nil - } - } - - return result, nil -} - -func (e *Evaluator) evalForStatement(node *ast.ForStatement, env *Environment, this Object) (Object, *Error) { - enclosedEnv := NewEnclosedEnvironment(env) - - _, err := e.Eval(node.Initializer, enclosedEnv, this) - if err != nil { - return nil, err - } - - switch body := node.Body.(type) { - case *ast.BlockStatement: // to optimize for block statements - bodyEnv := NewEnclosedEnvironment(enclosedEnv) - - for { - condition, err := e.Eval(node.Condition, enclosedEnv, this) - if err != nil { - return nil, err - } - - if !isTruthy(condition) { - break - } - - result, err := e.evalBlockStatement(body, bodyEnv, this) - if err != nil { - return nil, err - } - - if isReturn(result) { - return result, nil - } - - bodyEnv.ClearStore() - - _, err = e.Eval(node.Increment, enclosedEnv, this) - if err != nil { - return nil, err - } - } - - default: - for { - condition, err := e.Eval(node.Condition, enclosedEnv, this) - if err != nil { - return nil, err - } - - if !isTruthy(condition) { - break - } - - result, err := e.Eval(body, enclosedEnv, this) - if err != nil { - return nil, err - } - - if isReturn(result) { - return result, nil - } - - _, err = e.Eval(node.Increment, enclosedEnv, this) - if err != nil { - return nil, err - } - } - } - - return NIL, nil -} - -func (e *Evaluator) evalWhileStatement(node *ast.WhileStatement, env *Environment, this Object) (Object, *Error) { - for { - condition, err := e.Eval(node.Condition, env, this) - if err != nil { - return nil, err - } - - if !isTruthy(condition) { - break - } - - result, err := e.Eval(node.Body, env, this) - if err != nil { - return nil, err - } - - if isReturn(result) { - return result, nil - } - } - - return NIL, nil -} - -func (e *Evaluator) evalIncludeStatement(node *ast.IncludeStatement, env *Environment, this Object) (Object, *Error) { - path := node.Path - fileEnv, evaluated := e.envManager.Get(path) - - if !evaluated { - file, ioErr := ioutil.ReadFile(path) - if ioErr != nil { - return nil, e.newError(node.Token, "cannot read file: %s", path) - } - - input := string(file) - lexer := lexer.New(input) - parser := parser.New(lexer, path) - program := parser.ParseProgram() - parser.ReportErrors() - - _, err := e.Eval(program, fileEnv, this) - if err != nil { - return nil, err - } - } - - if node.Alias != nil { - includeObject := &IncludeObject{ - Value: fileEnv, - } - - env.AddAlias(node.Alias.Value, includeObject) - } else { - env.Includes = append(env.Includes, fileEnv) - } - - return NIL, nil -} - -func (e *Evaluator) evalPrefixExpression(node *ast.PrefixExpression, env *Environment, this Object) (Object, *Error) { - right, err := e.Eval(node.Right, env, this) - if err != nil { - return nil, err - } - - switch node.Operator { - case "!": - return e.evalBangOperatorExpression(node, right) - case "-": - return e.evalMinusPrefixOperatorExpression(node, right) - - default: - return nil, e.newError(node.Token, "unknown operator: %s%s", node.Operator, right.Inspect()) - } -} - -func (e *Evaluator) evalBangOperatorExpression(node *ast.PrefixExpression, right Object) (Object, *Error) { - return boolToBoolObject(!isTruthy(right)), nil -} - -func (e *Evaluator) evalMinusPrefixOperatorExpression(node *ast.PrefixExpression, right Object) (Object, *Error) { - switch right := right.(type) { - case Integer: - return Integer{Value: -right.Value}, nil - case *Float: - return &Float{Value: -right.Value}, nil - default: - return nil, e.newError(node.Token, "unknown operator: -%s", right.Inspect()) - } -} - -func (e *Evaluator) evalInfixExpression(node *ast.InfixExpression, env *Environment, this Object) (Object, *Error) { - left, err := e.Eval(node.Left, env, this) - if err != nil { - return nil, err - } - - right, err := e.Eval(node.Right, env, this) - if err != nil { - return nil, err - } - - switch { - case left.Type() == IntegerObj && right.Type() == IntegerObj: - leftVal := left.(Integer).Value - rightVal := right.(Integer).Value - - return e.evalIntegerInfixExpression(node, node.Operator, leftVal, rightVal) - - case left.Type() == FloatObj && right.Type() == FloatObj: - leftVal := left.(*Float).Value - rightVal := right.(*Float).Value - - return e.evalFloatInfixExpression(node, node.Operator, leftVal, rightVal) - - case left.Type() == FloatObj && right.Type() == IntegerObj: - leftVal := left.(*Float).Value - rightVal := right.(Integer).Value - - return e.evalFloatInfixExpression(node, node.Operator, leftVal, float64(rightVal)) - - case left.Type() == IntegerObj && right.Type() == FloatObj: - leftVal := left.(Integer).Value - rightVal := right.(*Float).Value - - return e.evalFloatInfixExpression(node, node.Operator, float64(leftVal), rightVal) - - case left.Type() == StringObj && right.Type() == StringObj: - return e.evalStringInfixExpression(node, node.Operator, left, right) - - case node.Operator == "==": - return boolToBoolObject(left == right), nil - - case node.Operator == "!=": - return boolToBoolObject(left != right), nil - - case node.Operator == "&&": - return boolToBoolObject(isTruthy(left) && isTruthy(right)), nil - - case node.Operator == "||": - return boolToBoolObject(isTruthy(left) || isTruthy(right)), nil - - default: - return nil, e.newError(node.Token, "unknown operator: %s %s %s", - left.Inspect(), node.Operator, right.Inspect()) - } -} - -func (e *Evaluator) evalIntegerInfixExpression(node *ast.InfixExpression, operator string, left, right int) (Object, *Error) { - switch operator { - case "<": - return boolToBoolObject(left < right), nil - case "<=": - return boolToBoolObject(left <= right), nil - case ">": - return boolToBoolObject(left > right), nil - case ">=": - return boolToBoolObject(left >= right), nil - case "==": - return boolToBoolObject(left == right), nil - case "!=": - return boolToBoolObject(left != right), nil - case "+": - return Integer{Value: left + right}, nil - case "-": - return Integer{Value: left - right}, nil - case "*": - return Integer{Value: left * right}, nil - case "/": - return Integer{Value: left / right}, nil - case "%": - return Integer{Value: left % right}, nil - default: - return nil, e.newError(node.Token, "unknown operator: %d %s %d", - left, operator, right) - } -} - -func (e *Evaluator) evalFloatInfixExpression(node *ast.InfixExpression, operator string, left, right float64) (Object, *Error) { - switch operator { - case "<": - return boolToBoolObject(left < right), nil - case "<=": - return boolToBoolObject(left <= right), nil - case ">": - return boolToBoolObject(left > right), nil - case ">=": - return boolToBoolObject(left >= right), nil - case "==": - return boolToBoolObject(left == right), nil - case "!=": - return boolToBoolObject(left != right), nil - case "+": - return &Float{Value: left + right}, nil - case "-": - return &Float{Value: left - right}, nil - case "*": - return &Float{Value: left * right}, nil - case "/": - return &Float{Value: left / right}, nil - case "%": - return &Float{Value: math.Mod(left, right)}, nil - default: - return nil, e.newError(node.Token, "unknown operator: %f %s %f", - left, operator, right) - } -} - -func (e *Evaluator) evalStringInfixExpression(node *ast.InfixExpression, operator string, left, right Object) (Object, *Error) { - if operator != "+" { - return nil, e.newError(node.Token, "unknown operator: %s %s %s", - left.Inspect(), operator, right.Inspect()) - } - - leftVal := left.(*String).Value - rightVal := right.(*String).Value - return &String{Value: leftVal + rightVal}, nil -} - -func (e *Evaluator) evalIfExpression(ie *ast.IfExpression, env *Environment, this Object) (Object, *Error) { - condition, err := e.Eval(ie.Condition, env, this) - if err != nil { - return nil, err - } - - if isTruthy(condition) { - return e.Eval(ie.ThenBranch, env, this) - } else if ie.ElseBranch != nil { - return e.Eval(ie.ElseBranch, env, this) - } else { - return NIL, nil - } -} - -func (e *Evaluator) evalIdentifier(node *ast.Identifier, env *Environment, this Object) (Object, *Error) { - if val, ok := env.Get(node.Value); ok { - return val, nil - } - - if node.Value == "this" { - return this, nil - } - - if builtin, ok := builtins[node.Value]; ok { - return builtin, nil - } - - return nil, e.newError(node.Token, "identifier not found: "+node.Value) -} - -func (e *Evaluator) evalArrayLiteral(node *ast.ArrayLiteral, env *Environment, this Object) (Object, *Error) { - objects := make([]Object, len(node.Value)) - - for index, expr := range node.Value { - object, err := e.Eval(expr, env, this) - if err != nil { - return nil, err - } - - objects[index] = object - } - - return &Array{Value: objects}, nil -} - -func (e *Evaluator) evalIndexExpression(node *ast.IndexExpression, env *Environment, this Object) (Object, *Error) { - left, err := e.Eval(node.Left, env, this) - if err != nil { - return nil, err - } - - index, err := e.Eval(node.Index, env, this) - if err != nil { - return nil, err - } - - switch left := left.(type) { - case *Array: - switch index.Type() { - case IntegerObj: - return e.evalArrayIndexExpression(node, left, index) - default: - return e.evalWithFunctionsIndexExpression(node, left, index) - } - - case *Hash: - return e.evalHashIndexExpression(node, left, index) - - case *IncludeObject: - return e.evalIncludeIndexExpression(node, left, index) - - case ObjectWithFunctions: - return e.evalWithFunctionsIndexExpression(node, left, index) - - default: - return nil, e.newError(node.Token, "index operator not supported: %s", left.Inspect()) - } -} - -func (e *Evaluator) evalHashLiteral(node *ast.HashLiteral, env *Environment) (Object, *Error) { - hash := &Hash{Pairs: make(map[HashKey]Object)} - - for key, value := range node.Pairs { - hashKey, err := e.Eval(key, env, hash) - if err != nil { - return nil, err - } - - key, ok := hashKey.(Hashable) - if !ok { - return nil, e.newError(node.Token, "unusable as hash key: %s", hashKey.Inspect()) - } - - hashValue, err := e.Eval(value, env, hash) - if err != nil { - return nil, err - } - - hash.Pairs[key.HashKey()] = hashValue - } - - return hash, nil -} - -func (e *Evaluator) evalArrayIndexExpression(node *ast.IndexExpression, array *Array, index Object) (Object, *Error) { - idx := index.(Integer).Value - max := len(array.Value) - 1 - - if idx < 0 || idx > max { - return NIL, nil - } - - return array.Value[idx], nil -} - -func (e *Evaluator) evalHashIndexExpression(node *ast.IndexExpression, hash *Hash, index Object) (Object, *Error) { - key, ok := index.(Hashable) - if !ok { - return nil, e.newError(node.Token, "unusable as hash key: %s", index.Inspect()) - } - - if val, ok := hash.Pairs[key.HashKey()]; ok { - return val, nil - } - - return NIL, nil -} - -func (e *Evaluator) evalIncludeIndexExpression(node *ast.IndexExpression, include, index Object) (Object, *Error) { - includeObj := include.(*IncludeObject) - key, ok := index.(*String) - if !ok { - return nil, e.newError(node.Token, "unusable as include key: %s", key.Inspect()) - } - - obj, ok := includeObj.Value.Store[key.Value] - if !ok { - return nil, e.newError(node.Token, "include key not found: %s", key.Inspect()) - } - - return obj, nil -} - -func (e *Evaluator) evalWithFunctionsIndexExpression(node *ast.IndexExpression, obj ObjectWithFunctions, index Object) (Object, *Error) { - name, ok := index.(*String) - if !ok { - return nil, e.newError(node.Token, "cannot use %s as an index", index.Type().String()) - } - - fn, ok := obj.GetFunction(name.Value) - if !ok { - return nil, e.newError( - node.Token, "cannot find '%s' function on type %s", - name.Value, - obj.(Object).Type().String(), - ) - } - - return fn, nil -} - -func (e *Evaluator) evalAssignExpression(node *ast.AssignExpression, env *Environment, this Object) (Object, *Error) { - val, err := e.Eval(node.Value, env, this) - if err != nil { - return nil, err - } - - switch left := node.Name.(type) { - case *ast.Identifier: - return e.evalAssingIdentifierExpression(node, left, val, env) - - case *ast.IndexExpression: - return e.evalAssingIndexExpression(node, left, val, env, this) - } - - return nil, e.newError(node.Token, "cannot assign to %s", node.Name.String()) -} - -func (e *Evaluator) evalAssingIdentifierExpression(node *ast.AssignExpression, left *ast.Identifier, val Object, env *Environment) (Object, *Error) { - if env.IsConstant(left.Value) { - return nil, e.newError(node.Token, "cannot assign to a constant variable %s", left.Value) - } - - _, ok := env.Set(left.Value, val) - if !ok { - return nil, e.newError(node.Token, "identifier not found: "+left.Value) - } - - return val, nil -} - -func (e *Evaluator) evalAssingIndexExpression(node *ast.AssignExpression, left *ast.IndexExpression, val Object, env *Environment, this Object) (Object, *Error) { - leftObj, err := e.Eval(left.Left, env, this) - if err != nil { - return nil, err - } - - index, err := e.Eval(left.Index, env, this) - if err != nil { - return nil, err - } - - switch leftObj := leftObj.(type) { - case *Array: - return e.evalAssingArrayIndexExpression(node, leftObj, index, val) - case *Hash: - return e.evalAssingHashIndexExpression(node, leftObj, index, val) - default: - return nil, e.newError(node.Token, "index operator not supported: %s", leftObj.Inspect()) - } -} - -func (e *Evaluator) evalAssingArrayIndexExpression(node *ast.AssignExpression, leftObj *Array, index Object, val Object) (Object, *Error) { - idx := index.(Integer).Value - max := len(leftObj.Value) - 1 - - if idx < 0 || idx > max { - return nil, e.newError(node.Token, "index out of bounds") - } - - leftObj.Value[idx] = val - - return val, nil -} - -func (e *Evaluator) evalAssingHashIndexExpression(node *ast.AssignExpression, leftObj *Hash, index Object, val Object) (Object, *Error) { - key, ok := index.(Hashable) - if !ok { - return nil, e.newError(node.Token, "unusable as hash key: %s", index.Inspect()) - } - - leftObj.Pairs[key.HashKey()] = val - - return val, nil -} - -func (e *Evaluator) evalPostfixExpression(node *ast.PostfixExpression, env *Environment, this Object) (Object, *Error) { - left, err := e.Eval(node.Left, env, this) - if err != nil { - return nil, err - } - - switch left := left.(type) { - case Integer: - return e.evalPostfixIntegerExpression(node, node.Operator, left) - default: - return nil, e.newError(node.Token, "postfix operator not supported: %s", left.Inspect()) - } -} - -func (e *Evaluator) evalPostfixIntegerExpression(node *ast.PostfixExpression, operator string, left Integer) (Object, *Error) { - switch operator { - case "++": - left.Value++ - return left, nil - case "--": - left.Value-- - return left, nil - default: - return nil, e.newError(node.Token, "postfix operator not supported: %s", operator) - } -} - -func (e *Evaluator) newError(token token.Token, format string, a ...interface{}) *Error { - return &Error{Message: fmt.Sprintf("[file %s:%d] %s", e.filePath, token.Line, fmt.Sprintf(format, a...))} -} - -func isTruthy(obj Object) bool { - switch obj { - case NIL: - return false - - case TRUE: - return true - - case FALSE: - return false - - default: - return true - } -} - -func boolToBoolObject(value bool) *Boolean { - if value { - return TRUE - } - - return FALSE -} - -func isReturn(obj Object) bool { - rt := obj.Type() - - return rt == ReturnValueObj -} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go deleted file mode 100644 index d5afd7c..0000000 --- a/evaluator/evaluator_test.go +++ /dev/null @@ -1,491 +0,0 @@ -package evaluator - -import ( - "testing" - - "github.com/joetifa2003/windlang/lexer" - "github.com/joetifa2003/windlang/parser" - "github.com/stretchr/testify/assert" -) - -const fileName = "main-test.wind" - -func TestEvalBooleanInfixExpression(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected bool - }{ - {"true", true}, - {"false", false}, - {"1 < 2", true}, - {"1 > 2", false}, - {"1 < 1", false}, - {"1 > 1", false}, - {"1 == 1", true}, - {"1 != 1", false}, - {"1 == 2", false}, - {"1 != 2", true}, - {"3.1 > 3.0", true}, - {"3.1 > 3", true}, - {"3 < 3.1", true}, - {"true == true", true}, - {"false == false", true}, - {"true == false", false}, - {"true != false", true}, - {"false != true", true}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Boolean{}, evaluated) - boolVal := evaluated.(*Boolean).Value - assert.Equal(tc.expected, boolVal) - } -} - -func TestBangOperator(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - {"!true", &Boolean{Value: false}}, - {"!false", &Boolean{Value: true}}, - {"!5", &Boolean{Value: false}}, - {"!!true", &Boolean{Value: true}}, - {"!!false", &Boolean{Value: false}}, - {"!!5", &Boolean{Value: true}}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestIfElseExpressions(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - {"if (true) { 10 }", &Integer{Value: 10}}, - {"if (1) { 10 }", &Integer{Value: 10}}, - {"if (1 < 2) { 10 }", &Integer{Value: 10}}, - {"if (1 > 2) { 10 } else { 20 }", &Integer{Value: 20}}, - {"if (1 < 2) { 10 } else { 20 }", &Integer{Value: 10}}, - {"if (false) { 1 }", &Nil{}}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestLetStatements(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - {"let a = 5; a;", &Integer{Value: 5}}, - {"let a = 5 * 5; a;", &Integer{Value: 25}}, - {"let a = 5; let b = a; b;", &Integer{Value: 5}}, - {"let a = 5; let b = a; let c = a + b + 5; c;", &Integer{Value: 15}}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestFunctions(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - {"let double = fn(x) { x * 2; }; double(5);", &Integer{Value: 10}}, - {"let add = fn(x, y) { x + y; }; add(5, 5);", &Integer{Value: 10}}, - {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", &Integer{Value: 20}}, - {"fn(x) { x; }(5)", &Integer{Value: 5}}, - {"fn(x) { return x; }(5)", &Integer{Value: 5}}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestClosures(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - {"let newAdder = fn(x) { fn(y) { x + y }; }; let addTwo = newAdder(2); addTwo(2);", &Integer{Value: 4}}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestStringLiteral(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - {`"Hello World!"`, &String{Value: "Hello World!"}}, - {`"Hello" + " World!"`, &String{Value: "Hello World!"}}, - {`"Hello" + " " + "World!"`, &String{Value: "Hello World!"}}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestHashLiterals(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - { - `let x = { "foo": 1, "bar": 2 }; x["foo"]`, - &Integer{Value: 1}, - }, - { - `let x = { "foo": 1, "bar": 2 }; x.bar`, - &Integer{Value: 2}, - }, - { - `let x = { "foo": fn() { return 1; } }; x["foo"]()`, - &Integer{Value: 1}, - }, - { - `let x = { "foo": fn() { return 1; } }; x.foo()`, - &Integer{Value: 1}, - }, - { - `let x = { "foo": 1 }; x.foo++; x.foo`, - &Integer{Value: 2}, - }, - { - `let x = { "foo": 1 }; x["foo"]++; x["foo"]`, - &Integer{Value: 2}, - }, - { - `let x = {"foo": { "bar": 1 } }; x.foo.bar`, - &Integer{Value: 1}, - }, - { - `let x = {"foo": { "bar": 1 } }; x.foo.bar = 2; x.foo.bar`, - &Integer{Value: 2}, - }, - { - `let x = { "foo": { "bar": fn() { return { "baz": 1 }; } } }; x.foo.bar().baz`, - &Integer{Value: 1}, - }, - { - `let x = { "value": 1, "incrementValue": fn() { this.value++ } }; x.incrementValue(); x.value`, - &Integer{Value: 2}, - }, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestIntInfixExpression(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected int64 - }{ - {"5 + 5;", 10}, - {"5 - 5;", 0}, - {"5 * 5;", 25}, - {"5 / 5;", 1}, - {"4 % 2;", 0}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Integer{}, evaluated) - intVal := evaluated.(*Integer).Value - assert.Equal(tc.expected, intVal) - } -} - -func TestFloatInfixExpression(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected float64 - }{ - {"5.0 + 5.0;", 10}, - {"5.5 - 5.5;", 0}, - {"5.5 * 5.5;", 30.25}, - {"5.8 / 5.8;", 1}, - {"4.0 % 2.0;", 0}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Float{}, evaluated) - intVal := evaluated.(*Float).Value - assert.Equal(tc.expected, intVal) - } -} - -func TestFloatInfixIntExpression(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected float64 - }{ - {"5.0 + 5;", 10}, - {"5.5 - 5;", 0.5}, - {"5.5 * 5;", 27.5}, - {"5.8 / 5;", 1.16}, - {"4.0 % 2;", 0}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Float{}, evaluated) - intVal := evaluated.(*Float).Value - assert.Equal(tc.expected, intVal) - } -} - -func TestIntPrefixOperators(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected int64 - }{ - {"-5;", -5}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Integer{}, evaluated) - intVal := evaluated.(*Integer).Value - assert.Equal(tc.expected, intVal) - } -} - -func TestFloatPrefixOperators(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected float64 - }{ - {"-5.0;", -5}, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Float{}, evaluated) - intVal := evaluated.(*Float).Value - assert.Equal(tc.expected, intVal) - } -} - -func TestArrayLiteral(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected []Object - }{ - { - `[1, 2, 3]`, - []Object{&Integer{Value: 1}, &Integer{Value: 2}, &Integer{Value: 3}}, - }, - { - `[1, 2.5, 3, true, "Hello"]`, - []Object{&Integer{Value: 1}, &Float{Value: 2.5}, &Integer{Value: 3}, &Boolean{Value: true}, &String{Value: "Hello"}}, - }, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(&Array{}, evaluated) - arrayVal := evaluated.(*Array).Value - assert.Equal(tc.expected, arrayVal) - } -} - -func TestArrayIndex(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - { - `[1, 2, 3][0]`, - &Integer{Value: 1}, - }, - { - `[1, 5, true, "Hello", fn() { return "Hello"; }][4]()`, - &String{Value: "Hello"}, - }, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestForLoop(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - { - ` - let x = 0; - - for (let i = 0; i < 5; i++) { - x = x + 1; - } - - x - `, - &Integer{Value: 5}, - }, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestWhileLoop(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - { - ` - let x = 0; - - while (x < 5) { - x = x + 1; - } - - x - `, - &Integer{Value: 5}, - }, - } - - for _, tc := range tests { - evaluated, err := testEval(tc.input) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func TestRecursion(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - input string - expected Object - }{ - { - ` - let multiply = fn(n, m) { - if (m == 0) { - return 0; - } - - return n + multiply(n, m - 1); - }; - multiply(2, 3); - `, - &Integer{Value: 6}, - }, - } - - for _, tc := range tests { - evaluated, err := testEval( - tc.input, - ) - assert.Nil(err) - assert.IsType(tc.expected, evaluated) - assert.Equal(tc.expected, evaluated) - } -} - -func testEval(input string) (Object, *Error) { - l := lexer.New(input) - p := parser.New(l, fileName) - program := p.ParseProgram() - - envManager := NewEnvironmentManager() - env, _ := envManager.Get(fileName) - evaluator := New(envManager, fileName) - - return evaluator.Eval(program, env, nil) -} diff --git a/evaluator/object.go b/evaluator/object.go deleted file mode 100644 index e54c97b..0000000 --- a/evaluator/object.go +++ /dev/null @@ -1,281 +0,0 @@ -package evaluator - -import ( - "bytes" - "fmt" - "math" - "strings" - - "github.com/joetifa2003/windlang/ast" -) - -type ObjectType int - -const ( - Any ObjectType = iota - IntegerObj - FloatObj - BooleanObj - NilObj - ReturnValueObj - ErrorObj - FunctionObj - StringObj - BuiltinObj - ArrayObj - HashObj - IncludeObj -) - -func (ot ObjectType) String() string { - switch ot { - case IntegerObj: - return "INTEGER" - case FloatObj: - return "FLOAT" - case BooleanObj: - return "BOOLEAN" - case NilObj: - return "NIL" - case ReturnValueObj: - return "RETURN_VALUE" - case ErrorObj: - return "ERROR" - case FunctionObj: - return "FUNCTION" - case StringObj: - return "STRING" - case BuiltinObj: - return "BUILTIN" - case ArrayObj: - return "ARRAY" - case HashObj: - return "HASH" - case IncludeObj: - return "INCLUDE" - default: - return "UNKNOWN" - } -} - -type Object interface { - Type() ObjectType - Inspect() string - Clone() Object -} - -type OwnedFunction[T Object] struct { - ArgsCount int - ArgsTypes []ObjectType - Fn func(evaluator *Evaluator, node *ast.CallExpression, this T, args ...Object) (Object, *Error) -} - -type ObjectWithFunctions interface { - GetFunction(name string) (*GoFunction, bool) -} - -func GetFunctionFromObject[T Object](name string, object T, functions map[string]OwnedFunction[T]) (*GoFunction, bool) { - if fn, ok := functions[name]; ok { - return &GoFunction{ - ArgsCount: fn.ArgsCount, - ArgsTypes: fn.ArgsTypes, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - return fn.Fn(evaluator, node, object, args...) - }, - }, true - } - - return nil, false -} - -type Hashable interface { - HashKey() HashKey -} - -type HashKey struct { - Type ObjectType - Value uint64 - InspectValue string -} - -func (hk *HashKey) Inspect() string { - return hk.InspectValue -} - -type Integer struct { - Value int -} - -func (i Integer) Type() ObjectType { return IntegerObj } -func (i Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } -func (i Integer) HashKey() HashKey { - return HashKey{Type: i.Type(), Value: uint64(i.Value)} -} -func (i Integer) Clone() Object { - return &i -} - -type Float struct { - Value float64 -} - -func (f *Float) Type() ObjectType { return FloatObj } -func (f *Float) Inspect() string { return fmt.Sprintf("%f", f.Value) } -func (f Float) Clone() Object { - return &f -} - -type Boolean struct { - Value bool -} - -func (b *Boolean) Type() ObjectType { return BooleanObj } -func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) } -func (b *Boolean) HashKey() HashKey { - var val uint64 - - if b.Value { - val = 1 - } else { - val = 2 - } - - return HashKey{Type: b.Type(), Value: val, InspectValue: b.Inspect()} -} -func (b Boolean) Clone() Object { - return &b -} - -type Nil struct{} - -func (n *Nil) Type() ObjectType { return NilObj } -func (n *Nil) Inspect() string { return "nil" } -func (n Nil) Clone() Object { - return &n -} - -type ReturnValue struct { - Value Object -} - -func (rv *ReturnValue) Type() ObjectType { return ReturnValueObj } -func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } -func (rv ReturnValue) Clone() Object { - return &rv -} - -type Error struct { - Message string -} - -func (e *Error) Type() ObjectType { return ErrorObj } -func (e *Error) Inspect() string { return e.Message } - -type Function struct { - Parameters []*ast.Identifier - Body *ast.BlockStatement - Env *Environment - This Object -} - -func (f *Function) Type() ObjectType { return FunctionObj } -func (f *Function) Inspect() string { - var out bytes.Buffer - params := []string{} - for _, p := range f.Parameters { - params = append(params, p.String()) - } - out.WriteString("fn") - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") {\n") - out.WriteString(f.Body.String()) - out.WriteString("\n}") - - return out.String() -} -func (f Function) Clone() Object { - return &f -} - -type BuiltinFunction func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) - -type GoFunction struct { - ArgsCount int - ArgsTypes []ObjectType - Fn BuiltinFunction -} - -func (b *GoFunction) Type() ObjectType { return BuiltinObj } -func (b *GoFunction) Inspect() string { return "builtin function" } -func (b GoFunction) Clone() Object { - return &b -} - -type Hash struct { - Pairs map[HashKey]Object -} - -func (h *Hash) Type() ObjectType { return HashObj } -func (h *Hash) Inspect() string { - var out bytes.Buffer - - out.WriteString("{") - - for key, value := range h.Pairs { - out.WriteString(key.Inspect()) - out.WriteString(": ") - out.WriteString(value.Inspect()) - out.WriteString(", ") - } - - out.WriteString("}") - - return out.String() -} -func (h Hash) Clone() Object { - return &h -} - -type IncludeObject struct { - Value *Environment -} - -func (i *IncludeObject) Type() ObjectType { return IncludeObj } -func (i *IncludeObject) Inspect() string { - return "include_OBJ" -} -func (i IncludeObject) Clone() Object { - return &i -} - -func GetObjectFromInterFace(v interface{}) Object { - switch v := v.(type) { - case float64: - if v == math.Trunc(v) { - return &Integer{Value: int(v)} - } else { - return &Float{Value: v} - } - - case string: - return &String{Value: v} - - case bool: - if v { - return TRUE - } else { - return FALSE - } - - case []interface{}: - res := make([]Object, len(v)) - for i, val := range v { - res[i] = GetObjectFromInterFace(val) - } - - return &Array{Value: res} - } - - return NIL -} diff --git a/evaluator/objectArray.go b/evaluator/objectArray.go deleted file mode 100644 index 3728790..0000000 --- a/evaluator/objectArray.go +++ /dev/null @@ -1,217 +0,0 @@ -package evaluator - -import ( - "bytes" - "strings" - - "github.com/joetifa2003/windlang/ast" -) - -type Array struct { - Value []Object -} - -func (a *Array) GetFunction(name string) (*GoFunction, bool) { - return GetFunctionFromObject(name, a, arrayFunctions) -} -func (a *Array) Type() ObjectType { return ArrayObj } -func (a *Array) Inspect() string { - var out bytes.Buffer - - out.WriteString("[") - for _, obj := range a.Value { - out.WriteString(obj.Inspect()) - out.WriteString(",") - } - out.WriteString("]") - - return out.String() -} -func (a Array) Clone() Object { - return &a -} - -var arrayFunctions = map[string]OwnedFunction[*Array]{ - "len": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - return &Integer{ - Value: len(this.Value), - }, nil - }, - }, - "join": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - strArr := []string{} - for _, obj := range this.Value { - strArr = append(strArr, obj.Inspect()) - } - - return &String{ - Value: strings.Join(strArr, args[0].(*String).Value), - }, nil - }, - }, - "filter": { - ArgsCount: 1, - ArgsTypes: []ObjectType{FunctionObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - fn := args[0].(*Function) - - filtered := []Object{} - for _, obj := range this.Value { - result, err := evaluator.applyFunction(node, fn, []Object{obj}) - if err != nil { - return nil, err - } - - if result == TRUE { - filtered = append(filtered, obj) - } - } - - return &Array{ - Value: filtered, - }, nil - }, - }, - "map": { - ArgsCount: 1, - ArgsTypes: []ObjectType{FunctionObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - fn := args[0].(*Function) - - mapped := []Object{} - for _, obj := range this.Value { - result, err := evaluator.applyFunction(node, fn, []Object{obj}) - if err != nil { - return nil, err - } - - mapped = append(mapped, result) - } - - return &Array{ - Value: mapped, - }, nil - }, - }, - "reduce": { - ArgsCount: 2, - ArgsTypes: []ObjectType{FunctionObj, Any}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - fn := args[0].(*Function) - initial := args[1] - - accumulator := initial - for _, obj := range this.Value { - result, err := evaluator.applyFunction(node, fn, []Object{accumulator, obj}) - if err != nil { - return nil, err - } - - accumulator = result - } - - return accumulator, nil - }, - }, - "push": { - ArgsCount: 1, - ArgsTypes: []ObjectType{Any}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - this.Value = append(this.Value, args[0].Clone()) - return this, nil - }, - }, - "pop": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - last := this.Value[len(this.Value)-1] - this.Value = this.Value[:len(this.Value)-1] - return last, nil - }, - }, - "contains": { - ArgsCount: 1, - ArgsTypes: []ObjectType{FunctionObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - fn := args[0].(*Function) - - for _, obj := range this.Value { - result, err := evaluator.applyFunction(node, fn, []Object{obj}) - if err != nil { - return nil, err - } - - if result == TRUE { - return TRUE, nil - } - } - - return FALSE, nil - }, - }, - "count": { - ArgsCount: 1, - ArgsTypes: []ObjectType{FunctionObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - fn := args[0].(*Function) - - count := 0 - for _, obj := range this.Value { - result, err := evaluator.applyFunction(node, fn, []Object{obj}) - if err != nil { - return nil, err - } - - if result == TRUE { - count++ - } - } - - return &Integer{ - Value: int(count), - }, nil - }, - }, - "clone": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - newValue := make([]Object, len(this.Value)) - - copy(this.Value, newValue) - - return &Array{ - Value: newValue, - }, nil - }, - }, - "removeAt": { - ArgsCount: 1, - ArgsTypes: []ObjectType{IntegerObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *Array, args ...Object) (Object, *Error) { - index := args[0].(*Integer).Value - - if index < 0 || index >= len(this.Value) { - return nil, evaluator.newError(node.Token, "index %d out of bounds", index) - } - - newValue := []Object{} - for i, v := range this.Value { - if i != int(index) { - newValue = append(newValue, v) - } - } - removedValue := this.Value[index] - this.Value = newValue - - return removedValue, nil - }, - }, -} diff --git a/evaluator/objectString.go b/evaluator/objectString.go deleted file mode 100644 index a3d4062..0000000 --- a/evaluator/objectString.go +++ /dev/null @@ -1,214 +0,0 @@ -package evaluator - -import ( - "hash/fnv" - "strings" - - "github.com/joetifa2003/windlang/ast" -) - -type String struct { - Value string -} - -func (s *String) GetFunction(name string) (*GoFunction, bool) { - return GetFunctionFromObject(name, s, stringFunctions) -} -func (s *String) Type() ObjectType { return StringObj } -func (s *String) Inspect() string { return s.Value } -func (s *String) HashKey() HashKey { - algo := fnv.New64a() - algo.Write([]byte(s.Value)) - return HashKey{Type: s.Type(), Value: algo.Sum64(), InspectValue: s.Inspect()} -} -func (s String) Clone() Object { - return &s -} - -var stringFunctions = map[string]OwnedFunction[*String]{ - "len": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - return &Integer{ - Value: len(this.Value), - }, nil - }, - }, - "charAt": { - ArgsCount: 1, - ArgsTypes: []ObjectType{IntegerObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - index := args[0].(*Integer) - - if index.Value >= len(this.Value) { - return NIL, nil - } - - return &String{ - Value: string([]rune(this.Value)[index.Value]), - }, nil - }, - }, - "contains": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - substr := args[0].(*String) - - if strings.Contains(this.Value, substr.Value) { - return TRUE, nil - } else { - return FALSE, nil - } - }, - }, - "containsAny": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - substr := args[0].(*String) - - if strings.ContainsAny(this.Value, substr.Value) { - return TRUE, nil - } else { - return FALSE, nil - } - }, - }, - "count": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - substr := args[0].(*String) - - return &Integer{ - Value: strings.Count(this.Value, substr.Value), - }, nil - }, - }, - "replace": { - ArgsCount: 2, - ArgsTypes: []ObjectType{StringObj, StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - old := args[0].(*String) - new := args[1].(*String) - - return &String{ - Value: strings.Replace(this.Value, old.Value, new.Value, 1), - }, nil - }, - }, - "replaceN": { - ArgsCount: 3, - ArgsTypes: []ObjectType{StringObj, StringObj, IntegerObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - old := args[0].(*String) - new := args[1].(*String) - n := args[2].(*Integer) - - return &String{ - Value: strings.Replace(this.Value, old.Value, new.Value, int(n.Value)), - }, nil - }, - }, - "replaceAll": { - ArgsCount: 2, - ArgsTypes: []ObjectType{StringObj, StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - old := args[0].(*String) - new := args[1].(*String) - - return &String{ - Value: strings.ReplaceAll(this.Value, old.Value, new.Value), - }, nil - }, - }, - "toLowerCase": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - return &String{ - Value: strings.ToLower(this.Value), - }, nil - }, - }, - "toUpperCase": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - return &String{ - Value: strings.ToUpper(this.Value), - }, nil - }, - }, - "indexOf": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - substr := args[0].(*String) - - return &Integer{ - Value: strings.Index(this.Value, substr.Value), - }, nil - }, - }, - "lastIndexOf": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - substr := args[0].(*String) - - return &Integer{ - Value: strings.LastIndex(this.Value, substr.Value), - }, nil - }, - }, - "changeAt": { - ArgsCount: 2, - ArgsTypes: []ObjectType{IntegerObj, StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - index := args[0].(*Integer) - newValue := args[1].(*String) - - if index.Value >= len(this.Value) { - return nil, evaluator.newError(node.Token, "index out of range: got %d max %d", index.Value, len(this.Value)-1) - } - - if len(newValue.Value) > 1 { - return nil, evaluator.newError(node.Token, "new value can be at most one character") - } - - return &String{ - Value: this.Value[:index.Value] + newValue.Value + this.Value[index.Value+1:], - }, nil - }, - }, - "trim": { - ArgsCount: 0, - ArgsTypes: []ObjectType{}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - return &String{ - Value: strings.TrimSpace(this.Value), - }, nil - }, - }, - "split": { - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, this *String, args ...Object) (Object, *Error) { - seperator := args[0].(*String) - - strArr := strings.Split(this.Value, seperator.Value) - objArr := []Object{} - - for _, str := range strArr { - objArr = append(objArr, &String{Value: str}) - } - - return &Array{ - Value: objArr, - }, nil - }, - }, -} diff --git a/evaluator/stdLib.go b/evaluator/stdLib.go deleted file mode 100644 index 6ddc61b..0000000 --- a/evaluator/stdLib.go +++ /dev/null @@ -1,29 +0,0 @@ -package evaluator - -var initiatedLibraries map[string]*Environment - -func GetStdlib(filePath string) (*Environment, bool) { - switch filePath { - case "math": - return getLibrary("math", stdLibMath), true - - case "request": - return getLibrary("request", stdLibReq), true - } - - return nil, false -} - -func getLibrary(name string, initiator func() *Environment) *Environment { - lib, ok := initiatedLibraries[name] - if !ok { - lib = initiator() - initiatedLibraries[name] = lib - } - - return lib -} - -func init() { - initiatedLibraries = make(map[string]*Environment) -} diff --git a/evaluator/stdLibMath.go b/evaluator/stdLibMath.go deleted file mode 100644 index 99591c6..0000000 --- a/evaluator/stdLibMath.go +++ /dev/null @@ -1,23 +0,0 @@ -package evaluator - -import ( - "math" - - "github.com/joetifa2003/windlang/ast" -) - -func stdLibMath() *Environment { - return &Environment{ - Store: map[string]Object{ - "abs": &GoFunction{ - ArgsCount: 1, - ArgsTypes: []ObjectType{FloatObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - num := args[0].(*Float).Value - - return &Float{Value: math.Abs(num)}, nil - }, - }, - }, - } -} diff --git a/evaluator/stdLibRequest.go b/evaluator/stdLibRequest.go deleted file mode 100644 index dbe897e..0000000 --- a/evaluator/stdLibRequest.go +++ /dev/null @@ -1,53 +0,0 @@ -package evaluator - -import ( - "encoding/json" - "io/ioutil" - "net/http" - - "github.com/joetifa2003/windlang/ast" -) - -func stdLibReq() *Environment { - return &Environment{ - Store: map[string]Object{ - "get": &GoFunction{ - ArgsCount: 1, - ArgsTypes: []ObjectType{StringObj}, - Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - url := args[0].(*String) - - resp, err := http.Get(url.Value) - if err != nil { - return NIL, evaluator.newError(node.Token, "get request failed") - } - - respBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return NIL, evaluator.newError(node.Token, "get request failed") - } - - result := make(map[string]interface{}) - json.Unmarshal(respBytes, &result) - - objectResults := make(map[HashKey]Object) - for k, v := range result { - key := &String{Value: k} - - objectResults[key.HashKey()] = GetObjectFromInterFace(v) - } - - return &Hash{Pairs: objectResults}, nil - - }, - }, - // "post": &GoFunction{ - // ArgsCount: 1, - // ArgsTypes: []ObjectType{StringObj}, - // Fn: func(evaluator *Evaluator, node *ast.CallExpression, args ...Object) (Object, *Error) { - // http.Post() - // }, - // }, - }, - } -} diff --git a/go.mod b/go.mod index d73658e..e9713b9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/joetifa2003/windlang go 1.19 require ( + github.com/pkg/profile v1.7.0 github.com/spf13/cobra v1.4.0 github.com/stretchr/testify v1.8.0 ) @@ -12,7 +13,6 @@ require ( github.com/felixge/fgprof v0.9.3 // indirect github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/pkg/profile v1.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/opcode/opcode.go b/opcode/opcode.go index fc3424b..312c8b5 100644 --- a/opcode/opcode.go +++ b/opcode/opcode.go @@ -1,5 +1,10 @@ package opcode +import ( + "fmt" + "strings" +) + type OpCode int const ( @@ -11,16 +16,91 @@ const ( OP_MODULO OP_LESS OP_LESSEQ - OP_LET // args: [index] OP_EQ - OP_JUMP_FALSE // args: [offset] - OP_JUMP // args: [offset] - OP_BLOCK // args: [N of variables] - OP_END_BLOCK - OP_SET // args: [index, scope index] - OP_GET // args: [index, scope index] - OP_INC // args: [index, scope index] OP_POP OP_ECHO - OP_ARRAY // args: [n of elements] + OP_RET + + OP_LET // args: [frameOffset] + OP_JUMP_FALSE // args: [offset] + OP_JUMP // args: [offset] + OP_SET // args: [frameOffset] + OP_SET_GLOBAL // args: [index] + OP_GET // args: [frameOffset] + OP_GET_GLOBAL // args [index] + OP_INC // args: [frameOffset] + OP_INC_GLOBAL // args: [index] + OP_ARRAY // args: [n of elements] + OP_CALL // args: [n of args] ) + +type Instructions []OpCode + +func (instructions Instructions) String() string { + var out strings.Builder + ip := 0 + for ip < len(instructions) { + op := instructions[ip] + switch op { + case OP_CONST: + ip++ + idx := int(instructions[ip]) + out.WriteString(fmt.Sprintf("const %d", idx)) + case OP_ADD: + out.WriteString("add") + case OP_SUBTRACT: + out.WriteString("sub") + case OP_MULTIPLY: + out.WriteString("mul") + case OP_DIVIDE: + out.WriteString("div") + case OP_MODULO: + out.WriteString("mod") + case OP_LESS: + out.WriteString("less") + case OP_LESSEQ: + out.WriteString("lessq") + case OP_EQ: + out.WriteString("eq") + case OP_POP: + out.WriteString("pop") + case OP_ECHO: + out.WriteString("echo") + case OP_RET: + out.WriteString("ret") + case OP_LET: + ip++ + frameOffset := int(instructions[ip]) + out.WriteString(fmt.Sprintf("let %d", frameOffset)) + case OP_GET: + ip++ + frameOffset := int(instructions[ip]) + out.WriteString(fmt.Sprintf("get %d", frameOffset)) + case OP_JUMP: + ip++ + offset := int(instructions[ip]) + out.WriteString(fmt.Sprintf("jmp %d", offset)) + case OP_JUMP_FALSE: + ip++ + offset := int(instructions[ip]) + out.WriteString(fmt.Sprintf("jmpf %d", offset)) + case OP_SET: + ip++ + frameOffset := int(instructions[ip]) + out.WriteString(fmt.Sprintf("set %d", frameOffset)) + case OP_CALL: + out.WriteString("call") + + default: + panic(fmt.Sprintf("Unimplemented opcode %d", op)) + } + + ip++ + + if ip < len(instructions) { + out.WriteString("\n") + } + } + + return out.String() +} diff --git a/parser/parser.go b/parser/parser.go index 4afe6dd..f411706 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -77,7 +77,7 @@ func (p *Parser) getPrecedence(tokenType token.TokenType) int { return EQUALS case token.ASSIGN: return ASSIGN - case token.LT, token.GT: + case token.LT, token.GT, token.LT_EQ: return LessGreater case token.PLUS, token.MINUS: return SUM @@ -110,8 +110,6 @@ func (p *Parser) getPrefixParseFn(tokenType token.TokenType) prefixParseFn { return p.parseBoolean case token.LPAREN: return p.parseGroupedExpression - case token.IF: - return p.parseIfExpression case token.FUNCTION: return p.parseFunctionLiteral case token.STRING: @@ -165,10 +163,12 @@ func (p *Parser) parseStatement() ast.Statement { return p.parseIncludeStatement() case token.WHILE: return p.parseWhileStatement() - // case token.BREAK: - // return p.parseBreakStatement() - // case token.CONTINUE: - // return p.parseContinueStatement() + // case token.BREAK: + // return p.parseBreakStatement() + // case token.CONTINUE: + // return p.parseContinueStatement() + case token.IF: + return p.parseIfStatement() case token.ECHO: return p.parseEchoStatement() default: @@ -331,7 +331,9 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { leftExp := prefix() - for !p.currentTokenIs(token.SEMICOLON) && p.curPrecedence() >= precedence { + // if the current precedence is higher, then we take the left expression + // and do the higer level of percendence first + for !p.currentTokenIs(token.SEMICOLON) && p.curPrecedence() > precedence { infix := p.getInfixParseFn(p.curToken.Type) if infix == nil { return leftExp @@ -446,29 +448,28 @@ func (p *Parser) parseGroupedExpression() ast.Expression { return exp } -func (p *Parser) parseIfExpression() ast.Expression { - expression := ast.IfExpression{Token: p.curToken} +func (p *Parser) parseIfStatement() ast.Statement { + stmt := ast.IfStatement{Token: p.curToken} p.nextToken() p.expectCurrent(token.LPAREN) - expression.Condition = p.parseExpression(LOWEST) + stmt.Condition = p.parseExpression(LOWEST) p.expectCurrent(token.RPAREN) thenStatement := p.parseStatement() - expression.ThenBranch = thenStatement + stmt.ThenBranch = thenStatement if p.currentTokenIs(token.ELSE) { p.nextToken() elseStatement := p.parseStatement() - expression.ElseBranch = elseStatement + stmt.ElseBranch = elseStatement } - return &expression - + return &stmt } func (p *Parser) parseFunctionLiteral() ast.Expression { diff --git a/value/value.go b/value/value.go index acfd2f4..9d83345 100644 --- a/value/value.go +++ b/value/value.go @@ -3,16 +3,19 @@ package value import ( "fmt" "unsafe" + + "github.com/joetifa2003/windlang/opcode" ) type ValueType int const ( - VALUE_INT ValueType = iota + VALUE_NIL ValueType = iota + VALUE_INT VALUE_BOOL - VALUE_NIL VALUE_ARRAY VALUE_OBJECT + VALUE_FN ) type Value struct { @@ -23,6 +26,12 @@ type Value struct { type NonPrimitiveData struct { ArrayV []Value + FnV FnValue +} + +type FnValue struct { + Instructions []opcode.OpCode + VarCount int } func NewNilValue() Value { @@ -62,10 +71,26 @@ func NewArrayValue(v []Value) Value { } } +func NewFnValue(instructions []opcode.OpCode, varCount int) Value { + return Value{ + VType: VALUE_FN, + nonPrimitive: &NonPrimitiveData{ + FnV: FnValue{ + Instructions: instructions, + VarCount: varCount, + }, + }, + } +} + func (v *Value) GetArray() []Value { return v.nonPrimitive.ArrayV } +func (v *Value) GetFn() FnValue { + return v.nonPrimitive.FnV +} + func (v *Value) GetInt() int { return *(*int)(unsafe.Pointer(&v.primitiveData[0])) } diff --git a/vm/env.go b/vm/env.go deleted file mode 100644 index 35f370c..0000000 --- a/vm/env.go +++ /dev/null @@ -1,71 +0,0 @@ -package vm - -import ( - "github.com/joetifa2003/windlang/value" -) - -type Environment struct { - Store []value.Value -} - -func NewEnvironment(varCount int) Environment { - store := make([]value.Value, varCount) - return Environment{ - Store: store, - } -} - -type EnvironmentStack struct { - Value []Environment - p int -} - -func NewEnvironmentStack() EnvironmentStack { - return EnvironmentStack{ - Value: make([]Environment, 2048), - } -} - -func (s *EnvironmentStack) pop() Environment { - lastEle := (s.Value)[s.p-1] - (s.Value)[s.p-1] = Environment{Store: nil} - s.p-- - return lastEle -} - -func (s *EnvironmentStack) push(env Environment) { - s.Value[s.p] = env - s.p++ -} - -func (s *EnvironmentStack) let(index int, val value.Value) { - env := &s.Value[s.p-1] - - env.Store[index] = val -} - -func (s *EnvironmentStack) get(scopeIndex, index int) value.Value { - env := &s.Value[scopeIndex] - - return env.Store[index] -} - -func (s *EnvironmentStack) set(scopeIndex, index int, value value.Value) value.Value { - env := &s.Value[scopeIndex] - env.Store[index] = value - - return env.Store[index] -} - -func (s *EnvironmentStack) increment(scopeIndex, index int) (ok bool) { - env := &s.Value[scopeIndex] - val := &env.Store[index] - if val.VType != value.VALUE_INT { - return false - } - - ptr := val.GetIntPtr() - *ptr++ - - return true -} diff --git a/vm/stack.go b/vm/stack.go index a079b34..98e8c5f 100644 --- a/vm/stack.go +++ b/vm/stack.go @@ -23,3 +23,11 @@ func (s *Stack) pop() value.Value { func (s *Stack) push(value value.Value) { s.Value = append(s.Value, value) } + +func (s *Stack) update(value value.Value) { + s.Value[len(s.Value)-1] = value +} + +func (s *Stack) peek() value.Value { + return s.Value[len(s.Value)-1] +} diff --git a/vm/vm.go b/vm/vm.go index 3d2f651..1f098d4 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -7,43 +7,70 @@ import ( "github.com/joetifa2003/windlang/value" ) +type Frame struct { + Instructions []opcode.OpCode + NumOfLocals int + + ip int +} + type VM struct { Stack Stack - EnvStack EnvironmentStack Constants []value.Value + Frames []Frame } -func NewVM(constants []value.Value) VM { - stack := NewStack() - envStack := NewEnvironmentStack() - - return VM{ - Stack: stack, - EnvStack: envStack, +func NewVM(constants []value.Value, mainFrame Frame) VM { + vm := VM{ + Stack: NewStack(), Constants: constants, + Frames: []Frame{mainFrame}, + } + vm.initCurFrame() + return vm +} + +func (v *VM) curFrame() *Frame { + return &v.Frames[len(v.Frames)-1] +} + +func (v *VM) pushFrame(f Frame) { + v.Frames = append(v.Frames, f) +} + +func (v *VM) popFrame() { + v.Frames = v.Frames[:len(v.Frames)-1] +} + +// initCurFrame initializes the stack to hold local variables +func (v *VM) initCurFrame() { + for i := 0; i < v.curFrame().NumOfLocals; i++ { + v.Stack.push(value.NewNilValue()) } } -func (v *VM) Interpret(instructions []opcode.OpCode) { - ip := 0 - for ip < len(instructions) { - switch instructions[ip] { +func (v *VM) Interpret() { + for v.curFrame().ip < len(v.curFrame().Instructions) { + instructions := v.curFrame().Instructions + ip := &v.curFrame().ip + + switch instructions[*ip] { case opcode.OP_CONST: - ip++ - value := v.Constants[instructions[ip]] + *ip++ + value := v.Constants[instructions[*ip]] v.Stack.push(value) case opcode.OP_ADD: right := v.Stack.pop() - left := v.Stack.pop() + left := v.Stack.peek() switch { case left.VType == value.VALUE_INT && right.VType == value.VALUE_INT: leftNumber := left.GetInt() rightNumber := right.GetInt() - v.Stack.push(value.NewIntValue(leftNumber + rightNumber)) + v.Stack.update(value.NewIntValue(leftNumber + rightNumber)) } case opcode.OP_SUBTRACT: @@ -120,64 +147,48 @@ func (v *VM) Interpret(instructions []opcode.OpCode) { case opcode.OP_JUMP_FALSE: operand := v.Stack.pop() - ip++ - offset := int(instructions[ip]) + *ip++ + offset := int(instructions[*ip]) if !isTruthy(operand) { - ip += offset + *ip += offset continue } case opcode.OP_JUMP: - ip++ - offset := int(instructions[ip]) + *ip++ + offset := int(instructions[*ip]) - ip += offset + *ip += offset continue - case opcode.OP_BLOCK: - ip++ - varCount := int(instructions[ip]) - - v.EnvStack.push(NewEnvironment(varCount)) - - case opcode.OP_END_BLOCK: - v.EnvStack.pop() - case opcode.OP_LET: value := v.Stack.pop() - - ip++ - index := int(instructions[ip]) - - v.EnvStack.let(index, value) + *ip++ + offset := int(instructions[*ip]) + v.Stack.Value[offset] = value case opcode.OP_SET: - value := v.Stack.pop() - - ip++ - index := int(instructions[ip]) - ip++ - scopeIndex := int(instructions[ip]) - - newVal := v.EnvStack.set(scopeIndex, index, value) - v.Stack.push(newVal) + value := v.Stack.peek() + *ip++ + offset := int(instructions[*ip]) + v.Stack.Value[offset] = value case opcode.OP_GET: - ip++ - index := int(instructions[ip]) - ip++ - scopeIndex := int(instructions[ip]) + *ip++ + offset := int(instructions[*ip]) + v.Stack.push(v.Stack.Value[offset]) - value := v.EnvStack.get(scopeIndex, index) - v.Stack.push(value) + case opcode.OP_GET_GLOBAL: + panic("Unimplemented") + + case opcode.OP_SET_GLOBAL: + panic("Unimplemented") case opcode.OP_POP: - if len(v.Stack.Value) != 0 { - v.Stack.pop() - } + v.Stack.pop() case opcode.OP_ECHO: operand := v.Stack.pop() @@ -185,8 +196,8 @@ func (v *VM) Interpret(instructions []opcode.OpCode) { fmt.Println(operand.String()) case opcode.OP_ARRAY: - ip++ - n := int(instructions[ip]) + *ip++ + n := int(instructions[*ip]) values := make([]value.Value, n) for i := 0; i < n; i++ { @@ -196,21 +207,24 @@ func (v *VM) Interpret(instructions []opcode.OpCode) { v.Stack.push(value.NewArrayValue(values)) case opcode.OP_INC: - ip++ - index := int(instructions[ip]) - ip++ - scopeIndex := int(instructions[ip]) - - ok := v.EnvStack.increment(scopeIndex, index) - if !ok { - panic("") + // ip++ + // index := int(instructions[ip]) + // ip++ + // scopeIndex := int(instructions[ip]) + + case opcode.OP_CALL: + f := v.Stack.pop() + if f.VType == value.VALUE_FN { + fn := f.GetFn() + v.pushFrame(Frame{Instructions: fn.Instructions, NumOfLocals: fn.VarCount}) } + continue default: - panic("Unimplemented OpCode " + fmt.Sprint(instructions[ip])) + panic("Unimplemented OpCode " + fmt.Sprint(instructions[*ip])) } - ip++ + *ip++ } }