Implemented source tracking and type checking
All checks were successful
/ test (push) Successful in 21s

This commit is contained in:
Eduard Urbach 2025-06-27 00:05:16 +02:00
parent 70c2da4a4d
commit 329fcfff6f
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
30 changed files with 427 additions and 125 deletions

View file

@ -6,6 +6,7 @@ import (
"git.urbach.dev/cli/q/src/build"
"git.urbach.dev/cli/q/src/core"
"git.urbach.dev/cli/q/src/scanner"
"git.urbach.dev/cli/q/src/types"
)
// Compile waits for the scan to finish and compiles all functions.
@ -16,13 +17,43 @@ func Compile(b *build.Build) (*core.Environment, error) {
return nil, err
}
// Check for existence of `init`
init, exists := all.Functions["core.init"]
if !exists {
return nil, MissingInitFunction
}
// Check for existence of `main`
_, exists := all.Functions["main.main"]
_, exists = all.Functions["main.main"]
if !exists {
return nil, MissingMainFunction
}
// Resolve types
for _, f := range all.Functions {
f.Type = &types.Function{
Input: make([]types.Type, len(f.Input)),
Output: make([]types.Type, len(f.Output)),
}
for i, input := range f.Input {
input.Typ = types.Parse(input.Source[1:], f.File.Bytes)
f.Type.Input[i] = input.Typ
}
for i, output := range f.Output {
if len(output.Source) > 1 {
output.Typ = types.Parse(output.Source[1:], f.File.Bytes)
} else {
output.Typ = types.Parse(output.Source, f.File.Bytes)
}
f.Type.Output[i] = output.Typ
}
}
compileFunctions(maps.Values(all.Functions))
for _, f := range all.Functions {
@ -32,7 +63,7 @@ func Compile(b *build.Build) (*core.Environment, error) {
}
if b.ShowSSA {
showSSA(maps.Values(all.Functions))
showSSA(init)
}
return all, nil

View file

@ -3,5 +3,6 @@ package compiler
import "git.urbach.dev/cli/q/src/errors"
var (
MissingInitFunction = errors.String("Missing init function")
MissingMainFunction = errors.String("Missing main function")
)

View file

@ -2,16 +2,15 @@ package compiler
import (
"fmt"
"iter"
"git.urbach.dev/cli/q/src/core"
"git.urbach.dev/go/color/ansi"
)
// showSSA shows the SSA IR.
func showSSA(functions iter.Seq[*core.Function]) {
for f := range functions {
ansi.Bold.Printf("%s:\n", f.UniqueName)
func showSSA(root *core.Function) {
root.EachDependency(make(map[*core.Function]bool), func(f *core.Function) {
ansi.Yellow.Printf("%s:\n", f.UniqueName)
for i, block := range f.Blocks {
if i != 0 {
@ -19,8 +18,13 @@ func showSSA(functions iter.Seq[*core.Function]) {
}
for i, instr := range block.Instructions {
fmt.Printf("t%d = %s\n", i, instr.String())
}
ansi.Dim.Printf("%-4d", i)
fmt.Printf("%-40s", instr.String())
ansi.Cyan.Printf("%-30s", instr.Type().Name())
ansi.Dim.Printf("%s\n", f.File.Bytes[instr.Start():instr.End()])
}
}
fmt.Println()
})
}

View file

@ -8,7 +8,7 @@ import (
func (f *Function) CheckDeadCode() error {
for instr := range f.Values {
if instr.IsConst() && instr.Alive() == 0 {
return errors.New(&UnusedValue{Value: instr.String()}, f.File, instr.Token().Position)
return errors.New(&UnusedValue{Value: instr.String()}, f.File, instr.Start())
}
}

View file

@ -6,7 +6,7 @@ import (
"git.urbach.dev/cli/q/src/asm"
"git.urbach.dev/cli/q/src/cpu"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Compile turns a function into machine code.
@ -18,12 +18,35 @@ func (f *Function) Compile() {
continue
}
f.Identifiers[input.Name] = f.AppendRegister(i + extra)
array, isArray := input.Typ.(*types.Array)
if input.TypeTokens[0].Kind == token.ArrayStart {
extra++
f.Identifiers[input.Name+".length"] = f.AppendRegister(i + extra)
if isArray {
pointer := &ssa.Parameter{
Index: uint8(i + extra),
Name: input.Name,
Typ: &types.Pointer{To: array.Of},
Source: input.Source,
}
f.Append(pointer)
f.Identifiers[pointer.Name] = pointer
extra++
length := &ssa.Parameter{
Index: uint8(i + extra),
Name: input.Name + ".len",
Typ: types.AnyInt,
Source: input.Source,
}
f.Append(length)
f.Identifiers[length.Name] = length
continue
}
input.Index = uint8(i + extra)
f.Append(input)
f.Identifiers[input.Name] = input
}
for instr := range f.Body.Instructions {
@ -49,15 +72,15 @@ func (f *Function) Compile() {
for instr := range f.Values {
switch instr := instr.(type) {
case *ssa.Call:
f.mv(instr.Args[1:], f.CPU.Call)
f.mv(instr.Arguments[1:], f.CPU.Call)
switch arg := instr.Args[0].(type) {
switch arg := instr.Arguments[0].(type) {
case *ssa.Function:
f.Assembler.Instructions = append(f.Assembler.Instructions, &asm.Call{Label: arg.UniqueName})
}
case *ssa.Syscall:
f.mv(instr.Args, f.CPU.Syscall)
f.mv(instr.Arguments, f.CPU.Syscall)
f.Assembler.Append(&asm.Syscall{})
case *ssa.Return:

View file

@ -16,9 +16,8 @@ func (f *Function) CompileReturn(tokens token.List) error {
}
f.Append(&ssa.Return{
Arguments: ssa.Arguments{
Args: []ssa.Value{value},
},
Arguments: []ssa.Value{value},
Source: ssa.Source(tokens),
})
return nil

View file

@ -8,6 +8,7 @@ import (
"git.urbach.dev/cli/q/src/expression"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Evaluate converts an expression to an SSA value.
@ -26,8 +27,8 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
f.Dependencies.Add(function)
v := f.AppendFunction(function.UniqueName)
v.Source = expr.Token
v := f.AppendFunction(function.UniqueName, function.Type)
v.Source = ssa.Source(expr.Source)
return v, nil
}
@ -41,14 +42,14 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
v := f.AppendInt(number)
v.Source = expr.Token
v.Source = ssa.Source(expr.Source)
return v, nil
case token.String:
data := expr.Token.Bytes(f.File.Bytes)
data = Unescape(data)
v := f.AppendBytes(data)
v.Source = expr.Token
v.Source = ssa.Source(expr.Source)
return v, nil
}
@ -65,7 +66,13 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
if funcName == "len" {
identifier := children[1].String(f.File.Bytes)
return f.Identifiers[identifier+".length"], nil
length, exists := f.Identifiers[identifier+".len"]
if !exists {
return nil, errors.New(&UnknownIdentifier{Name: identifier + ".len"}, f.File, expr.Token.Position)
}
return length, nil
}
if funcName == "syscall" {
@ -87,19 +94,37 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
if isSyscall {
v := f.Append(&ssa.Syscall{
Arguments: ssa.Arguments{Args: args},
HasToken: ssa.HasToken{Source: expr.Token},
})
syscall := &ssa.Syscall{
Arguments: args,
Source: ssa.Source(expr.Source),
}
return v, nil
return f.Append(syscall), nil
} else {
v := f.Append(&ssa.Call{
Arguments: ssa.Arguments{Args: args},
HasToken: ssa.HasToken{Source: expr.Token},
})
name := args[0].(*ssa.Function).UniqueName
fn := f.All.Functions[name]
parameters := args[1:]
return v, nil
if len(parameters) != len(fn.Input) {
return nil, errors.New(&ParameterCountMismatch{Function: name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, expr.Source[0].Position)
}
for i, param := range parameters {
if !types.Is(param.Type(), fn.Input[i].Typ) {
return nil, errors.New(&TypeMismatch{
Encountered: param.Type().Name(),
Expected: fn.Input[i].Typ.Name(),
ParameterName: fn.Input[i].Name,
}, f.File, param.Start())
}
}
call := &ssa.Call{
Arguments: args,
Source: ssa.Source(expr.Source),
}
return f.Append(call), nil
}
case token.Dot:
@ -113,8 +138,8 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
}
f.Dependencies.Add(function)
v := f.AppendFunction(function.UniqueName)
v.Source = expr.Children[1].Token
v := f.AppendFunction(function.UniqueName, function.Type)
v.Source = ssa.Source(expr.Source)
return v, nil
}

View file

@ -9,6 +9,7 @@ import (
"git.urbach.dev/cli/q/src/set"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Function is the smallest unit of code.
@ -17,14 +18,15 @@ type Function struct {
Name string
UniqueName string
File *fs.File
Input []*Parameter
Output []*Parameter
Input []*ssa.Parameter
Output []*ssa.Parameter
Body token.List
Identifiers map[string]ssa.Value
All *Environment
Dependencies set.Ordered[*Function]
Assembler asm.Assembler
CPU *cpu.CPU
Type *types.Function
Err error
}

View file

@ -18,8 +18,4 @@ func TestFunction(t *testing.T) {
assert.False(t, main.IsExtern())
assert.Equal(t, main.UniqueName, "main.main")
assert.Equal(t, main.String(), main.UniqueName)
write, exists := env.Functions["io.write"]
assert.True(t, exists)
write.Output[0].Type()
}

View file

@ -1,18 +0,0 @@
package core
import (
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
// Parameter is an input or output parameter in a function.
type Parameter struct {
Name string
TypeTokens token.List
typ types.Type
}
// Type returns the data type of the parameter.
func (p *Parameter) Type() types.Type {
return p.typ
}

View file

@ -12,6 +12,43 @@ var (
InvalidRune = errors.String("Invalid rune")
)
// ParameterCountMismatch error is created when the number of provided parameters doesn't match the function signature.
type ParameterCountMismatch struct {
Function string
Count int
ExpectedCount int
}
func (err *ParameterCountMismatch) Error() string {
if err.Count > err.ExpectedCount {
return fmt.Sprintf("Too many parameters in '%s' function call", err.Function)
}
return fmt.Sprintf("Not enough parameters in '%s' function call", err.Function)
}
// TypeMismatch represents an error where a type requirement was not met.
type TypeMismatch struct {
Encountered string
Expected string
ParameterName string
IsReturn bool
}
func (err *TypeMismatch) Error() string {
subject := "type"
if err.IsReturn {
subject = "return type"
}
if err.ParameterName != "" {
return fmt.Sprintf("Expected parameter '%s' of %s '%s' (encountered '%s')", err.ParameterName, subject, err.Expected, err.Encountered)
}
return fmt.Sprintf("Expected %s '%s' instead of '%s'", subject, err.Expected, err.Encountered)
}
// UnknownIdentifier represents unknown identifiers.
type UnknownIdentifier struct {
Name string

View file

@ -11,6 +11,7 @@ type Expression struct {
Parent *Expression
Children []*Expression
Token token.Token
Source token.List
precedence int8
}

View file

@ -7,12 +7,13 @@ import (
)
// Parse generates an expression tree from tokens.
func Parse(tokens []token.Token) *Expression {
func Parse(tokens token.List) *Expression {
var (
cursor *Expression
root *Expression
groupLevel = 0
groupPosition = 0
cursorStart = 0
)
for i, t := range tokens {
@ -38,6 +39,7 @@ func Parse(tokens []token.Token) *Expression {
parameters := NewList(tokens[groupPosition:i])
node := New()
node.Token.Position = tokens[groupPosition].Position
node.Source = tokens[groupPosition:i]
switch t.Kind {
case token.GroupEnd:
@ -72,6 +74,7 @@ func Parse(tokens []token.Token) *Expression {
continue
}
group.Source = tokens[groupPosition:i]
group.precedence = math.MaxInt8
if cursor == nil {
@ -79,6 +82,7 @@ func Parse(tokens []token.Token) *Expression {
cursor = New()
cursor.Token.Position = tokens[groupPosition].Position
cursor.Token.Kind = token.Array
cursor.Source = tokens[groupPosition:i]
cursor.precedence = precedence(token.Array)
cursor.AddChild(group)
root = cursor
@ -88,6 +92,7 @@ func Parse(tokens []token.Token) *Expression {
}
} else {
cursor.AddChild(group)
cursor.Source = tokens[cursorStart : i+1]
}
continue
@ -100,9 +105,13 @@ func Parse(tokens []token.Token) *Expression {
if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String || t.Kind == token.Rune {
if cursor != nil {
node := NewLeaf(t)
node.Source = tokens[i : i+1]
cursor.Source = tokens[cursorStart : i+1]
cursor.AddChild(node)
} else {
cursor = NewLeaf(t)
cursorStart = i
cursor.Source = tokens[i : i+1]
root = cursor
}
@ -115,12 +124,15 @@ func Parse(tokens []token.Token) *Expression {
if cursor == nil {
cursor = NewLeaf(t)
cursorStart = i
cursor.Source = tokens[i : i+1]
cursor.precedence = precedence(t.Kind)
root = cursor
continue
}
node := NewLeaf(t)
node.Source = tokens[i : i+1]
node.precedence = precedence(t.Kind)
if cursor.Token.IsOperator() {
@ -131,6 +143,7 @@ func Parse(tokens []token.Token) *Expression {
if len(cursor.Children) == numOperands(cursor.Token.Kind) {
cursor.LastChild().InsertAbove(node)
} else {
cursor.Source = tokens[cursorStart : i+1]
cursor.AddChild(node)
}
} else {
@ -169,5 +182,9 @@ func Parse(tokens []token.Token) *Expression {
cursor = node
}
if root != nil {
root.Source = tokens
}
return root
}

View file

@ -4,6 +4,7 @@ import (
"git.urbach.dev/cli/q/src/core"
"git.urbach.dev/cli/q/src/errors"
"git.urbach.dev/cli/q/src/fs"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/token"
)
@ -94,9 +95,9 @@ func scanSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind
return nil, i, errors.New(MissingType, file, param[0].End())
}
function.Input = append(function.Input, &core.Parameter{
function.Input = append(function.Input, &ssa.Parameter{
Name: param[0].String(file.Bytes),
TypeTokens: param[1:],
Source: ssa.Source(param),
})
}
@ -127,18 +128,19 @@ func scanSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind
}
if len(param) == 1 {
function.Output = append(function.Output, &core.Parameter{
function.Output = append(function.Output, &ssa.Parameter{
Name: "",
TypeTokens: param,
Source: ssa.Source(param),
})
} else {
function.Output = append(function.Output, &core.Parameter{
function.Output = append(function.Output, &ssa.Parameter{
Name: param[0].String(file.Bytes),
TypeTokens: param[1:],
Source: ssa.Source(param),
})
}
errorPos = param[len(param)-1].End() + 1
}
return function, i, nil
}

View file

@ -1,20 +1,18 @@
package ssa
type Arguments struct {
Args []Value
}
type Arguments []Value
func (v *Arguments) Dependencies() []Value {
return v.Args
func (v Arguments) Dependencies() []Value {
return v
}
func (a Arguments) Equals(b Arguments) bool {
if len(a.Args) != len(b.Args) {
if len(a) != len(b) {
return false
}
for i := range a.Args {
if !a.Args[i].Equals(b.Args[i]) {
for i := range a {
if !a[i].Equals(b[i]) {
return false
}
}

View file

@ -5,6 +5,7 @@ import (
"git.urbach.dev/cli/q/src/expression"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
type BinaryOperation struct {
@ -12,7 +13,7 @@ type BinaryOperation struct {
Right Value
Op token.Kind
Liveness
HasToken
Source
}
func (v *BinaryOperation) Dependencies() []Value {
@ -26,10 +27,6 @@ func (a *BinaryOperation) Equals(v Value) bool {
return false
}
if a.Source.Kind != b.Source.Kind {
return false
}
if !a.Left.Equals(b.Left) {
return false
}
@ -48,3 +45,7 @@ func (v *BinaryOperation) IsConst() bool {
func (v *BinaryOperation) String() string {
return fmt.Sprintf("%s %s %s", v.Left, expression.Operators[v.Op].Symbol, v.Right)
}
func (v *BinaryOperation) Type() types.Type {
return v.Left.Type()
}

View file

@ -1,11 +1,16 @@
package ssa
import "bytes"
import (
"bytes"
"strconv"
"git.urbach.dev/cli/q/src/types"
)
type Bytes struct {
Bytes []byte
Liveness
HasToken
Source
}
func (v *Bytes) Dependencies() []Value {
@ -27,5 +32,9 @@ func (v *Bytes) IsConst() bool {
}
func (v *Bytes) String() string {
return string(v.Bytes)
return strconv.Quote(string(v.Bytes))
}
func (v *Bytes) Type() types.Type {
return types.String
}

View file

@ -1,11 +1,15 @@
package ssa
import "fmt"
import (
"fmt"
"git.urbach.dev/cli/q/src/types"
)
type Call struct {
Arguments
Liveness
HasToken
Source
}
func (a *Call) Equals(v Value) bool {
@ -23,5 +27,15 @@ func (v *Call) IsConst() bool {
}
func (v *Call) String() string {
return fmt.Sprintf("call%v", v.Args)
return fmt.Sprintf("%s(%v)", v.Arguments[0], v.Arguments[1:])
}
func (v *Call) Type() types.Type {
typ := v.Arguments[0].(*Function).Typ
if len(typ.Output) == 0 {
return types.Void
}
return typ.Output[0]
}

View file

@ -1,9 +1,12 @@
package ssa
import "git.urbach.dev/cli/q/src/types"
type Function struct {
UniqueName string
Typ *types.Function
Liveness
HasToken
Source
}
func (v *Function) Dependencies() []Value {
@ -27,3 +30,7 @@ func (v *Function) IsConst() bool {
func (v *Function) String() string {
return v.UniqueName
}
func (v *Function) Type() types.Type {
return v.Typ
}

View file

@ -1,11 +0,0 @@
package ssa
import "git.urbach.dev/cli/q/src/token"
type HasToken struct {
Source token.Token
}
func (v *HasToken) Token() token.Token {
return v.Source
}

View file

@ -1,5 +1,7 @@
package ssa
import "git.urbach.dev/cli/q/src/types"
// IR is a list of basic blocks.
type IR struct {
Blocks []*Block
@ -39,16 +41,9 @@ func (f *IR) AppendInt(x int) *Int {
return v
}
// AppendRegister adds a new register value to the last block.
func (f *IR) AppendRegister(index int) *Parameter {
v := &Parameter{Index: uint8(index)}
f.Append(v)
return v
}
// AppendFunction adds a new function value to the last block.
func (f *IR) AppendFunction(name string) *Function {
v := &Function{UniqueName: name}
func (f *IR) AppendFunction(name string, typ *types.Function) *Function {
v := &Function{UniqueName: name, Typ: typ}
f.Append(v)
return v
}

View file

@ -2,12 +2,14 @@ package ssa
import (
"fmt"
"git.urbach.dev/cli/q/src/types"
)
type Int struct {
Int int
Liveness
HasToken
Source
}
func (v *Int) Dependencies() []Value {
@ -31,3 +33,7 @@ func (v *Int) IsConst() bool {
func (v *Int) String() string {
return fmt.Sprintf("%d", v.Int)
}
func (v *Int) Type() types.Type {
return types.AnyInt
}

View file

@ -1,11 +1,17 @@
package ssa
import "fmt"
import (
"fmt"
"git.urbach.dev/cli/q/src/types"
)
type Parameter struct {
Index uint8
Name string
Typ types.Type
Liveness
HasToken
Source
}
func (v *Parameter) Dependencies() []Value {
@ -27,5 +33,9 @@ func (v *Parameter) IsConst() bool {
}
func (v *Parameter) String() string {
return fmt.Sprintf("arg[%d]", v.Index)
return fmt.Sprintf("in[%d]", v.Index)
}
func (v *Parameter) Type() types.Type {
return v.Typ
}

View file

@ -1,10 +1,14 @@
package ssa
import "fmt"
import (
"fmt"
"git.urbach.dev/cli/q/src/types"
)
type Return struct {
Arguments
HasToken
Source
}
func (a *Return) AddUse(user Value) { panic("return is not a value") }
@ -17,12 +21,12 @@ func (a *Return) Equals(v Value) bool {
return false
}
if len(a.Args) != len(b.Args) {
if len(a.Arguments) != len(b.Arguments) {
return false
}
for i := range a.Args {
if !a.Args[i].Equals(b.Args[i]) {
for i := range a.Arguments {
if !a.Arguments[i].Equals(b.Arguments[i]) {
return false
}
}
@ -35,5 +39,9 @@ func (v *Return) IsConst() bool {
}
func (v *Return) String() string {
return fmt.Sprintf("return %v", v.Args)
return fmt.Sprintf("return %v", v.Arguments)
}
func (v *Return) Type() types.Type {
return types.Void
}

13
src/ssa/Source.go Normal file
View file

@ -0,0 +1,13 @@
package ssa
import "git.urbach.dev/cli/q/src/token"
type Source token.List
func (v Source) Start() token.Position {
return v[0].Position
}
func (v Source) End() token.Position {
return v[len(v)-1].End()
}

View file

@ -1,11 +1,15 @@
package ssa
import "fmt"
import (
"fmt"
"git.urbach.dev/cli/q/src/types"
)
type Syscall struct {
Arguments
Liveness
HasToken
Source
}
func (a *Syscall) Equals(v Value) bool {
@ -23,5 +27,9 @@ func (v *Syscall) IsConst() bool {
}
func (v *Syscall) String() string {
return fmt.Sprintf("syscall%v", v.Args)
return fmt.Sprintf("syscall(%v)", v.Arguments)
}
func (v *Syscall) Type() types.Type {
return types.Any
}

View file

@ -1,13 +1,18 @@
package ssa
import "git.urbach.dev/cli/q/src/token"
import (
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
type Value interface {
AddUse(Value)
Alive() int
Dependencies() []Value
End() token.Position
Equals(Value) bool
IsConst() bool
String() string
Token() token.Token
Start() token.Position
Type() types.Type
}

View file

@ -17,6 +17,7 @@ var (
UInt32 = &Base{name: "uint32", size: 4}
UInt16 = &Base{name: "uint16", size: 2}
UInt8 = &Base{name: "uint8", size: 1}
Void = &Base{name: "void", size: 0}
)
var (

47
src/types/Function.go Normal file
View file

@ -0,0 +1,47 @@
package types
import "strings"
// Function transforms inputs to new outputs.
type Function struct {
Input []Type
Output []Type
}
// Name returns the type name.
func (f *Function) Name() string {
builder := strings.Builder{}
builder.WriteString("(")
for i, input := range f.Input {
builder.WriteString(input.Name())
if i != len(f.Input)-1 {
builder.WriteString(", ")
}
}
builder.WriteString(")")
if len(f.Output) == 0 {
return builder.String()
}
builder.WriteString(" -> (")
for i, output := range f.Output {
builder.WriteString(output.Name())
if i != len(f.Output)-1 {
builder.WriteString(",")
}
}
builder.WriteString(")")
return builder.String()
}
// Size returns the total size in bytes.
func (f *Function) Size() int {
return 8
}

71
src/types/Parse.go Normal file
View file

@ -0,0 +1,71 @@
package types
import (
"git.urbach.dev/cli/q/src/token"
)
// Parse returns the type with the given tokens or `nil` if it doesn't exist.
func Parse[T ~[]token.Token](tokens T, source []byte) Type {
if tokens[0].Kind == token.Mul {
to := tokens[1:]
typ := Parse(to, source)
if typ == Any {
return AnyPointer
}
return &Pointer{To: typ}
}
if len(tokens) >= 2 && tokens[0].Kind == token.ArrayStart && tokens[1].Kind == token.ArrayEnd {
to := tokens[2:]
typ := Parse(to, source)
if typ == Any {
return AnyArray
}
return &Array{Of: typ}
}
if tokens[0].Kind != token.Identifier {
return nil
}
switch tokens[0].String(source) {
case "int":
return Int
case "int64":
return Int64
case "int32":
return Int32
case "int16":
return Int16
case "int8":
return Int8
case "uint":
return UInt
case "uint64":
return UInt64
case "uint32":
return UInt32
case "uint16":
return UInt16
case "uint8":
return UInt8
case "byte":
return Byte
case "bool":
return Bool
case "float":
return Float
case "float64":
return Float64
case "float32":
return Float32
case "any":
return Any
default:
return nil
}
}