package build import ( "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) // Function represents a function. type Function struct { Name string File *fs.File Body token.List compiler } // Compile turns a function into machine code. func (f *Function) Compile() { f.assembler.Label(f.Name) f.err = f.CompileTokens(f.Body) f.assembler.Return() } // CompileTokens compiles a token list. func (f *Function) CompileTokens(body token.List) error { start := 0 groupLevel := 0 blockLevel := 0 for i, t := range body { if start == i && t.Kind == token.NewLine { start = i + 1 continue } switch t.Kind { case token.NewLine: if groupLevel > 0 || blockLevel > 0 { continue } instruction := body[start:i] if config.Verbose { f.debug = append(f.debug, debug{ position: len(f.assembler.Instructions), source: instruction, }) } err := f.CompileInstruction(instruction) if err != nil { return err } start = i + 1 case token.GroupStart: groupLevel++ case token.GroupEnd: groupLevel-- case token.BlockStart: blockLevel++ case token.BlockEnd: blockLevel-- } } return nil } // 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() switch true { case isVariableDefinition(expr): return f.CompileVariableDefinition(expr) case isAssignment(expr): return f.CompileAssignment(expr) case isFunctionCall(expr): return f.CompileFunctionCall(expr) default: return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } } // String returns the function name. func (f *Function) String() string { return f.Name } // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { _, exists := f.variables[name] return exists } // isAssignment returns true if the expression is an assignment. func isAssignment(expr *expression.Expression) bool { return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' } // 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() == "λ" } // 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() == ":=" }