diff --git a/src/build/Build.go b/src/build/Build.go index fcf1698..1efb881 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -19,8 +19,8 @@ func New(files ...string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() (map[string]*Function, error) { - functions, errors := Scan(build.Files) - return Compile(functions, errors) + functions, errors := scan(build.Files) + return compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/Function.go b/src/build/Function.go index 251d5e1..9b70b36 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -73,6 +73,123 @@ func (f *Function) Compile() { f.Assembler.Return() } +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(line token.List) error { + if len(line) == 0 { + return nil + } + + if line[0].Kind == token.Keyword { + return f.CompileKeyword(line) + } + + expr := expression.Parse(line) + + if expr == nil { + return nil + } + + defer expr.Close() + + if isVariableDefinition(expr) { + return f.CompileVariableDefinition(expr) + } + + if isFunctionCall(expr) { + return f.CompileFunctionCall(expr) + } + + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) +} + +// CompileKeyword compiles an instruction that starts with a keyword. +func (f *Function) CompileKeyword(line token.List) error { + switch line[0].Text() { + case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.Assembler.Return() + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + + return nil +} + +// CompileVariableDefinition compiles a variable definition. +func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + } + + name := expr.Children[0].Token.Text() + _, exists := f.Variables[name] + + if exists { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + + value := expr.Children[1] + + // All expressions are returned to the memory pool. + // To avoid losing variable values, we will detach it from the expression. + expr.RemoveChild(value) + + f.Variables[name] = &Variable{ + Name: name, + Value: value, + IsConst: true, + } + + return nil +} + +// CompileFunctionCall compiles a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + for i, parameter := range parameters { + switch parameter.Token.Kind { + case token.Identifier: + name := parameter.Token.Text() + variable, exists := f.Variables[name] + + if !exists { + panic("Unknown identifier " + name) + } + + if !variable.IsConst { + panic("Not implemented yet") + } + + n, _ := strconv.Atoi(variable.Value.Token.Text()) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := parameter.Token.Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") + } + } + + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + + return nil +} + // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { ansi.Bold.Println(f.Name) @@ -92,111 +209,17 @@ func (f *Function) PrintAsm() { ansi.Dim.Println("╰────────────────────────────────────────────────────────────") } -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(line token.List) error { - if len(line) == 0 { - return nil - } - - if line[0].Kind == token.Keyword { - switch line[0].Text() { - case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) - } - } - - expr := expression.Parse(line) - - if expr == nil { - return nil - } - - defer expr.Close() - - if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String { - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } - - if expr.Token.Text() == ":=" { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) - } - - name := expr.Children[0].Token.Text() - _, exists := f.Variables[name] - - if exists { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) - } - - value := expr.Children[1] - - // All expressions are returned to the memory pool. - // To avoid losing variable values, we will remove it from the expression. - expr.RemoveChild(value) - - f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, - } - - return nil - } - - if expr.Token.Text() == "λ" { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - for i, parameter := range parameters { - switch parameter.Token.Kind { - case token.Identifier: - name := parameter.Token.Text() - variable, exists := f.Variables[name] - - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - case token.Number: - value := parameter.Token.Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - default: - panic("Unknown expression") - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil - } - - return nil -} - // String returns the function name. func (f *Function) String() string { return f.Name } + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} diff --git a/src/build/Compile.go b/src/build/compile.go similarity index 84% rename from src/build/Compile.go rename to src/build/compile.go index f6f1736..528fa43 100644 --- a/src/build/Compile.go +++ b/src/build/compile.go @@ -4,8 +4,8 @@ import ( "sync" ) -// Compile compiles all the functions. -func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { +// compile waits for all functions to finish compilation. +func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { allFunctions := map[string]*Function{} for functions != nil || errors != nil { diff --git a/src/build/Scan.go b/src/build/scan.go similarity index 97% rename from src/build/Scan.go rename to src/build/scan.go index b3e808a..4d16bb0 100644 --- a/src/build/Scan.go +++ b/src/build/scan.go @@ -11,8 +11,8 @@ import ( "git.akyoto.dev/cli/q/src/errors" ) -// Scan scans the directory. -func Scan(files []string) (<-chan *Function, <-chan error) { +// scan scans the directory. +func scan(files []string) (<-chan *Function, <-chan error) { functions := make(chan *Function) errors := make(chan error)