diff --git a/examples/array/array.q b/examples/array/array.q index 2cdf875..a57691a 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -2,12 +2,13 @@ import mem import sys main() { - length := 4 + length := 5 address := mem.alloc(length) - address[0] = 65 - address[1] = 66 - address[2] = 67 - address[3] = 68 + address[0] = 'H' + address[1] = 'e' + address[2] = 'l' + address[3] = 'l' + address[4] = 'o' sys.write(1, address, length) mem.free(address, length) } \ No newline at end of file diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 4d3e08b..767a2df 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,8 +1,6 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" @@ -38,19 +36,25 @@ func (f *Function) CompileAssign(node *ast.Assign) error { defer f.useVariable(variable) index := left.Children[1] - offset, err := strconv.Atoi(index.Token.Text(f.File.Bytes)) + offset, _, err := f.Number(index.Token) if err != nil { return err } - num, err := strconv.Atoi(right.Token.Text(f.File.Bytes)) + number, size, err := f.Number(right.Token) if err != nil { return err } - f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + elementSize := byte(1) + + if size != elementSize { + return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: elementSize, Size: size}, f.File, right.Token.Position) + } + + f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: elementSize}, number) return nil } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index d6cc124..8dbda36 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -1,8 +1,6 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" @@ -22,9 +20,8 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope defer f.useVariable(variable) return f.ExecuteRegisterRegister(operation, register, variable.Register) - case token.Number: - value := operand.Text(f.File.Bytes) - number, err := strconv.Atoi(value) + case token.Number, token.Rune: + number, _, err := f.Number(operand) if err != nil { return err diff --git a/src/build/core/Number.go b/src/build/core/Number.go new file mode 100644 index 0000000..a3d2f47 --- /dev/null +++ b/src/build/core/Number.go @@ -0,0 +1,36 @@ +package core + +import ( + "strconv" + "unicode/utf8" + + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Number tries to convert the token into a numeric value. +func (f *Function) Number(t token.Token) (int, byte, error) { + switch t.Kind { + case token.Number: + number, err := strconv.Atoi(t.Text(f.File.Bytes)) + return number, 8, err + + case token.Rune: + r := t.Bytes(f.File.Bytes) + r = r[1 : len(r)-1] + + if len(r) == 0 { + return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + } + + number, size := utf8.DecodeRune(r) + + if len(r) > size { + return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + } + + return int(number), byte(size), nil + } + + return 0, 0, errors.New(errors.InvalidNumber, f.File, t.Position) +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index f048fac..36af04f 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "strconv" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" @@ -26,15 +25,14 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.RegisterRegister(asm.MOVE, register, variable.Register) return nil - case token.Number: - value := t.Text(f.File.Bytes) - n, err := strconv.Atoi(value) + case token.Number, token.Rune: + number, _, err := f.Number(t) if err != nil { return err } - f.RegisterNumber(asm.MOVE, register, n) + f.RegisterNumber(asm.MOVE, register, number) return nil case token.String: diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 886d1f8..3aab709 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,9 +1,11 @@ package errors var ( - InvalidStatement = &Base{"Invalid statement"} + InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} - MissingOperand = &Base{"Missing operand"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} NotImplemented = &Base{"Not implemented"} ) diff --git a/src/build/errors/NumberExceedsBounds.go b/src/build/errors/NumberExceedsBounds.go new file mode 100644 index 0000000..4d33f43 --- /dev/null +++ b/src/build/errors/NumberExceedsBounds.go @@ -0,0 +1,15 @@ +package errors + +import "fmt" + +// NumberExceedsBounds error is created when the number doesn't fit into the destination. +type NumberExceedsBounds struct { + Number int + Size byte + ExpectedSize byte +} + +// Error generates the string representation. +func (err *NumberExceedsBounds) Error() string { + return fmt.Sprintf("Number %d needs %d bytes but the maximum is %d bytes", err.Number, err.Size, err.ExpectedSize) +} diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index f2e8b56..7fbd6ad 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -89,7 +89,7 @@ func Parse(tokens []token.Token) *Expression { continue } - if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String { + if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String || t.Kind == token.Rune { if cursor != nil { node := NewLeaf(t) cursor.AddChild(node) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 1df7adf..53997c4 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -9,6 +9,7 @@ const ( NewLine // NewLine is the newline character. Identifier // Identifier is a series of characters used to identify a variable or function. Number // Number is a series of numerical characters. + Rune // Rune is a single unicode code point. String // String is an uninterpreted series of characters in the source code. Comment // Comment is a comment. Separator // , diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 0060958..f0d282c 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -48,13 +48,14 @@ func Tokenize(buffer []byte) List { continue - case '"': + case '"', '\'': + limiter := buffer[i] start := i end := Position(len(buffer)) i++ for i < Position(len(buffer)) { - if buffer[i] == '"' { + if buffer[i] == limiter { end = i + 1 i++ break @@ -63,7 +64,13 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: String, Position: start, Length: Length(end - start)}) + kind := String + + if limiter == '\'' { + kind = Rune + } + + tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) continue default: diff --git a/tests/examples_test.go b/tests/examples_test.go index 03139f0..e4b083a 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -17,7 +17,7 @@ var examples = []struct { {"hello", "", "Hello", 0}, {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, - {"array", "", "ABCD", 0}, + {"array", "", "Hello", 0}, } func TestExamples(t *testing.T) {