From 6ad0168eaf1cd1ca865de58d4f30ff158171376a Mon Sep 17 00:00:00 2001 From: overflowerror Date: Fri, 2 Feb 2024 22:01:37 +0100 Subject: [PATCH] initial commit I wrote this a few year back (according to the mtime of the files Sep 12th 2021) --- cmd/main.go | 38 ++++ internal/analysis/static.go | 293 +++++++++++++++++++++++++++ internal/ast/ast.go | 98 +++++++++ internal/ast/operators.go | 19 ++ internal/ast/types.go | 15 ++ internal/parser/elements.go | 141 +++++++++++++ internal/parser/parser.go | 301 +++++++++++++++++++++++++++ internal/scope/scope.go | 86 ++++++++ internal/values/values.go | 36 ++++ internal/vm/exprs.go | 393 ++++++++++++++++++++++++++++++++++++ internal/vm/functions.go | 28 +++ internal/vm/statements.go | 111 ++++++++++ internal/vm/vm.go | 11 + test.xml | 51 +++++ 14 files changed, 1621 insertions(+) create mode 100644 cmd/main.go create mode 100644 internal/analysis/static.go create mode 100644 internal/ast/ast.go create mode 100644 internal/ast/operators.go create mode 100644 internal/ast/types.go create mode 100644 internal/parser/elements.go create mode 100644 internal/parser/parser.go create mode 100644 internal/scope/scope.go create mode 100644 internal/values/values.go create mode 100644 internal/vm/exprs.go create mode 100644 internal/vm/functions.go create mode 100644 internal/vm/statements.go create mode 100644 internal/vm/vm.go create mode 100644 test.xml diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..73ac54e --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + "io" + "os" + "xml-programming/internal/analysis" + "xml-programming/internal/parser" + "xml-programming/internal/vm" +) + +func main() { + flag.Parse() + filename := flag.Arg(0) + file, err := os.Open(filename) + if err != nil { + panic(err) + } + content, err := io.ReadAll(file) + if err != nil { + panic(err) + } + + program, err := parser.Parse(content) + if err != nil { + panic(err) + } + + //json, _ := json.MarshalIndent(program, "", " ") + //fmt.Println(string(json)) + + err = analysis.StaticAnalysis(program) + if err != nil { + panic(err) + } + + vm.Run(program) +} \ No newline at end of file diff --git a/internal/analysis/static.go b/internal/analysis/static.go new file mode 100644 index 0000000..51d39f0 --- /dev/null +++ b/internal/analysis/static.go @@ -0,0 +1,293 @@ +package analysis + +import ( + "fmt" + "xml-programming/internal/ast" + "xml-programming/internal/scope" + "xml-programming/internal/values" +) + +func analyseArithmetic(types []ast.Type) (ast.Type, error) { + isFloat := false + for _, _type := range types { + if !_type.IsNumber() { + return ast.Void, fmt.Errorf("can't do arithmetic on non-number types") + } + if _type == ast.Float { + isFloat = true + } + } + if isFloat { + return ast.Float, nil + } else { + return ast.Int, nil + } +} + +func analyseComparison(types []ast.Type, operator ast.Operator) (ast.Type, error) { + if operator != ast.Equal { + for _, _type := range types { + if !_type.IsNumber() { + return ast.Void, fmt.Errorf("can not compare non-number types") + } + } + } + return ast.Bool, nil +} + +func analyseLogic(types []ast.Type) (ast.Type, error) { + for _, _type := range types { + if _type != ast.Bool { + return ast.Void, fmt.Errorf("can only do logic on bools") + } + } + return ast.Bool, nil +} + +func analyseExpression(expression ast.Expression, localScope *scope.Scope) (ast.Type, error) { + switch v := expression.(type) { + case ast.LiteralExpression: + return v.Type, nil + case ast.VariableExpression: + variable := localScope.GetVariable(v.Name) + if variable == nil { + return ast.Void, fmt.Errorf("name %s not found in local scope", v.Name) + } + return variable.Type, nil + case ast.FunctionCall: + function := localScope.GetFunction(v.Name) + if function == nil { + return ast.Void, fmt.Errorf("name %s not found in local scope", v.Name) + } + if len(function.Args) != len(v.Args) { + return ast.Void, fmt.Errorf("mismatched number of arguments for name %s", v.Name) + } + for i, _ := range function.Args { + expectedType := function.Args[i] + actualType, err := analyseExpression(v.Args[i], localScope) + if err != nil { + return ast.Void, err + } + if expectedType.Type != actualType { + return ast.Void, fmt.Errorf("mismatched types for argument %d of name %s", i + 1, v.Name) + } + } + + return function.Return, nil + case ast.OperatorExpression: + types, err := analyseExpressionist(v.Exprs, localScope) + if err != nil { + return ast.Void, err + } + + if len(types) < 1 { + return ast.Void, fmt.Errorf("operator with no arguments is not valid") + } + + switch v.Operator { + case ast.Add: + return analyseArithmetic(types) + case ast.Sub: + if len(types) != 2 { + return ast.Void, fmt.Errorf("sub is binary") + } + return analyseArithmetic(types) + case ast.Mul: + return analyseArithmetic(types) + case ast.Div: + if len(types) != 2 { + return ast.Void, fmt.Errorf("div is binary") + } + return analyseArithmetic(types) + case ast.Mod: + if len(types) != 2 { + return ast.Void, fmt.Errorf("mod is binary") + } + if types[0] == ast.Float || types[1] == ast.Float { + return ast.Void, fmt.Errorf("mod only supports int") + } + return ast.Int, nil + case ast.Concat: + return ast.String, nil + case ast.Equal: + if len(types) != 2 { + return ast.Void, fmt.Errorf("equal is binary") + } + return analyseComparison(types, v.Operator) + case ast.LessThan: + if len(types) != 2 { + return ast.Void, fmt.Errorf("less than is binary") + } + return analyseComparison(types, v.Operator) + case ast.GreaterThan: + if len(types) != 2 { + return ast.Void, fmt.Errorf("greater than is binary") + } + return analyseComparison(types, v.Operator) + case ast.Not: + if len(types) != 1 { + return ast.Void, fmt.Errorf("not is unary") + } + return analyseLogic(types) + case ast.And: + return analyseLogic(types) + case ast.Or: + return analyseLogic(types) + default: + return ast.Void, fmt.Errorf("not yet implemented") + } + default: + return ast.Void, fmt.Errorf("not yet implemented") + } +} + +func analyseExpressionist(expressions []ast.Expression, localScope *scope.Scope) ([]ast.Type, error) { + var types []ast.Type + for _, expr := range expressions { + _type, err := analyseExpression(expr, localScope) + if err != nil { + return nil, err + } + types = append(types, _type) + } + return types, nil +} + +func analyseStatement(statement ast.Statement, localScope *scope.Scope, currentFunction *scope.Function) error { + switch v := statement.(type) { + case ast.OutputStatement: + _, err := analyseExpressionist(v.Exprs, localScope) + if err != nil { + return err + } + case ast.VariableDeclarationStatement: + if localScope.CurrentScopeHas(v.Name) { + return fmt.Errorf("name %s already exists in local scope", v.Name) + } + localScope.AddVariable(scope.Variable{ + Name: v.Name, + Value: values.Value{ + Type: v.Type, + }, + }) + case ast.VariableAssignmentStatement: + variable := localScope.GetVariable(v.Name) + if variable == nil { + return fmt.Errorf("name %s not found in local scope", v.Name) + } + _type, err := analyseExpression(v.Expr, localScope) + if err != nil { + return err + } + if _type != variable.Type { + return fmt.Errorf("type mismatch on assignment") + } + case ast.FunctionStatement: + if localScope.CurrentScopeHas(v.Name) { + return fmt.Errorf("name %s already exists in local scope", v.Name) + } + function := scope.Function{ + Name: v.Name, + Args: v.Args, + Return: v.Returns, + } + localScope.AddFunction(function) + + functionScope := scope.FromParent(localScope) + for _, arg := range v.Args { + functionScope.AddVariable(scope.Variable{ + Name: arg.Name, + Value: values.Value{ + Type: arg.Type, + }, + }) + } + + err := analyseStatements(v.Body, functionScope, &function) + if err != nil { + return err + } + case ast.FunctionReturnStatement: + if currentFunction == nil { + return fmt.Errorf("can not return outside of function") + } + + _type, err := analyseExpression(v.Expr, localScope) + if err != nil { + return err + } + if _type != currentFunction.Return { + return fmt.Errorf("return type mismatch in name %v", currentFunction.Name) + } + case ast.ConditionalStatement: + for _, _if := range v.Ifs { + _type, err := analyseExpression(_if.Expr, localScope) + if err != nil { + return err + } + if _type != ast.Bool { + return fmt.Errorf("condition type has to be bool") + } + + err = analyseStatements(_if.Then, localScope, currentFunction) + if err != nil { + return err + } + } + if v.Else != nil { + err := analyseStatements(v.Else, localScope, currentFunction) + if err != nil { + return err + } + } + case ast.LoopStatement: + _type, err := analyseExpression(v.LoopCondition, localScope) + if err != nil { + return err + } + if _type != ast.Bool { + return fmt.Errorf("condition type has to be bool") + } + + err = analyseStatements(v.Body, localScope, currentFunction) + if err != nil { + return err + } + case ast.ForStatement: + if localScope.CurrentScopeHas(v.Name) { + return fmt.Errorf("name %s already in local scope", v.Name) + } + localScope.AddVariable(scope.Variable{ + Name: v.Name, + Value: values.Value{ + Type: ast.Int, + }, + }) + + err := analyseStatements(v.Body, localScope, currentFunction) + if err != nil { + return err + } + default: + return fmt.Errorf("not yet implemented") + } + + return nil +} + +func analyseStatements(statements []ast.Statement, scope *scope.Scope, currentFunction *scope.Function) error { + for _, statement := range statements { + err := analyseStatement(statement, scope, currentFunction) + if err != nil { + return err + } + } + + return nil +} + +func StaticAnalysis(program *ast.Program) error { + scope := scope.New() + + return analyseStatements(program.Statements, scope, nil) +} \ No newline at end of file diff --git a/internal/ast/ast.go b/internal/ast/ast.go new file mode 100644 index 0000000..9e0079d --- /dev/null +++ b/internal/ast/ast.go @@ -0,0 +1,98 @@ +package ast + +type Program struct { + Statements []Statement +} + +type Statement interface { +} + +type OutputStatement struct { + Exprs []Expression +} +var _ Statement = OutputStatement{} + +type VariableDeclarationStatement struct { + Name string + Type Type +} +var _ Statement = VariableDeclarationStatement{} + +type VariableAssignmentStatement struct { + Name string + Expr Expression +} +var _ Statement = VariableAssignmentStatement{} + +type FunctionStatement struct { + Name string + Returns Type + Args []FunctionArg + Body []Statement +} +var _ Statement = FunctionStatement{} + +type FunctionArg struct { + Name string + Type Type +} + +type FunctionReturnStatement struct { + Expr Expression +} +var _ Statement = FunctionReturnStatement{} + +type ConditionalStatement struct { + Ifs []ConditionIf + Else []Statement +} +var _ Statement = ConditionalStatement{} + +type ConditionIf struct { + Expr Expression + Then []Statement +} + +type LoopStatement struct { + LoopCondition Expression + Body []Statement +} +var _ Statement = LoopStatement{} + +type ForStatement struct { + Name string + From int + To int + Body []Statement +} +var _ Statement = ForStatement{} + +type Expression interface { +} + +type LiteralExpression struct { + Type Type + String string + Bool bool + Int int + Float float32 +} +var _ Expression = LiteralExpression{} + +type VariableExpression struct { + Name string +} +var _ Expression = VariableExpression{} + +type OperatorExpression struct { + Operator Operator + Exprs []Expression +} +var _ Expression = OperatorExpression{} + +type FunctionCall struct { + Name string + Args []Expression +} +var _ Expression = FunctionCall{} +var _ Statement = FunctionCall{} diff --git a/internal/ast/operators.go b/internal/ast/operators.go new file mode 100644 index 0000000..f2be8c5 --- /dev/null +++ b/internal/ast/operators.go @@ -0,0 +1,19 @@ +package ast + +type Operator int + +const ( + Add Operator = iota + Sub + Mul + Div + Mod + Concat + + Equal + LessThan + GreaterThan + Not + Or + And +) \ No newline at end of file diff --git a/internal/ast/types.go b/internal/ast/types.go new file mode 100644 index 0000000..8febd2d --- /dev/null +++ b/internal/ast/types.go @@ -0,0 +1,15 @@ +package ast + +type Type int + +const ( + Void Type = iota + String + Bool + Int + Float +) + +func (t Type) IsNumber() bool { + return t == Int || t == Float +} \ No newline at end of file diff --git a/internal/parser/elements.go b/internal/parser/elements.go new file mode 100644 index 0000000..7cf3820 --- /dev/null +++ b/internal/parser/elements.go @@ -0,0 +1,141 @@ +package parser + +import "encoding/xml" + +type ProgramElement struct { + XMLName xml.Name `xml:"program"` + Statements []StatementElement `xml:",any"` +} + +type StatementElement struct { + XMLName xml.Name + + OutputStatementElement + VariableDeclarationElement + VariableAssignmentElement + FunctionElement + ConditionStatementElement + LoopStatementElement + ForStatementElement + + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + + Exprs []ExpressionElement `xml:",any"` + + Body StatementBody +} + +const OutputStatementElementName = "output" +type OutputStatementElement struct { +} + +const VariableDeclarationElementName = "declare" +type VariableDeclarationElement struct { +} + +const VariableAssignmentElementName = "assign" +type VariableAssignmentElement struct { +} + +const LiteralExpressionStringElementName = "string" +const LiteralExpressionBoolElementName = "bool" +const LiteralExpressionIntElementName = "int" +const LiteralExpressionFloatElementName = "float" +const VariableExpressionElementName = "var" +const OperatorExpressionAddElementName = "add" +const OperatorExpressionSubElementName = "sub" +const OperatorExpressionMulElementName = "mul" +const OperatorExpressionDivElementName = "div" +const OperatorExpressionModElementName = "mod" +const OperatorExpressionConcatElementName = "concat" +const OperatorExpressionEqualElementName = "equal" +const OperatorExpressionGreaterThanElementName = "gt" +const OperatorExpressionLessThanElementName = "lt" +const OperatorExpressionNotElementName = "not" +const OperatorExpressionAndElementName = "and" +const OperatorExpressionOrElementName = "or" +const FunctionCallExpressionElementName = "call" +type ExpressionElement struct { + XMLName xml.Name + + Name string `xml:"name,attr"` + + Content string `xml:",chardata"` + Exprs []ExpressionElement `xml:",any"` +} + +const FunctionElementName = "func" +type FunctionElement struct { + Args FunctionArgs `xml:"args"` +} + +type FunctionArgs struct { + Returns FunctionReturnType `xml:"returns"` + Args []FunctionArg `xml:"arg"` +} + +type FunctionArg struct { + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` +} + +type FunctionReturnType struct { + Type string `xml:"type,attr"` +} + +type StatementBody struct { + XMLName xml.Name `xml:"body"` + Statements []StatementElement `xml:",any"` +} + +const FunctionReturnElementName = "return" +type FunctionReturnElement struct { +} + +const FunctionCallStatementElementName = "call" +type FunctionCallStatementElement struct { +} + +const ConditionStatementElementName = "switch" +type ConditionStatementElement struct { + Ifs []ConditionStatementIfClauseElement `xml:"if"` + Else *ConditionStatementIfClauseElement `xml:"else"` +} + +type ConditionStatementIfClauseElement struct { + Condition ConditionStatementConditionElement `xml:"cond"` + Then ConditionStatementThenElement +} + +type ConditionStatementElseClauseElement struct { + Then ConditionStatementThenElement +} + +type ConditionStatementConditionElement struct { + Expr ExpressionElement `xml:",any"` +} + +type ConditionStatementThenElement struct { + XMLName xml.Name `xml:"then"` + Statements []StatementElement `xml:",any"` +} + +const LoopStatementElementName = "loop" +type LoopStatementElement struct { + Condition LoopConditionElement `xml:"cond"` +} + +type LoopConditionElement struct { + Expr ExpressionElement `xml:",any"` +} + +type LoopBodyElement struct { + Statements []StatementElement `xml:",any"` +} + +const ForStatementElementName = "for" +type ForStatementElement struct { + To int `xml:"to,attr"` + From int `xml:"from,attr"` +} \ No newline at end of file diff --git a/internal/parser/parser.go b/internal/parser/parser.go new file mode 100644 index 0000000..cfc2f4d --- /dev/null +++ b/internal/parser/parser.go @@ -0,0 +1,301 @@ +package parser + +import ( + "encoding/xml" + "errors" + "fmt" + "strconv" + "strings" + "xml-programming/internal/ast" +) + +func Parse(content []byte) (*ast.Program, error) { + var programElement ProgramElement + err := xml.Unmarshal(content, &programElement) + if err != nil { + return nil, err + } + + //json, _ := json.MarshalIndent(programElement, "", " ") + //fmt.Println(string(json)) + + var program ast.Program + program.Statements, err = ParseStatements(programElement.Statements) + if err != nil { + return nil, err + } + + return &program, nil +} + +func ParseStatements(statementsElements []StatementElement) ([]ast.Statement, error) { + var statements []ast.Statement + for _, element := range statementsElements { + statement, err := ParseStatement(element) + if err != nil { + return nil, err + } + statements = append(statements, statement) + } + + return statements, nil +} + +func ParseType(str string) (ast.Type, error) { + switch str { + case "string": + return ast.String, nil + case "bool": + return ast.Bool, nil + case "int": + return ast.Int, nil + case "float": + return ast.Float, nil + default: + return ast.Void, fmt.Errorf("unknown type: %v", str) + } +} + +func ParseStatement(statement StatementElement) (ast.Statement, error) { + switch statement.XMLName.Local { + case OutputStatementElementName: + var exprs []ast.Expression + for _, exprElement := range statement.Exprs { + expr, err := ParseExpression(exprElement) + if err != nil { + return nil, err + } + exprs = append(exprs, expr) + } + return ast.OutputStatement{ + Exprs: exprs, + }, nil + case VariableDeclarationElementName: + t, err := ParseType(statement.Type) + if err != nil { + return nil, err + } + return ast.VariableDeclarationStatement{ + Name: statement.Name, + Type: t, + }, nil + case VariableAssignmentElementName: + if len(statement.Exprs) != 1 { + return nil, errors.New("variable assignments must have exactly one expression") + } + expr, err := ParseExpression(statement.Exprs[0]) + if err != nil { + return nil, err + } + return ast.VariableAssignmentStatement{ + Name: statement.Name, + Expr: expr, + }, nil + case FunctionElementName: + var err error + var args []ast.FunctionArg + for _, argElement := range statement.Args.Args { + arg := ast.FunctionArg{ + Name: argElement.Name, + } + arg.Type, err = ParseType(argElement.Type) + if err != nil { + return nil, fmt.Errorf("couldn't parse function argument list: %w", err) + } + args = append(args, arg) + } + returns, err := ParseType(statement.Args.Returns.Type) + if err != nil { + return nil, fmt.Errorf("couldn't parse function return type: %w", err) + } + var body []ast.Statement + for _, statementElement := range statement.Body.Statements { + bodyStatement, err := ParseStatement(statementElement) + if err != nil { + return nil, err + } + body = append(body, bodyStatement) + } + return ast.FunctionStatement{ + Name: statement.Name, + Returns: returns, + Args: args, + Body: body, + }, nil + case FunctionReturnElementName: + if len(statement.Exprs) != 1 { + return nil, errors.New("a return statement must have exactly one expression") + } + expr, err := ParseExpression(statement.Exprs[0]) + if err != nil { + return nil, err + } + return ast.FunctionReturnStatement{ + Expr: expr, + }, nil + case FunctionCallStatementElementName: + exprs, err := ParseExpressionList(statement.Exprs) + if err != nil { + return nil, err + } + return ast.FunctionCall{ + Name: statement.Name, + Args: exprs, + }, nil + case ConditionStatementElementName: + conditional := ast.ConditionalStatement{} + + for _, _if := range statement.Ifs { + then, err := ParseStatements(_if.Then.Statements) + if err != nil { + return nil, err + } + expr, err := ParseExpression(_if.Condition.Expr) + if err != nil { + return nil, err + } + conditional.Ifs = append(conditional.Ifs, ast.ConditionIf{ + Expr: expr, + Then: then, + }) + } + + if statement.Else != nil { + then, err := ParseStatements(statement.Else.Then.Statements) + if err != nil { + return nil, err + } + conditional.Else = then + } + + return conditional, nil + case LoopStatementElementName: + expr, err := ParseExpression(statement.LoopStatementElement.Condition.Expr) + if err != nil { + return nil, err + } + body, err := ParseStatements(statement.Body.Statements) + if err != nil { + return nil, err + } + return ast.LoopStatement{ + LoopCondition: expr, + Body: body, + }, nil + case ForStatementElementName: + body, err := ParseStatements(statement.Body.Statements) + if err != nil { + return nil, err + } + return ast.ForStatement{ + From: statement.From, + To: statement.To, + Name: statement.Name, + Body: body, + }, nil + default: + return nil, fmt.Errorf("unknown statement: <%v>", statement.XMLName.Local) + } +} + +func ParseExpressionList(exprList []ExpressionElement) ([]ast.Expression, error) { + exprs := make([]ast.Expression, len(exprList)) + + var err error + for i, expr := range exprList { + exprs[i], err = ParseExpression(expr) + if err != nil { + return nil, fmt.Errorf("unable to parse sub expression: %w", err) + } + } + + return exprs, nil +} + +func ParseOperatorExpression(exprElement ExpressionElement, operator ast.Operator) (ast.Expression, error) { + exprs, err := ParseExpressionList(exprElement.Exprs) + if err != nil { + return nil, err + } + + return ast.OperatorExpression{ + Operator: operator, + Exprs: exprs, + }, nil +} + +func ParseExpression(exprElement ExpressionElement) (ast.Expression, error) { + switch exprElement.XMLName.Local { + case LiteralExpressionStringElementName: + return ast.LiteralExpression{ + Type: ast.String, + String: exprElement.Content, + }, nil + case LiteralExpressionBoolElementName: + val, err := strconv.ParseBool(strings.TrimSpace(exprElement.Content)) + if err != nil { + return nil, fmt.Errorf("unable to parse int value: %w", err) + } + return ast.LiteralExpression{ + Type: ast.Bool, + Bool: val, + }, nil + case LiteralExpressionIntElementName: + val, err := strconv.Atoi(strings.TrimSpace(exprElement.Content)) + if err != nil { + return nil, fmt.Errorf("unable to parse int value: %w", err) + } + return ast.LiteralExpression{ + Type: ast.Int, + Int: val, + }, nil + case LiteralExpressionFloatElementName: + val, err := strconv.ParseFloat(strings.TrimSpace(exprElement.Content), 32) + if err != nil { + return nil, fmt.Errorf("unable to parse float value: %w", err) + } + return ast.LiteralExpression{ + Type: ast.Float, + Float: float32(val), + }, nil + case VariableExpressionElementName: + return ast.VariableExpression{ + Name: exprElement.Name, + }, nil + case OperatorExpressionAddElementName: + return ParseOperatorExpression(exprElement, ast.Add) + case OperatorExpressionSubElementName: + return ParseOperatorExpression(exprElement, ast.Sub) + case OperatorExpressionMulElementName: + return ParseOperatorExpression(exprElement, ast.Mul) + case OperatorExpressionDivElementName: + return ParseOperatorExpression(exprElement, ast.Div) + case OperatorExpressionModElementName: + return ParseOperatorExpression(exprElement, ast.Mod) + case OperatorExpressionConcatElementName: + return ParseOperatorExpression(exprElement, ast.Concat) + case OperatorExpressionEqualElementName: + return ParseOperatorExpression(exprElement, ast.Equal) + case OperatorExpressionGreaterThanElementName: + return ParseOperatorExpression(exprElement, ast.GreaterThan) + case OperatorExpressionLessThanElementName: + return ParseOperatorExpression(exprElement, ast.LessThan) + case OperatorExpressionNotElementName: + return ParseOperatorExpression(exprElement, ast.Not) + case OperatorExpressionAndElementName: + return ParseOperatorExpression(exprElement, ast.And) + case OperatorExpressionOrElementName: + return ParseOperatorExpression(exprElement, ast.Or) + case FunctionCallExpressionElementName: + exprs, err := ParseExpressionList(exprElement.Exprs) + if err != nil { + return nil, err + } + return ast.FunctionCall{ + Name: exprElement.Name, + Args: exprs, + }, nil + default: + return nil, errors.New("unknown expression type") + } +} \ No newline at end of file diff --git a/internal/scope/scope.go b/internal/scope/scope.go new file mode 100644 index 0000000..8a3a54d --- /dev/null +++ b/internal/scope/scope.go @@ -0,0 +1,86 @@ +package scope + +import ( + "xml-programming/internal/ast" + "xml-programming/internal/values" +) + +type Function struct { + Name string + Args []ast.FunctionArg + Return ast.Type + Body []ast.Statement +} + +type Variable struct { + Name string + values.Value +} + +type Scope struct { + functions []Function + variables []Variable + + parentScope *Scope +} + +func (s *Scope) CurrentScopeHas(name string) bool { + for _, f := range s.functions { + if f.Name == name { + return true + } + } + + for _, v := range s.variables { + if v.Name == name { + return true + } + } + + return false +} + +func (s *Scope) GetFunction(name string) *Function { + for _, f := range s.functions { + if f.Name == name { + return &f + } + } + + if s.parentScope != nil { + return s.parentScope.GetFunction(name) + } else { + return nil + } +} + +func (s *Scope) GetVariable(name string) *Variable { + for i, v := range s.variables { + if v.Name == name { + return &s.variables[i] + } + } + + if s.parentScope != nil { + return s.parentScope.GetVariable(name) + } else { + return nil + } +} + +func (s *Scope) AddVariable(variable Variable) { + s.variables = append(s.variables, variable) +} + +func (s *Scope) AddFunction(function Function) { + s.functions = append(s.functions, function) +} + +func New() *Scope { + return &Scope{} +} + +func FromParent(scope *Scope) *Scope { + return &Scope{parentScope: scope} +} + diff --git a/internal/values/values.go b/internal/values/values.go new file mode 100644 index 0000000..ff4df91 --- /dev/null +++ b/internal/values/values.go @@ -0,0 +1,36 @@ +package values + +import ( + "fmt" + "xml-programming/internal/ast" +) + +type Value struct { + Type ast.Type + + String string + Bool bool + Int int + Float float32 +} + +func FromLiteralExpression(expression ast.LiteralExpression) Value { + value := Value{ + Type: expression.Type, + } + + switch expression.Type { + case ast.String: + value.String = expression.String + case ast.Int: + value.Int = expression.Int + case ast.Bool: + value.Bool = expression.Bool + case ast.Float: + value.Float = expression.Float + default: + fmt.Println("unknown type: ", expression.Type) + } + + return value +} \ No newline at end of file diff --git a/internal/vm/exprs.go b/internal/vm/exprs.go new file mode 100644 index 0000000..77f36d4 --- /dev/null +++ b/internal/vm/exprs.go @@ -0,0 +1,393 @@ +package vm + +import ( + "fmt" + "strconv" + "strings" + "xml-programming/internal/ast" + "xml-programming/internal/scope" + "xml-programming/internal/values" +) + +func evaluateAddExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + isFloat := false + + var args []values.Value + + for _, _arg := range expression.Exprs { + arg := evaluateExpression(_arg, localScope) + if arg.Type == ast.Float { + isFloat = true + } + args = append(args, arg) + } + + if isFloat { + var sum float32 = 0 + for _, arg := range args { + if arg.Type == ast.Int { + sum += float32(arg.Int) + } else { + sum += arg.Float + } + } + return values.Value{ + Type: ast.Float, + Float: sum, + } + } else { + var sum int = 0 + for _, arg := range args { + // all ints + sum += arg.Int + } + return values.Value{ + Type: ast.Int, + Int: sum, + } + } +} + +func evaluateSubExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + isFloat := false + + arg1 := evaluateExpression(expression.Exprs[0], localScope) + if arg1.Type == ast.Float { + isFloat = true + } + arg2 := evaluateExpression(expression.Exprs[1], localScope) + if arg2.Type == ast.Float { + isFloat = true + } + + if isFloat { + var result float32 + + if arg1.Type == ast.Int { + result = float32(arg1.Int) + } else { + result = arg1.Float + } + + if arg2.Type == ast.Int { + result -= float32(arg1.Int) + } else { + result -= arg2.Float + } + + return values.Value{ + Type: ast.Float, + Float: result, + } + } else { + var result int = arg1.Int + result -= arg2.Int + + return values.Value{ + Type: ast.Int, + Int: result, + } + } +} + +func evaluateMulExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + isFloat := false + + var args []values.Value + + for _, _arg := range expression.Exprs { + arg := evaluateExpression(_arg, localScope) + if arg.Type == ast.Float { + isFloat = true + } + args = append(args, arg) + } + + if isFloat { + var product float32 = 1 + for _, arg := range args { + if arg.Type == ast.Int { + product *= float32(arg.Int) + } else { + product *= arg.Float + } + } + return values.Value{ + Type: ast.Float, + Float: product, + } + } else { + var product int = 1 + for _, arg := range args { + // all ints + product *= arg.Int + } + return values.Value{ + Type: ast.Int, + Int: product, + } + } +} + +func evaluateDivExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + isFloat := false + + arg1 := evaluateExpression(expression.Exprs[0], localScope) + if arg1.Type == ast.Float { + isFloat = true + } + arg2 := evaluateExpression(expression.Exprs[1], localScope) + if arg2.Type == ast.Float { + isFloat = true + } + + if isFloat { + var result float32 + + if arg1.Type == ast.Int { + result = float32(arg1.Int) + } else { + result = arg1.Float + } + + if arg2.Type == ast.Int { + result /= float32(arg1.Int) + } else { + result /= arg2.Float + } + + return values.Value{ + Type: ast.Float, + Float: result, + } + } else { + var result int = arg1.Int + result /= arg2.Int + + return values.Value{ + Type: ast.Int, + Int: result, + } + } +} + +func evaluateModExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + arg1 := evaluateExpression(expression.Exprs[0], localScope) + arg2 := evaluateExpression(expression.Exprs[1], localScope) + + var result int = arg1.Int + result %= arg2.Int + + return values.Value{ + Type: ast.Int, + Int: result, + } +} + +func evaluateConcatExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + builder := strings.Builder{} + + for _, _arg := range expression.Exprs { + arg := evaluateExpression(_arg, localScope) + switch arg.Type { + case ast.String: + builder.WriteString(arg.String) + case ast.Bool: + builder.WriteString(strconv.FormatBool(arg.Bool)) + case ast.Int: + builder.WriteString(strconv.FormatInt(int64(arg.Int), 10)) + case ast.Float: + builder.WriteString(strconv.FormatFloat(float64(arg.Float), 'e', 4, 32)) + } + } + + return values.Value{ + Type: ast.String, + String: builder.String(), + } +} + +func evaluateEqualExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + arg1 := evaluateExpression(expression.Exprs[0], localScope) + arg2 := evaluateExpression(expression.Exprs[1], localScope) + + if arg1.Type == ast.Int { + if arg2.Type == ast.Float { + return values.Value{ + Type: ast.Bool, + Bool: float32(arg1.Int) == arg2.Float, + } + } else { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Int == arg2.Int, + } + } + } else { + if arg2.Type == ast.Float { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Float == arg2.Float, + } + } else { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Float == float32(arg2.Int), + } + } + } +} + +func evaluateGreaterThanExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + arg1 := evaluateExpression(expression.Exprs[0], localScope) + arg2 := evaluateExpression(expression.Exprs[1], localScope) + + if arg1.Type == ast.Int { + if arg2.Type == ast.Float { + return values.Value{ + Type: ast.Bool, + Bool: float32(arg1.Int) > arg2.Float, + } + } else { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Int > arg2.Int, + } + } + } else { + if arg2.Type == ast.Float { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Float > arg2.Float, + } + } else { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Float > float32(arg2.Int), + } + } + } +} + +func evaluateLessThanExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + arg1 := evaluateExpression(expression.Exprs[0], localScope) + arg2 := evaluateExpression(expression.Exprs[1], localScope) + + if arg1.Type == ast.Int { + if arg2.Type == ast.Float { + return values.Value{ + Type: ast.Bool, + Bool: float32(arg1.Int) < arg2.Float, + } + } else { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Int < arg2.Int, + } + } + } else { + if arg2.Type == ast.Float { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Float < arg2.Float, + } + } else { + return values.Value{ + Type: ast.Bool, + Bool: arg1.Float < float32(arg2.Int), + } + } + } +} + +func evaluateNotExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + arg := evaluateExpression(expression.Exprs[0], localScope) + + return values.Value{ + Type: ast.Bool, + Bool: !arg.Bool, + } +} + +func evaluateAndExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + for _, expr := range expression.Exprs { + arg := evaluateExpression(expr, localScope) + if !arg.Bool { + return values.Value{ + Type: ast.Bool, + Bool: false, + } + } + } + + return values.Value{ + Type: ast.Bool, + Bool: true, + } +} + +func evaluateOrExpression(expression ast.OperatorExpression, localScope *scope.Scope) values.Value { + for _, expr := range expression.Exprs { + arg := evaluateExpression(expr, localScope) + if arg.Bool { + return values.Value{ + Type: ast.Bool, + Bool: true, + } + } + } + + return values.Value{ + Type: ast.Bool, + Bool: false, + } +} + +func evaluateExpression(expression ast.Expression, localScope *scope.Scope) values.Value { + switch v := expression.(type) { + case ast.LiteralExpression: + return values.FromLiteralExpression(v) + case ast.VariableExpression: + return localScope.GetVariable(v.Name).Value + case ast.OperatorExpression: + switch v.Operator { + case ast.Add: + return evaluateAddExpression(v, localScope) + case ast.Sub: + return evaluateSubExpression(v, localScope) + case ast.Mul: + return evaluateMulExpression(v, localScope) + case ast.Div: + return evaluateDivExpression(v, localScope) + case ast.Mod: + return evaluateModExpression(v, localScope) + case ast.Concat: + return evaluateConcatExpression(v, localScope) + case ast.Equal: + return evaluateEqualExpression(v, localScope) + case ast.GreaterThan: + return evaluateGreaterThanExpression(v, localScope) + case ast.LessThan: + return evaluateLessThanExpression(v, localScope) + case ast.Not: + return evaluateNotExpression(v, localScope) + case ast.And: + return evaluateAndExpression(v, localScope) + case ast.Or: + return evaluateOrExpression(v, localScope) + default: + fmt.Println(v) + panic("not yet implemented") + } + case ast.FunctionCall: + var args []values.Value + for _, arg := range v.Args { + args = append(args, evaluateExpression(arg, localScope)) + } + + return callFunction(v.Name, localScope, args) + default: + fmt.Println(expression) + panic("not yet implemented") + } +} diff --git a/internal/vm/functions.go b/internal/vm/functions.go new file mode 100644 index 0000000..b673b93 --- /dev/null +++ b/internal/vm/functions.go @@ -0,0 +1,28 @@ +package vm + +import ( + "xml-programming/internal/ast" + "xml-programming/internal/scope" + "xml-programming/internal/values" +) + +func callFunction(name string, localScope *scope.Scope, args []values.Value) values.Value { + function := localScope.GetFunction(name) + functionScope := scope.FromParent(localScope) + + for i, arg := range args { + functionScope.AddVariable(scope.Variable{ + Name: function.Args[i].Name, + Value: arg, + }) + } + + result := executeStatements(function.Body, functionScope) + if result != nil { + return *result + } else { + return values.Value{ + Type: ast.Void, + } + } +} \ No newline at end of file diff --git a/internal/vm/statements.go b/internal/vm/statements.go new file mode 100644 index 0000000..d8ef316 --- /dev/null +++ b/internal/vm/statements.go @@ -0,0 +1,111 @@ +package vm + +import ( + "fmt" + "xml-programming/internal/ast" + "xml-programming/internal/scope" + "xml-programming/internal/values" +) + +func printValue(value values.Value) { + switch value.Type { + case ast.String: + fmt.Print(value.String) + case ast.Int: + fmt.Print(value.Int) + case ast.Float: + fmt.Print(value.Float) + case ast.Bool: + fmt.Print(value.Bool) + } +} + +func executeStatement(statement ast.Statement, localScope *scope.Scope) *values.Value { + switch v := statement.(type) { + case ast.OutputStatement: + for _, _arg := range v.Exprs { + arg := evaluateExpression(_arg, localScope) + printValue(arg) + } + fmt.Println() + case ast.VariableDeclarationStatement: + localScope.AddVariable(scope.Variable{ + Name: v.Name, + Value: values.Value{ + Type: v.Type, + }, + }) + case ast.VariableAssignmentStatement: + arg := evaluateExpression(v.Expr, localScope) + variable := localScope.GetVariable(v.Name) + variable.Value = arg + case ast.FunctionStatement: + localScope.AddFunction(scope.Function{ + Name: v.Name, + Args: v.Args, + Return: v.Returns, + Body: v.Body, + }) + case ast.FunctionReturnStatement: + arg := evaluateExpression(v.Expr, localScope) + return &arg + case ast.FunctionCall: + var args []values.Value + for _, arg := range v.Args { + args = append(args, evaluateExpression(arg, localScope)) + } + + _ = callFunction(v.Name, localScope, args) + case ast.ConditionalStatement: + for _, _if := range v.Ifs { + arg := evaluateExpression(_if.Expr, localScope) + if arg.Bool { + return executeStatements(_if.Then, localScope) + } + } + return executeStatements(v.Else, localScope) + case ast.LoopStatement: + for { + arg := evaluateExpression(v.LoopCondition, localScope) + if !arg.Bool { + break + } + + result := executeStatements(v.Body, localScope) + if result != nil { + return result + } + } + case ast.ForStatement: + for i := v.From; i < v.To; i++ { + forScope := scope.FromParent(localScope) + forScope.AddVariable(scope.Variable{ + Name: v.Name, + Value: values.Value{ + Type: ast.Int, + Int: i, + }, + }) + result := executeStatements(v.Body, forScope) + if result != nil { + return result + } + } + default: + fmt.Println(v) + panic("not yet implemented") + } + + return nil +} + +func executeStatements(statements []ast.Statement, localScope *scope.Scope) *values.Value { + for _, statement := range statements { + returnValue := executeStatement(statement, localScope) + if returnValue != nil { + return returnValue + } + } + + return nil +} \ No newline at end of file diff --git a/internal/vm/vm.go b/internal/vm/vm.go new file mode 100644 index 0000000..2836c10 --- /dev/null +++ b/internal/vm/vm.go @@ -0,0 +1,11 @@ +package vm + +import ( + "xml-programming/internal/ast" + "xml-programming/internal/scope" +) + +func Run(program *ast.Program) { + localScope := scope.New() + _ = executeStatements(program.Statements, localScope) +} diff --git a/test.xml b/test.xml new file mode 100644 index 0000000..e89083e --- /dev/null +++ b/test.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + +