static type analysis, formatted compiler errors, hex support, composite types, updated cli, more global builtins, fix list form, fix value refs

This commit is contained in:
Neemek 2025-03-18 20:22:46 +01:00
parent 84cc845748
commit 954308b29f
Signed by: neemek
GPG key ID: 28360A8951CD0E9B
18 changed files with 1352 additions and 285 deletions

View file

@ -1,6 +1,7 @@
package main
import (
"errors"
"github.com/alecthomas/kong"
"log"
"neemek.com/anglais/core"
@ -93,6 +94,10 @@ func (cmd *RunCmd) Run(ctx *Context) error {
tree, err := p.Parse()
if ctx.Debug {
log.Printf("Parsed tree, meaning:\n%s", tree)
}
// if there were parsing errors, print them out
if err != nil {
print(err.(*core.ParsingError).Format([]rune(src)))
@ -118,7 +123,11 @@ func (cmd *RunCmd) Run(ctx *Context) error {
}
err = c.Compile(tree)
if err != nil {
return err
var e core.CompilerError
if errors.As(err, &e) {
log.Fatal(e.Format([]rune(src)))
}
log.Fatal(err)
}
chunk = c.Chunk
@ -200,7 +209,7 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
tree, err := p.Parse()
if err != nil {
print(err.(*core.ParsingError).Format([]rune(src)))
log.Fatal(err.(*core.ParsingError).Format([]rune(src)))
}
if ctx.Debug {
@ -224,7 +233,11 @@ func (cmd *CompileCmd) Run(ctx *Context) error {
err = c.Compile(tree)
if err != nil {
return err
var e core.CompilerError
if errors.As(err, &e) {
log.Fatal(e.Format([]rune(src)))
}
log.Fatal(err)
}
if ctx.Debug {
@ -256,7 +269,7 @@ var cli struct {
Debug bool `short:"D" name:"debug" help:"Enable debug mode."`
Run RunCmd `cmd:"" name:"run" help:"Run program."`
CompileCmd CompileCmd `cmd:"" name:"compile" help:"Compile program to bytecode."`
Compile CompileCmd `cmd:"" name:"compile" help:"Compile program to bytecode."`
}
func main() {

View file

@ -1,8 +1,8 @@
package core
import (
"errors"
"fmt"
"strings"
)
type Compiler struct {
@ -26,6 +26,61 @@ type LocalVariable struct {
scope int
}
type CompilerError struct {
Description string
Causer Node
}
func (e CompilerError) Error() string {
return e.Description
}
func (e CompilerError) Format(src []rune) string {
b := strings.Builder{}
b.WriteString(e.Description)
b.WriteString("\n")
// highlight offending area
start, end := e.Causer.Bounds()
lineStart := 0
line := 1
pos := 0
for i := Pos(0); i < start; i++ {
pos++
if src[i] == '\n' {
line++
lineStart = int(i)
pos = 0
}
}
lineEnd := lineStart
for lineEnd < len(src) {
lineEnd++
if src[lineEnd] == '\n' {
break
}
}
lineDescriptor := fmt.Sprintf("%d:%d~%d", line, pos, int(end-start)+pos)
b.WriteString(lineDescriptor)
b.WriteString("\t | ")
b.WriteString(string(src[lineStart+1 : lineEnd]))
b.WriteString("\n")
b.WriteString(strings.Repeat(" ", len(lineDescriptor)))
b.WriteString("\t ")
b.WriteString(strings.Repeat(" ", int(start)-lineStart-1))
b.WriteString(strings.Repeat("^", int(end-start)))
return b.String()
}
func NewCompiler() *Compiler {
c := &Compiler{
NewChunk(make([]Bytecode, 0), make([]Value, 0)),
@ -113,6 +168,29 @@ func (c *Compiler) Compile(tree Node) error {
return err
}
case UnaryNodeType:
if c.isTreeConstant(tree.(*UnaryNode).value) {
v, err := c.compute(tree)
if err != nil {
return err
}
c.add(InstructionConstant)
c.addConstant(v)
} else {
err := c.Compile(tree.(*UnaryNode).value)
if err != nil {
return err
}
switch tree.(*UnaryNode).UnaryOperation {
case UnaryNegate:
c.add(InstructionNegate)
case UnaryNot:
c.add(InstructionNot)
}
}
case BooleanNodeType:
if tree.(*BooleanNode).value {
c.add(InstructionTrue)
@ -219,14 +297,34 @@ func (c *Compiler) Compile(tree Node) error {
case CallNodeType:
n := tree.(*CallNode)
for _, arg := range n.args {
err := c.Compile(arg)
s, err := c.deduceSignature(n.source)
if err != nil {
return err
}
f, ok := s.(*FunctionSignature)
if !ok {
return c.error(fmt.Sprintf("cannot call non-function value of type %s", s), n)
}
for i, arg := range n.args {
sig, err := c.deduceSignature(arg)
if err != nil {
return err
}
// check that arg type is as required
if !f.in[i].Matches(sig) {
return c.error(fmt.Sprintf("argument #%d does not have expected type signature: got %s, requires %s", i, sig, f.in[i]), arg)
}
err = c.Compile(arg)
if err != nil {
return err
}
}
err := c.Compile(n.source)
err = c.Compile(n.source)
if err != nil {
return err
}
@ -246,6 +344,13 @@ func (c *Compiler) Compile(tree Node) error {
c.add(InstructionConstant)
c.add(Bytecode(fi))
// allow self-referencing
sig, err := c.deduceSignature(n)
if err != nil {
return err
}
c.registerVar(n.name, sig)
// keep track of main chunk
mc := c.Chunk
// and ip
@ -257,10 +362,10 @@ func (c *Compiler) Compile(tree Node) error {
c.ip = 0
for _, p := range n.parameters {
c.registerVar(p.name, p.signature)
c.registerVar(p.Name, p.Signature)
}
err := c.Compile(n.logic)
err = c.Compile(n.logic)
if err != nil {
return err
}
@ -312,6 +417,9 @@ func (c *Compiler) Compile(tree Node) error {
case BreakpointNodeType:
c.add(InstructionBreakpoint)
default:
panic(fmt.Sprintf("unimplemented compiling of %s", tree.Type()))
}
return nil
@ -340,7 +448,16 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
switch binary.BinaryOperation {
case BinaryAddition:
res, err := c.deduceSignature(binary)
if err != nil {
return err
}
if res.Type() == TypeString {
c.add(InstructionStringConcatenation)
} else {
c.add(InstructionAdd)
}
case BinarySubtraction:
c.add(InstructionSub)
case BinaryMultiplication:
@ -375,13 +492,45 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
case NumberNodeType:
return &NumberSignature{}, nil
case ReferenceNodeType:
return c.getVarSignature(tree.(*ReferenceNode).name)
n := tree.(*ReferenceNode)
if c.isGlobal(n.name) {
return SignatureOf(DefaultGlobals[n.name]), nil
}
for i := c.stack.Current - 1; i >= 0; i-- {
v := c.stack.items[i]
if v.name == n.name {
return v.signature, nil
}
}
return nil, c.error(fmt.Sprintf("variable %s not defined", n.name), n)
case BooleanNodeType:
return &BooleanSignature{}, nil
case NilNodeType:
return &NilSignature{}, nil
case ListNodeType:
return &ListSignature{}, nil
n := tree.(*ListNode)
var contents TypeSignature
// check for contents type
for _, v := range n.items {
sig, err := c.deduceSignature(v)
if err != nil {
return nil, err
}
if contents == nil {
contents = sig
} else {
contents = &AnySignature{}
break
}
}
return &ListSignature{
contents,
}, nil
case BinaryNodeType:
n := tree.(*BinaryNode)
l, err := c.deduceSignature(n.Left)
@ -394,13 +543,13 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
}
if l != r {
return nil, errors.New(fmt.Sprintf("cannot perform binary %s on different types: %s and %s", n.BinaryOperation, l, r))
return nil, c.error(fmt.Sprintf("cannot perform binary %s on different types: %s and %s", n.BinaryOperation, l, r), n)
}
switch n.BinaryOperation {
case BinarySubtraction, BinaryMultiplication, BinaryDivision:
if l.Type() != TypeNumber {
return nil, errors.New(fmt.Sprintf("cannot perform binary %s non-number type %s", n.BinaryOperation, l))
return nil, c.error(fmt.Sprintf("cannot perform binary %s non-number type %s", n.BinaryOperation, l), n)
}
return &NumberSignature{}, nil
@ -411,11 +560,11 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
case TypeNumber:
return &NumberSignature{}, nil
default:
return nil, errors.New(fmt.Sprintf("cannot perform binary addition on type %s", l))
return nil, c.error(fmt.Sprintf("cannot perform binary addition on type %s", l), n)
}
case BinaryAnd, BinaryOr:
if l.Type() != TypeBoolean {
return nil, errors.New(fmt.Sprintf("cannot perform binary %s on type %s", l, n.BinaryOperation))
return nil, c.error(fmt.Sprintf("cannot perform binary %s on type %s", l, n.BinaryOperation), n)
}
return &BooleanSignature{}, nil
@ -423,11 +572,13 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
return &BooleanSignature{}, nil
case BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual:
if l.Type() != TypeNumber {
return nil, errors.New(fmt.Sprintf("cannot perform number comparison (%s) on type %s", l, n.BinaryOperation))
return nil, c.error(fmt.Sprintf("cannot perform number comparison (%s) on type %s", l, n.BinaryOperation), n)
}
return &BooleanSignature{}, nil
}
return nil, c.error(fmt.Sprintf("cannot deduce result type of binary %s", n.BinaryOperation), n)
case AccessNodeType:
n := tree.(*AccessNode)
sig, err := c.deduceSignature(n.source)
@ -436,13 +587,19 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
}
switch sig.Type() {
case TypeString, TypeList:
case TypeString:
return SignatureOf(StringPrototype[n.property]), nil
case TypeList:
return SignatureOf(ListPrototype[n.property]), nil
case TypeObject:
if v, ok := ObjectPrototype[n.property]; ok {
return SignatureOf(v), nil
}
return sig.(*ObjectSignature).members[n.property], nil
case TypeNumber, TypeBoolean, TypeNil, TypeFunction:
default:
panic(fmt.Sprintf("cannot access property from value of type %s", sig))
return nil, c.error(fmt.Sprintf("cannot access property from value of type %s", sig), n)
}
case CallNodeType:
@ -453,13 +610,13 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
}
if sig.Type() != TypeFunction {
return nil, errors.New(fmt.Sprintf("cannot call value of type %s", sig.Type()))
return nil, c.error(fmt.Sprintf("cannot call value of type %s", sig.Type()), n.source)
}
f := sig.(*FunctionSignature)
if len(n.args) != len(f.in) {
return nil, errors.New(fmt.Sprintf("bad argument count (expected %v, got %v)", len(f.in), len(n.args)))
return nil, c.error(fmt.Sprintf("bad argument count (expected %v, got %v)", len(f.in), len(n.args)), n)
}
// type check arguments
@ -470,6 +627,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
}
if !sig.Matches(f.in[i]) {
return nil, c.error(fmt.Sprintf("argument #%d has wrong type signature. requires %s, got %s", i, f.in[i], sig), arg)
}
}
@ -481,7 +639,7 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
sigs := make([]TypeSignature, len(n.parameters))
for i, p := range n.parameters {
sigs[i] = p.signature
sigs[i] = p.Signature
}
return &FunctionSignature{
@ -489,26 +647,76 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
n.yield,
}, nil
default:
panic("unhandled default case")
case UnaryNodeType:
n := tree.(*UnaryNode)
sig, err := c.deduceSignature(n.value)
if err != nil {
return nil, err
}
panic(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()))
switch n.UnaryOperation {
case UnaryNegate:
if sig.Type() != TypeNumber {
return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be number)", n.UnaryOperation), n)
}
return &NumberSignature{}, nil
case UnaryNot:
if sig.Type() != TypeBoolean {
return nil, c.error(fmt.Sprintf("cannot perform negation on type %s (must be boolean)", n.UnaryOperation), n)
}
return &BooleanSignature{}, nil
}
return nil, c.error(fmt.Sprintf("unimplemented result type deduction for unary %s", n.UnaryOperation), n)
default:
return nil, c.error(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()), tree)
}
}
func (c *Compiler) getVarSignature(name string) (TypeSignature, error) {
if c.isGlobal(name) {
return SignatureOf(DefaultGlobals[name]), nil
}
for i := c.stack.Current - 1; i >= 0; i-- {
v := c.stack.items[i]
if v.name == name {
return v.signature, nil
func (c *Compiler) affirmReturnSignature(tree Node, sig TypeSignature) error {
switch tree.Type() {
case BlockNodeType:
for _, stmt := range tree.(*BlockNode).statements {
if err := c.affirmReturnSignature(stmt, sig); err != nil {
return err
}
}
return nil, errors.New(fmt.Sprintf("variable %s not defined", name))
case ReturnNodeType:
n := tree.(*ReturnNode)
v, err := c.deduceSignature(n.value)
if err != nil {
return err
}
if !sig.Matches(v) {
return c.error(fmt.Sprintf("function cannot return a value with type %s. must be %s", v, sig), n.value)
}
case ConditionalNodeType:
n := tree.(*ConditionalNode)
if err := c.affirmReturnSignature(n.do, sig); err != nil {
return err
}
if n.otherwise != nil {
if err := c.affirmReturnSignature(n.otherwise, sig); err != nil {
return err
}
}
case LoopNodeType:
n := tree.(*LoopNode)
if err := c.affirmReturnSignature(n.do, sig); err != nil {
return err
}
default:
}
return nil
}
func (c *Compiler) getVar(name string) {
@ -628,6 +836,16 @@ func (c *Compiler) compute(tree Node) (Value, error) {
case *BinaryNode:
return c.computeBinary(n)
case *UnaryNode:
v, err := c.compute(n.value)
if err != nil {
return nil, err
}
return &NumberValue{
-v.(*NumberValue).Number,
}, nil
default:
panic(fmt.Sprintf("unexpected node %s, %T", tree.String(), tree))
}
@ -646,29 +864,29 @@ func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
var v interface{}
switch n.BinaryOperation {
case BinaryAddition:
v = l.(*NumberValue).float64 + r.(*NumberValue).float64
v = l.(*NumberValue).Number + r.(*NumberValue).Number
case BinarySubtraction:
v = l.(*NumberValue).float64 - r.(*NumberValue).float64
v = l.(*NumberValue).Number - r.(*NumberValue).Number
case BinaryMultiplication:
v = l.(*NumberValue).float64 * r.(*NumberValue).float64
v = l.(*NumberValue).Number * r.(*NumberValue).Number
case BinaryDivision:
v = l.(*NumberValue).float64 / r.(*NumberValue).float64
v = l.(*NumberValue).Number / r.(*NumberValue).Number
case BinaryAnd:
v = l.(*BoolValue).bool && r.(*BoolValue).bool
v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean
case BinaryOr:
v = l.(*BoolValue).bool && r.(*BoolValue).bool
v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean
case BinaryEquality:
v = l.Equals(r)
case BinaryInequality:
v = !l.Equals(r.(*BoolValue))
v = !l.Equals(r)
case BinaryLess:
v = l.(*NumberValue).float64 < r.(*NumberValue).float64
v = l.(*NumberValue).Number < r.(*NumberValue).Number
case BinaryGreater:
v = l.(*NumberValue).float64 > r.(*NumberValue).float64
v = l.(*NumberValue).Number > r.(*NumberValue).Number
case BinaryLessEqual:
v = l.(*NumberValue).float64 <= r.(*NumberValue).float64
v = l.(*NumberValue).Number <= r.(*NumberValue).Number
case BinaryGreaterEqual:
v = l.(*NumberValue).float64 >= r.(*NumberValue).float64
v = l.(*NumberValue).Number >= r.(*NumberValue).Number
}
return GoToValue(v), nil
@ -697,6 +915,13 @@ func (c *Compiler) descend() {
}
}
func (c *Compiler) error(msg string, causer Node) CompilerError {
return CompilerError{
msg,
causer,
}
}
func (c *Compiler) resolveImport(path string) Node {
if chunk, ok := c.imports[path]; ok {
return chunk

View file

@ -38,6 +38,7 @@ func GetCompileTestData() map[string]CompileTestData {
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
[]Value{
&StringValue{"Hello world!"},
@ -50,12 +51,15 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
false,
0, 0,
},
&BlockNode{
[]Node{
@ -63,14 +67,19 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
nil,
0, 0,
},
},
0, 0,
},
[]Value{
&VariableValue{
@ -87,12 +96,15 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
true,
0, 0,
},
&BlockNode{
[]Node{
@ -100,14 +112,19 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
nil,
0, 0,
},
},
0, 0,
},
[]Value{
&VariableValue{
@ -124,12 +141,15 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
false,
0, 0,
},
&BlockNode{
[]Node{
@ -137,10 +157,13 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
&BlockNode{
[]Node{
@ -148,13 +171,18 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
2,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
0, 0,
},
},
},
0, 0,
},
[]Value{
&VariableValue{
@ -171,12 +199,15 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
0,
0, 0,
},
true,
0, 0,
},
&ConditionalNode{
&BooleanNode{
true,
0, 0,
},
&BlockNode{
[]Node{
@ -184,10 +215,13 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
&BlockNode{
[]Node{
@ -195,13 +229,18 @@ func GetCompileTestData() map[string]CompileTestData {
"a",
&NumberNode{
2,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
0, 0,
},
},
},
0, 0,
},
[]Value{
&VariableValue{
@ -216,10 +255,13 @@ func GetCompileTestData() map[string]CompileTestData {
BinaryAddition,
&NumberNode{
1,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
[]Value{
&NumberValue{3},
@ -247,16 +289,28 @@ func GetCompileTestData() map[string]CompileTestData {
&ReturnNode{
&BinaryNode{
BinaryAddition,
&ReferenceNode{"a"},
&ReferenceNode{"b"},
},
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
[]Value{
&VariableValue{
@ -306,25 +360,39 @@ func GetCompileTestData() map[string]CompileTestData {
[]Node{
&AssignNode{
"b",
&NumberNode{1},
&NumberNode{
1,
0, 0,
},
true,
0, 0,
},
&ReturnNode{
&ReferenceNode{"b"},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
},
0, 0,
},
0, 0,
},
true,
0, 0,
},
&CallNode{
&ReferenceNode{
"a",
0, 0,
},
[]Node{},
false,
0, 0,
},
},
0, 0,
},
[]Value{
&VariableValue{
@ -363,7 +431,7 @@ func printChunk(t *testing.T, name string, chunk *Chunk) {
t.Logf("=-= constants =-=")
for i, ct := range chunk.Constants {
t.Logf("c=%d \t%s", i, ct)
t.Logf("c=%d \t%s", i, ct.DebugString())
f, ok := ct.(*FunctionValue)
if ok {

View file

@ -29,6 +29,7 @@ const (
TokenSemicolon
TokenNumber
TokenHexadecimal
TokenString
TokenName
@ -65,6 +66,7 @@ const (
TokenLessThanOrEqual
TokenDoubleAmpersand
TokenPipe
TokenDoublePipe
TokenBreakpoint
@ -156,6 +158,10 @@ func (t TokenType) String() string {
return "import"
case TokenColon:
return "colon"
case TokenPipe:
return "pipe"
case TokenHexadecimal:
return "hexadecimal"
}
return "UNDEFINED TOKENTYPE STRING CONVERSION"
@ -279,7 +285,7 @@ func (l *Lexer) NextToken() (Token, error) {
return l.makeToken(TokenDoublePipe), nil
}
return l.makeToken(TokenError), errors.New("malformed token (got '|', expected '|' to follow)")
return l.makeToken(TokenPipe), nil
case '"':
// include ending quote
@ -330,6 +336,19 @@ func (l *Lexer) NextToken() (Token, error) {
default:
return l.makeToken(TokenName), nil
}
} else if c == '0' && l.peek() != '.' {
if l.peek() == 'x' {
l.advance()
// hex
for unicode.In(l.peek(), unicode.Hex_Digit) {
l.advance()
}
return l.makeToken(TokenHexadecimal), nil
}
return l.makeToken(TokenNumber), nil
} else if unicode.IsDigit(c) {
for unicode.IsDigit(l.peek()) {
l.advance()

View file

@ -11,6 +11,8 @@ type NodeType int
type Node interface {
Type() NodeType
String() string
Bounds() (Pos, Pos)
}
const (
@ -21,6 +23,7 @@ const (
NilNodeType
ListNodeType
BinaryNodeType
UnaryNodeType
BlockNodeType
ConditionalNodeType
LoopNodeType
@ -69,6 +72,8 @@ func (n NodeType) String() string {
return "Breakpoint"
case ImportNodeType:
return "Import"
case UnaryNodeType:
return "Unary"
}
return "Invalid Node Type"
}
@ -76,6 +81,9 @@ func (n NodeType) String() string {
// ReferenceNode a reference to a variable on the stack
type ReferenceNode struct {
name string
start Pos
end Pos
}
func (n ReferenceNode) Type() NodeType {
@ -86,10 +94,17 @@ func (n ReferenceNode) String() string {
return n.name
}
func (n ReferenceNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// StringNode string/text values
type StringNode struct {
value string
quoted string
start Pos
end Pos
}
func (n StringNode) Type() NodeType {
@ -100,8 +115,15 @@ func (n StringNode) String() string {
return n.quoted
}
func (n StringNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type NumberNode struct {
value float64
start Pos
end Pos
}
func (n NumberNode) Type() NodeType {
@ -112,9 +134,16 @@ func (n NumberNode) String() string {
return strconv.FormatFloat(n.value, 'g', -1, NumberSize)
}
func (n NumberNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ListNode a list or sequence of values (items)
type ListNode struct {
items []Node
start Pos
end Pos
}
func (n ListNode) Type() NodeType {
@ -125,18 +154,25 @@ func (n ListNode) String() string {
sb := strings.Builder{}
sb.WriteString("[")
for i, item := range n.items {
sb.WriteString(item.String())
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(item.String())
}
sb.WriteString("]")
return sb.String()
}
func (n ListNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type AccessNode struct {
source Node
property string
start Pos
end Pos
}
func (n AccessNode) Type() NodeType {
@ -147,6 +183,10 @@ func (n AccessNode) String() string {
return fmt.Sprintf("(%s from %s)", n.property, n.source)
}
func (n AccessNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type BinaryOperation uint
func (n BinaryOperation) String() string {
@ -198,11 +238,45 @@ const (
BinaryGreaterEqual
)
func (n BinaryOperation) Symbol() string {
switch n {
case BinaryAddition:
return "+"
case BinarySubtraction:
return "-"
case BinaryMultiplication:
return "*"
case BinaryDivision:
return "/"
case BinaryEquality:
return "=="
case BinaryInequality:
return "!="
case BinaryLess:
return "<"
case BinaryGreater:
return ">"
case BinaryAnd:
return "&&"
case BinaryOr:
return "||"
case BinaryLessEqual:
return "<="
case BinaryGreaterEqual:
return ">="
}
panic("unsupported binary operation to symbol conversion for " + n.String())
}
// BinaryNode All operations which take 2 variables
type BinaryNode struct {
BinaryOperation
Left Node
Right Node
start Pos
end Pos
}
func (n BinaryNode) Type() NodeType {
@ -213,9 +287,65 @@ func (n BinaryNode) String() string {
return fmt.Sprintf("%s %s %s", n.Left.String(), n.BinaryOperation.String(), n.Right.String())
}
func (n BinaryNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type UnaryOperation int
const (
UnaryNegate UnaryOperation = iota
UnaryNot
)
func (op UnaryOperation) String() string {
switch op {
case UnaryNegate:
return "negate"
case UnaryNot:
return "not"
}
panic("unimplemented unary operation to string conversion")
}
func (op UnaryOperation) Symbol() string {
switch op {
case UnaryNegate:
return "-"
case UnaryNot:
return "!"
}
panic("unimplemented unary operation to symbol conversion")
}
type UnaryNode struct {
UnaryOperation
value Node
start Pos
end Pos
}
func (n UnaryNode) Type() NodeType {
return UnaryNodeType
}
func (n UnaryNode) String() string {
return fmt.Sprintf("%s %s", n.UnaryOperation.String(), n.value.String())
}
func (n UnaryNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// BooleanNode boolean value
type BooleanNode struct {
value bool
start Pos
end Pos
}
func (n BooleanNode) Type() NodeType {
@ -226,8 +356,15 @@ func (n BooleanNode) String() string {
return strconv.FormatBool(n.value)
}
func (n BooleanNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// NilNode nil value
type NilNode struct{}
type NilNode struct {
start Pos
end Pos
}
func (n NilNode) Type() NodeType {
return NilNodeType
@ -237,9 +374,16 @@ func (n NilNode) String() string {
return "nil"
}
func (n NilNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// BlockNode block node with statements
type BlockNode struct {
statements []Node
start Pos
end Pos
}
func (n BlockNode) Type() NodeType {
@ -257,8 +401,15 @@ func (n BlockNode) String() string {
return builder.String()
}
func (n BlockNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type ImportNode struct {
path string
start Pos
end Pos
}
func (n ImportNode) Type() NodeType {
@ -269,11 +420,18 @@ func (n ImportNode) String() string {
return fmt.Sprintf("import %s", n.path)
}
func (n ImportNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ConditionalNode conditionals (if statements)
type ConditionalNode struct {
condition Node
do Node
otherwise Node
start Pos
end Pos
}
func (n ConditionalNode) Type() NodeType {
@ -284,10 +442,17 @@ func (n ConditionalNode) String() string {
return fmt.Sprintf("if %s then %s otheriwise %s", n.condition.String(), n.do.String(), n.otherwise.String())
}
func (n ConditionalNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// LoopNode Loops (for/while)
type LoopNode struct {
condition Node
do Node
start Pos
end Pos
}
func (n LoopNode) Type() NodeType {
@ -298,11 +463,18 @@ func (n LoopNode) String() string {
return fmt.Sprintf("while %s loop %s", n.condition.String(), n.do.String())
}
func (n LoopNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// AssignNode assignment
type AssignNode struct {
name string
value Node
declare bool
start Pos
end Pos
}
func (n AssignNode) Type() NodeType {
@ -313,11 +485,18 @@ func (n AssignNode) String() string {
return fmt.Sprintf("set %s to %s", n.name, n.value)
}
func (n AssignNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// CallNode function call
type CallNode struct {
source Node
args []Node
keep bool
start Pos
end Pos
}
func (n CallNode) Type() NodeType {
@ -328,17 +507,24 @@ func (n CallNode) String() string {
return fmt.Sprintf("call %s with args (%s)", n.source.String(), n.args)
}
func (n CallNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// FunctionNode definition of function
type FunctionNode struct {
name string
parameters []FunctionParameter
yield TypeSignature
logic Node
start Pos
end Pos
}
type FunctionParameter struct {
name string
signature TypeSignature
Name string
Signature TypeSignature
}
func (n FunctionNode) Type() NodeType {
@ -349,9 +535,16 @@ func (n FunctionNode) String() string {
return fmt.Sprintf("definition of %s, do %s", n.name, n.logic.String())
}
func (n FunctionNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
// ReturnNode return a value out of this context
type ReturnNode struct {
value Node
start Pos
end Pos
}
func (n ReturnNode) Type() NodeType {
@ -362,7 +555,14 @@ func (n ReturnNode) String() string {
return fmt.Sprintf("return %s", n.value)
}
type BreakpointNode struct{}
func (n ReturnNode) Bounds() (Pos, Pos) {
return n.start, n.end
}
type BreakpointNode struct {
start Pos
end Pos
}
func (n BreakpointNode) Type() NodeType {
return BreakpointNodeType
@ -371,3 +571,7 @@ func (n BreakpointNode) Type() NodeType {
func (n BreakpointNode) String() string {
return "breakpoint"
}
func (n BreakpointNode) Bounds() (Pos, Pos) {
return n.start, n.end
}

View file

@ -147,6 +147,8 @@ func (p *Parser) factor() (Node, error) {
return &StringNode{
(*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1],
(*p.prev).Lexeme,
p.prev.Start,
p.prev.Length,
}, nil
case TokenNumber:
@ -159,17 +161,37 @@ func (p *Parser) factor() (Node, error) {
return &NumberNode{
num,
p.prev.Start,
p.prev.Length,
}, nil
case TokenHexadecimal:
p.advance()
start := (*p.prev).Start
num, err := strconv.ParseUint((*p.prev).Lexeme[2:], 16, NumberSize)
if err != nil {
return nil, err
}
return &NumberNode{
float64(num),
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenTrue:
p.advance()
return &BooleanNode{
true,
p.prev.Start,
p.prev.Length,
}, nil
case TokenFalse:
p.advance()
return &BooleanNode{
false,
p.prev.Start,
p.prev.Length,
}, nil
case TokenNil:
@ -179,6 +201,8 @@ func (p *Parser) factor() (Node, error) {
case TokenOpenBracket:
p.advance()
start := p.prev.Start
var values []Node
for !p.accept(TokenCloseBracket) {
if len(values) > 0 {
@ -198,24 +222,47 @@ func (p *Parser) factor() (Node, error) {
return &ListNode{
values,
start,
p.prev.Start + p.prev.Length,
}, nil
// unary minus
case TokenMinus:
p.advance()
first := p.prev
f, err := p.factor()
if err != nil {
return nil, err
}
return &BinaryNode{
BinarySubtraction,
&NumberNode{0},
return &UnaryNode{
UnaryNegate,
f,
first.Start,
p.prev.Start + p.prev.Length,
}, nil
case TokenBang:
p.advance()
start := p.prev.Start
v, err := p.factor()
if err != nil {
return nil, err
}
return &UnaryNode{
UnaryNot,
v,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenName:
p.advance()
name := (*p.prev).Lexeme
start := p.prev.Start
nameEnd := start + p.prev.Length
if p.curr.Type == TokenOpenParenthesis {
args, err := p.parseArgs()
@ -226,27 +273,38 @@ func (p *Parser) factor() (Node, error) {
return &CallNode{
&ReferenceNode{
name,
start,
nameEnd,
},
args,
true,
start,
p.prev.Start + p.prev.Length,
}, nil
}
return &ReferenceNode{
name,
start,
nameEnd,
}, nil
case TokenFunc:
p.advance()
start := p.prev.Start
params, err := p.parseParams()
if err != nil {
return nil, err
}
sig, err := p.parseSignature()
var sig TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
sig, err = p.parseSignature()
if err != nil {
return nil, err
}
}
b, err := p.block(false)
if err != nil {
@ -258,6 +316,8 @@ func (p *Parser) factor() (Node, error) {
params,
sig,
b,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenOpenParenthesis:
@ -280,6 +340,8 @@ func (p *Parser) factor() (Node, error) {
}
func (p *Parser) prop() (Node, error) {
start := p.curr.Start
v, err := p.factor()
if err != nil {
return nil, err
@ -295,6 +357,8 @@ func (p *Parser) prop() (Node, error) {
v = &AccessNode{
v,
property,
start,
p.prev.Start + p.prev.Length,
}
// if called, also add
@ -308,6 +372,8 @@ func (p *Parser) prop() (Node, error) {
v,
args,
true,
start,
p.prev.Start + p.prev.Length,
}
}
}
@ -316,6 +382,7 @@ func (p *Parser) prop() (Node, error) {
}
func (p *Parser) product() (Node, error) {
start := p.curr.Start
left, err := p.prop()
if err != nil {
return nil, err
@ -337,6 +404,8 @@ func (p *Parser) product() (Node, error) {
op,
left,
f,
start,
p.prev.Start + p.prev.Length,
}
}
@ -344,6 +413,8 @@ func (p *Parser) product() (Node, error) {
}
func (p *Parser) term() (Node, error) {
start := p.curr.Start
left, err := p.product()
if err != nil {
return nil, err
@ -365,6 +436,8 @@ func (p *Parser) term() (Node, error) {
op,
left,
pr,
start,
p.prev.Start + p.prev.Length,
}
}
@ -372,6 +445,7 @@ func (p *Parser) term() (Node, error) {
}
func (p *Parser) comparison() (Node, error) {
start := p.curr.Start
left, err := p.term()
if err != nil {
@ -409,10 +483,13 @@ func (p *Parser) comparison() (Node, error) {
op,
left,
t,
start,
p.prev.Start + p.prev.Length,
}, nil
}
func (p *Parser) condition() (Node, error) {
start := p.curr.Start
left, err := p.comparison()
if err != nil {
return nil, err
@ -440,12 +517,15 @@ func (p *Parser) condition() (Node, error) {
op,
left,
c,
start,
p.prev.Start + p.prev.Length,
}, nil
}
func (p *Parser) statement() (Node, error) {
switch (*p.curr).Type {
case TokenIf:
start := p.curr.Start
p.advance()
condition, err := p.condition()
@ -476,15 +556,20 @@ func (p *Parser) statement() (Node, error) {
condition,
then,
otherwise,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenName:
p.advance()
start := p.prev.Start
name := (*p.prev).Lexeme
if (*p.curr).Type == TokenDot {
var v Node = &ReferenceNode{
name,
start,
p.prev.Start + p.prev.Length,
}
// parse chains of prop-getting ( "".split().join().length.round() )
@ -497,6 +582,8 @@ func (p *Parser) statement() (Node, error) {
v = &AccessNode{
v,
property,
start,
p.prev.Start + p.prev.Length,
}
// if called, also add
@ -510,6 +597,8 @@ func (p *Parser) statement() (Node, error) {
v,
args,
(*p.curr).Type == TokenDot, // if the chain is continued, keep the value.
start,
p.prev.Start + p.prev.Length,
}
}
}
@ -524,9 +613,13 @@ func (p *Parser) statement() (Node, error) {
return &CallNode{
&ReferenceNode{
name,
start,
start + Pos(len(name)),
},
args,
false,
start,
p.prev.Start + p.prev.Length,
}, nil
} else if p.accept(TokenAssign) || p.accept(TokenDeclare) {
isDeclaration := p.prev.Type == TokenDeclare
@ -539,6 +632,8 @@ func (p *Parser) statement() (Node, error) {
name,
c,
isDeclaration,
start,
p.prev.Start + p.prev.Length,
}, nil
} else {
return p.condition()
@ -546,6 +641,7 @@ func (p *Parser) statement() (Node, error) {
case TokenImport:
p.advance()
start := p.prev.Start
if err := p.expect(TokenString); err != nil {
return nil, err
@ -555,11 +651,15 @@ func (p *Parser) statement() (Node, error) {
return &ImportNode{
path,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenFunc:
p.advance()
funcStart := p.prev.Start
if err := p.expect(TokenName); err != nil {
return nil, err
}
@ -570,7 +670,13 @@ func (p *Parser) statement() (Node, error) {
return nil, err
}
yield, err := p.parseSignature()
var yield TypeSignature = &NilSignature{}
if p.curr.Type != TokenOpenBrace {
yield, err = p.parseSignature()
if err != nil {
return nil, err
}
}
b, err := p.block(false)
if err != nil {
@ -584,12 +690,17 @@ func (p *Parser) statement() (Node, error) {
params,
yield,
b,
funcStart,
p.prev.Start + p.prev.Length,
},
true,
funcStart,
p.prev.Start + p.prev.Length,
}, nil
case TokenWhile:
p.advance()
start := p.prev.Start
c, err := p.condition()
if err != nil {
@ -604,10 +715,13 @@ func (p *Parser) statement() (Node, error) {
return &LoopNode{
c,
b,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenReturn:
p.advance()
start := p.prev.Start
c, err := p.condition()
if err != nil {
@ -616,6 +730,8 @@ func (p *Parser) statement() (Node, error) {
return &ReturnNode{
c,
start,
p.prev.Start + p.prev.Length,
}, nil
case TokenBreakpoint:
@ -641,6 +757,8 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
}
}
start := p.prev.Start
statements := make([]Node, 0)
for !p.accept(TokenCloseBrace) {
@ -655,6 +773,8 @@ func (p *Parser) block(canBeStatement bool) (Node, error) {
return &BlockNode{
statements,
start,
p.prev.Start + p.prev.Length,
}, nil
}
@ -740,6 +860,8 @@ func (p *Parser) parseParams() ([]FunctionParameter, error) {
}
func (p *Parser) parseSignature() (TypeSignature, error) {
var s TypeSignature
if p.accept(TokenFunc) {
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
@ -766,10 +888,10 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
return nil, err
}
return &FunctionSignature{
s = &FunctionSignature{
in,
out,
}, nil
}
}
if err := p.expect(TokenName); err != nil {
@ -779,11 +901,11 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
switch name {
case "string":
return &StringSignature{}, nil
s = &StringSignature{}
case "number":
return &NumberSignature{}, nil
s = &NumberSignature{}
case "boolean":
return &BooleanSignature{}, nil
s = &BooleanSignature{}
case "list":
if err := p.expect(TokenOpenBracket); err != nil {
return nil, err
@ -798,10 +920,28 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
return nil, err
}
return &ListSignature{
s = &ListSignature{
contents,
}
case "any":
s = &AnySignature{}
}
if p.accept(TokenPipe) {
other, err := p.parseSignature()
if err != nil {
return nil, err
}
return &CompositeSignature{
s,
other,
}, nil
}
panic("unsupported type: " + name)
if s == nil {
return nil, p.error("unsupported type: "+name, p.prev)
}
return s, nil
}

View file

@ -68,14 +68,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&NumberNode{
1,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"assignment": {
@ -92,10 +97,13 @@ func GetTokenTestData() map[string]TokenTestData {
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"declaration": {
@ -115,14 +123,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&NumberNode{
1,
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
// (2 + 1) * 5 + 3 / (6 - 2) - 10 / 2
@ -169,30 +182,63 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryMultiplication,
&BinaryNode{
BinaryAddition,
&NumberNode{2},
&NumberNode{1},
&NumberNode{
2,
0, 0,
},
&NumberNode{5},
&NumberNode{
1,
0, 0,
},
0, 0,
},
&NumberNode{
5,
0, 0,
},
0, 0,
},
&BinaryNode{
BinaryDivision,
&NumberNode{3},
&NumberNode{
3,
0, 0,
},
&BinaryNode{
BinarySubtraction,
&NumberNode{6},
&NumberNode{2},
&NumberNode{
6,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
0, 0,
},
0, 0,
},
&BinaryNode{
BinaryDivision,
&NumberNode{10},
&NumberNode{2},
&NumberNode{
10,
0, 0,
},
&NumberNode{
2,
0, 0,
},
0, 0,
},
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"condition_equal": {
@ -212,14 +258,19 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality,
&NumberNode{
20,
0, 0,
},
&NumberNode{
15,
0, 0,
},
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
"if_statement": {
@ -242,10 +293,13 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality,
&ReferenceNode{
"a",
0, 0,
},
&NumberNode{
0,
0, 0,
},
0, 0,
},
do: &BlockNode{
[]Node{
@ -253,13 +307,17 @@ func GetTokenTestData() map[string]TokenTestData {
"b",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
},
},
},
0, 0,
},
},
"if_else_statement": {
@ -288,10 +346,13 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryEquality,
&ReferenceNode{
"a",
0, 0,
},
&NumberNode{
0,
0, 0,
},
0, 0,
},
do: &BlockNode{
[]Node{
@ -299,10 +360,13 @@ func GetTokenTestData() map[string]TokenTestData {
"b",
&NumberNode{
1,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
otherwise: &BlockNode{
[]Node{
@ -310,13 +374,17 @@ func GetTokenTestData() map[string]TokenTestData {
"b",
&NumberNode{
0,
0, 0,
},
false,
0, 0,
},
},
0, 0,
},
},
},
},
},
0, 0,
},
},
"empty_block": {
@ -329,8 +397,10 @@ func GetTokenTestData() map[string]TokenTestData {
[]Node{
&BlockNode{
[]Node{},
0, 0,
},
},
0, 0,
},
},
"lambda": { // a := func(a, b) { return a + b }
@ -382,18 +452,26 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
},
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
"function_declaration": {
@ -439,18 +517,26 @@ func GetTokenTestData() map[string]TokenTestData {
BinaryAddition,
&ReferenceNode{
"a",
0, 0,
},
&ReferenceNode{
"b",
0, 0,
},
0, 0,
},
0, 0,
},
},
0, 0,
},
},
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
"prop_getting": {
@ -470,12 +556,16 @@ func GetTokenTestData() map[string]TokenTestData {
&AccessNode{
&ReferenceNode{
"a",
0, 0,
},
"b",
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
"list_init": {
@ -515,27 +605,41 @@ func GetTokenTestData() map[string]TokenTestData {
[]Node{
&ReferenceNode{
"a",
0, 0,
},
&NumberNode{
3.141,
0, 0,
},
&StringNode{
"Hello world!",
"\"Hello world!\"",
0, 0,
},
&BooleanNode{
true,
0, 0,
},
&ListNode{
[]Node{
&NumberNode{2}, &NumberNode{3},
&NumberNode{
2,
0, 0,
}, &NumberNode{
3,
0, 0,
},
},
0, 0,
},
},
0, 0,
},
true,
0, 0,
},
},
0, 0,
},
},
}
@ -676,10 +780,10 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
}
for i, p := range m.parameters {
if !n.parameters[i].signature.Matches(p.signature) {
t.Errorf("Function node parameter signature %d does not match: %s and %s", i, p.signature, n.parameters[i].signature)
} else if n.parameters[i].name != p.name {
t.Errorf("Function node parameter name %d does not match: %s and %s", i, p.name, n.parameters[i].name)
if !n.parameters[i].Signature.Matches(p.Signature) {
t.Errorf("Function node parameter signature %d does not match: %s and %s", i, p.Signature, n.parameters[i].Signature)
} else if n.parameters[i].Name != p.Name {
t.Errorf("Function node parameter name %d does not match: %s and %s", i, p.Name, n.parameters[i].Name)
} else {
t.Logf("Function node parameter %d matches (%s)", i, p)
}
@ -782,6 +886,10 @@ func SerializeTokens(tokens []Token) string {
out.WriteString("breakpoint")
case TokenEOF:
out.WriteString(fmt.Sprintf("<error: \"%s\">", token.Lexeme))
case TokenHexadecimal:
out.WriteString(token.Lexeme)
case TokenPipe:
out.WriteString(" | ")
case TokenError:
}
}

View file

@ -1,6 +1,9 @@
package core
import "fmt"
import (
"fmt"
"strings"
)
type Type int
@ -13,50 +16,34 @@ const (
TypeObject
TypeFunction
TypeAny
TypeComposite
)
func (t Type) String() string {
switch t {
case TypeString:
return "String"
return "string"
case TypeNumber:
return "Number"
return "number"
case TypeBoolean:
return "Boolean"
return "boolean"
case TypeNil:
return "Nil"
return "nil"
case TypeList:
return "List"
return "list"
case TypeObject:
return "Object"
return "object"
case TypeFunction:
return "Function"
return "func"
case TypeAny:
return "any"
case TypeComposite:
return "composite"
}
panic(fmt.Sprintf("unsupported string conversion for type %v", int(t)))
}
func TypeOf(v Value) Type {
switch v.(type) {
case *StringValue:
return TypeString
case *NumberValue:
return TypeNumber
case *BoolValue:
return TypeBoolean
case *ListValue:
return TypeList
case *ObjectValue:
return TypeObject
case *FunctionValue:
return TypeFunction
case *BuiltinFunctionValue:
return TypeFunction
}
panic(fmt.Sprintf("unsupported value (of type %T)", v))
}
func SignatureOf(v Value) TypeSignature {
switch t := v.(type) {
case *StringValue:
@ -83,6 +70,9 @@ type TypeSignature interface {
// Matches check if this type signature matches another.
Matches(TypeSignature) bool
// String create a human-readable string version of the value type.
String() string
}
type NilSignature struct{}
@ -91,40 +81,72 @@ func (*NilSignature) Type() Type {
return TypeNil
}
func (*NilSignature) Matches(other TypeSignature) bool {
func (s *NilSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeNil
}
func (*NilSignature) String() string {
return "nil"
}
type StringSignature struct{}
func (*StringSignature) Type() Type {
return TypeString
}
func (*StringSignature) Matches(other TypeSignature) bool {
func (s *StringSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeString
}
func (*StringSignature) String() string {
return "string"
}
type NumberSignature struct{}
func (*NumberSignature) Type() Type {
return TypeNumber
}
func (*NumberSignature) Matches(other TypeSignature) bool {
func (s *NumberSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeNumber
}
func (*NumberSignature) String() string {
return "number"
}
type BooleanSignature struct{}
func (*BooleanSignature) Type() Type {
return TypeBoolean
}
func (*BooleanSignature) Matches(other TypeSignature) bool {
func (s *BooleanSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || other.Type() == TypeBoolean
}
func (*BooleanSignature) String() string {
return "boolean"
}
type ListSignature struct {
contents TypeSignature
}
@ -134,7 +156,15 @@ func (*ListSignature) Type() Type {
}
func (s *ListSignature) Matches(other TypeSignature) bool {
return other.Type() == TypeAny || other.Type() == TypeList && other.(*ListSignature).contents.Matches(s.contents)
if other.Type() == TypeComposite {
return other.Matches(s)
}
return other.Type() == TypeAny || (other.Type() == TypeList && other.(*ListSignature).contents.Matches(s.contents))
}
func (s *ListSignature) String() string {
return fmt.Sprintf("list[%s]", s.contents)
}
type ObjectSignature struct {
@ -146,6 +176,10 @@ func (*ObjectSignature) Type() Type {
}
func (s *ObjectSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
if other.Type() == TypeAny {
return true
}
@ -175,6 +209,10 @@ func (s *ObjectSignature) Matches(other TypeSignature) bool {
return true
}
func (s *ObjectSignature) String() string {
panic("unimplemented")
}
type FunctionSignature struct {
in []TypeSignature
out TypeSignature
@ -185,6 +223,10 @@ func (*FunctionSignature) Type() Type {
}
func (s *FunctionSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeComposite {
return other.Matches(s)
}
if other.Type() == TypeAny {
return true
}
@ -213,12 +255,51 @@ func (s *FunctionSignature) Matches(other TypeSignature) bool {
return true
}
func (s *FunctionSignature) String() string {
b := strings.Builder{}
b.WriteString("func(")
for i, t := range s.in {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(t.String())
}
b.WriteString(") ")
b.WriteString(s.out.String())
return b.String()
}
type AnySignature struct{}
func (AnySignature) Type() Type {
func (*AnySignature) Type() Type {
return TypeAny
}
func (AnySignature) Matches(_ TypeSignature) bool {
func (*AnySignature) Matches(_ TypeSignature) bool {
return true
}
func (*AnySignature) String() string {
return "any"
}
type CompositeSignature struct {
A TypeSignature
B TypeSignature
}
func (*CompositeSignature) Type() Type {
return TypeComposite
}
func (s *CompositeSignature) Matches(other TypeSignature) bool {
return s.A.Matches(other) || s.B.Matches(other)
}
func (s *CompositeSignature) String() string {
return fmt.Sprintf("%s|%s", s.A, s.B)
}

View file

@ -106,6 +106,9 @@ type Value interface {
// Get a member from the value. An error is returned if the member does not exist
Get(string) (Value, error)
// Clone create a clone of the value. The returned value is a pointer to a new value of the same type as the value.
Clone() Value
}
type NilValue struct{}
@ -130,8 +133,12 @@ func (v *NilValue) Get(_ string) (Value, error) {
return nil, errors.New("nil has no properties")
}
func (v *NilValue) Clone() Value {
return &NilValue{}
}
type BoolValue struct {
bool
Boolean bool
}
func (v *BoolValue) Type() ValueType {
@ -139,7 +146,7 @@ func (v *BoolValue) Type() ValueType {
}
func (v *BoolValue) String() string {
if v.bool {
if v.Boolean {
return "true"
} else {
return "false"
@ -151,16 +158,22 @@ func (v *BoolValue) DebugString() string {
}
func (v *BoolValue) Equals(other Value) bool {
return other.Type() == BoolValueType && other.(*BoolValue).bool == v.bool
return other.Type() == BoolValueType && other.(*BoolValue).Boolean == v.Boolean
}
func (v *BoolValue) Get(_ string) (Value, error) {
return nil, errors.New("booleans have no properties")
}
func (v *BoolValue) Clone() Value {
return &BoolValue{
v.Boolean,
}
}
// ObjectValue An object with any number of members (key-value pairs)
type ObjectValue struct {
members map[string]Value
Members map[string]Value
}
func (v *ObjectValue) Type() ValueType {
@ -169,7 +182,7 @@ func (v *ObjectValue) Type() ValueType {
func (v *ObjectValue) String() string {
out := "{"
for key, value := range v.members {
for key, value := range v.Members {
if out != "{" {
out += ", "
}
@ -191,8 +204,8 @@ func (v *ObjectValue) Equals(other Value) bool {
return false
}
for key, value := range v.members {
if !object.members[key].Equals(value) {
for key, value := range v.Members {
if !object.Members[key].Equals(value) {
return false
}
}
@ -216,7 +229,7 @@ var ObjectPrototype = map[string]Value{
return nil, errors.New("property is not a string")
}
this.members[v.string] = p
this.Members[v.Text] = p
return &NilValue{}, nil
},
@ -225,7 +238,7 @@ var ObjectPrototype = map[string]Value{
}
func (v *ObjectValue) Get(key string) (Value, error) {
if member, ok := v.members[key]; ok {
if member, ok := v.Members[key]; ok {
return member, nil
} else if p, ok := ObjectPrototype[key]; ok {
return p, nil
@ -234,9 +247,21 @@ func (v *ObjectValue) Get(key string) (Value, error) {
}
}
func (v *ObjectValue) Clone() Value {
m := make(map[string]Value, len(v.Members))
for name, mem := range v.Members {
m[name] = mem.Clone()
}
return &ObjectValue{
m,
}
}
// NumberValue Integer or floating-point values
type NumberValue struct {
float64
Number float64
}
const NumberSize int = 64
@ -246,7 +271,7 @@ func (v *NumberValue) Type() ValueType {
}
func (v *NumberValue) String() string {
return strconv.FormatFloat(v.float64, 'g', -1, NumberSize)
return strconv.FormatFloat(v.Number, 'g', -1, NumberSize)
}
func (v *NumberValue) DebugString() string {
@ -254,7 +279,7 @@ func (v *NumberValue) DebugString() string {
}
func (v *NumberValue) Equals(other Value) bool {
return other.Type() == NumberValueType && other.(*NumberValue).float64 == v.float64
return other.Type() == NumberValueType && other.(*NumberValue).Number == v.Number
}
func (v *NumberValue) Get(_ string) (Value, error) {
@ -262,8 +287,14 @@ func (v *NumberValue) Get(_ string) (Value, error) {
return nil, errors.New("numbers have no properties")
}
func (v *NumberValue) Clone() Value {
return &NumberValue{
v.Number,
}
}
type StringValue struct {
string
Text string
}
func (v *StringValue) Type() ValueType {
@ -271,7 +302,7 @@ func (v *StringValue) Type() ValueType {
}
func (v *StringValue) String() string {
return v.string
return v.Text
}
func (v *StringValue) DebugString() string {
@ -279,7 +310,7 @@ func (v *StringValue) DebugString() string {
}
func (v *StringValue) Equals(other Value) bool {
return other.Type() == StringValueType && other.(*StringValue).string == v.string
return other.Type() == StringValueType && other.(*StringValue).Text == v.Text
}
var StringPrototype = map[string]*BuiltinFunctionValue{
@ -318,9 +349,15 @@ func (v *StringValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
}
func (v *StringValue) Clone() Value {
return &StringValue{
v.Text,
}
}
// ListValue a dynamic list of values
type ListValue struct {
items []Value
Items []Value
}
func (v *ListValue) Type() ValueType {
@ -329,7 +366,7 @@ func (v *ListValue) Type() ValueType {
func (v *ListValue) String() string {
out := "["
for i, item := range v.items {
for i, item := range v.Items {
if i != 0 {
out += ", "
}
@ -351,12 +388,12 @@ func (v *ListValue) Equals(other Value) bool {
l := other.(*ListValue)
if len(v.items) != len(l.items) {
if len(v.Items) != len(l.Items) {
return false
}
for i, item := range l.items {
if !item.Equals(l.items[i]) {
for i, item := range l.Items {
if !item.Equals(l.Items[i]) {
return false
}
}
@ -372,7 +409,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
this.(*ListValue).items = append(this.(*ListValue).items, v[0])
this.(*ListValue).Items = append(this.(*ListValue).Items, v[0])
return &NilValue{}, nil
},
nil,
@ -386,8 +423,8 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
&AnySignature{},
},
func(_ *VM, this Value, p []Value) (Value, error) {
items := this.(*ListValue).items
index := int(p[0].(*NumberValue).float64)
items := this.(*ListValue).Items
index := int(p[0].(*NumberValue).Number)
if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -404,7 +441,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
&NumberSignature{},
},
func(_ *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*ListValue).items)), nil
return GoToValue(len(this.(*ListValue).Items)), nil
},
nil,
},
@ -426,18 +463,16 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
v := m[0]
var f Value
f, ok := v.(*FunctionValue)
if !ok {
f, ok = v.(*BuiltinFunctionValue)
if !ok {
switch a := v.(type) {
case *FunctionValue, *BuiltinFunctionValue:
f = a
default:
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
}
}
for i, item := range list.items {
for i, item := range list.Items {
var err error
list.items[i], err = vm.Call(f, []Value{
list.Items[i], err = vm.Call(f, []Value{
item,
})
@ -470,7 +505,7 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
f := m[0]
sum := m[1]
for _, v := range list.items {
for _, v := range list.Items {
result, err := vm.Call(f, []Value{sum, v})
if err != nil {
return nil, err
@ -492,6 +527,18 @@ func (v *ListValue) Get(key string) (Value, error) {
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
}
func (v *ListValue) Clone() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &ListValue{
n,
}
}
type FunctionValue struct {
Name string
Params []FunctionParameter
@ -521,6 +568,15 @@ func (v *FunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties")
}
func (v *FunctionValue) Clone() Value {
return &FunctionValue{
v.Name,
v.Params,
v.Chunk,
v.Parent,
}
}
type BuiltinFunctionValue struct {
Name string
Signature *FunctionSignature
@ -549,6 +605,15 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties")
}
func (v *BuiltinFunctionValue) Clone() Value {
return &BuiltinFunctionValue{
v.Name,
v.Signature,
v.F,
v.Parent,
}
}
// VariableValue a value wrapper for variables kept on the stack
type VariableValue struct {
name string
@ -580,3 +645,11 @@ func (v *VariableValue) Equals(other Value) bool {
func (v *VariableValue) Get(_ string) (Value, error) {
return nil, errors.New("variables have no properties")
}
func (v *VariableValue) Clone() Value {
return &VariableValue{
v.name,
v.value.Clone(),
v.scope,
}
}

View file

@ -16,19 +16,19 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Logf("Both are nil")
return
case BoolValueType:
if got.(*BoolValue).bool != want.(*BoolValue).bool {
if got.(*BoolValue).Boolean != want.(*BoolValue).Boolean {
t.Errorf("bool value mismatch: got %v, want %v", got.(*BoolValue), want.(*BoolValue))
} else {
t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
}
case NumberValueType:
if got.(*NumberValue).float64 != want.(*NumberValue).float64 {
if got.(*NumberValue).Number != want.(*NumberValue).Number {
t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue))
} else {
t.Logf("Both are same number (%s)", got.(*NumberValue).String())
}
case StringValueType:
if got.(*StringValue).string != want.(*StringValue).string {
if got.(*StringValue).Text != want.(*StringValue).Text {
t.Errorf("string value mismatch: got %v, want %v", got.(*StringValue), want.(*StringValue))
} else {
t.Logf("Both are same string (%s)", got.(*StringValue).String())

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"log"
"os"
"strings"
)
@ -26,6 +27,8 @@ const (
InstructionMul
// InstructionDiv pop two and divide the second by the first
InstructionDiv
// InstructionNegate negate the value; if it was positive, make it negative, and vice versa.
InstructionNegate
// InstructionEquals whether the two top values on the stack are equal
InstructionEquals
// InstructionNotEqual whether the two top values on the stack are not equal
@ -118,6 +121,8 @@ func (b Bytecode) String() string {
return "MUL"
case InstructionDiv:
return "DIV"
case InstructionNegate:
return "NEGATE"
case InstructionEquals:
return "EQUALS"
case InstructionNotEqual:
@ -202,7 +207,7 @@ func (c Chunk) String() string {
b.WriteString("=-= constants =-=\n")
for i, ct := range c.Constants {
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct))
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct.DebugString()))
f, ok := ct.(*FunctionValue)
if ok {
@ -228,6 +233,16 @@ func RegisterGOBTypes() {
Params: nil,
Chunk: nil,
})
// Signatures
gob.Register(&NilSignature{})
gob.Register(&NumberSignature{})
gob.Register(&StringSignature{})
gob.Register(&FunctionSignature{})
gob.Register(&ListSignature{})
gob.Register(&ObjectSignature{})
gob.Register(&BooleanSignature{})
}
func (c Chunk) Serialize() []byte {
@ -314,14 +329,47 @@ var DefaultGlobals = map[string]Value{
&FunctionSignature{
[]TypeSignature{
&StringSignature{},
&StringSignature{},
&ListSignature{
&AnySignature{},
},
},
&StringSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
valuies := m[1].(*ListValue).items
b := strings.Builder{}
template := m[0].(*StringValue).Text
valuies := m[1].(*ListValue).Items
return GoToValue(fmt.Sprintf(m[0].String(), valuies)), nil
vi := 0
last := 0
for i := 0; i < len(template); i++ {
if template[i] == '%' {
b.WriteString(template[last:i])
b.WriteString(valuies[vi].String())
vi++
last = i + 1
}
}
b.WriteString(template[last:])
return GoToValue(b.String()), nil
},
nil,
},
"char": &BuiltinFunctionValue{
"char",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&StringSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
n := args[0].(*NumberValue).Number
b := byte(n)
return &StringValue{
string([]byte{b}),
}, nil
},
nil,
},
@ -367,6 +415,29 @@ var DefaultGlobals = map[string]Value{
},
nil,
},
"str": &BuiltinFunctionValue{
"str",
&FunctionSignature{
[]TypeSignature{&AnySignature{}},
&StringSignature{},
},
func(vm *VM, _ Value, args []Value) (Value, error) {
return GoToValue(args[0].String()), nil
},
nil,
},
"exit": &BuiltinFunctionValue{
"exit",
&FunctionSignature{
[]TypeSignature{&NumberSignature{}},
&NilSignature{},
},
func(vm *VM, this Value, args []Value) (Value, error) {
os.Exit(int(args[0].(*NumberValue).Number))
return &NilValue{}, nil
},
nil,
},
}
func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
@ -417,29 +488,34 @@ func (vm *VM) Next() bool {
vm.stack.Push(vm.ReadConstant())
case InstructionAdd:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l + r})
case InstructionSub:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l - r})
case InstructionMul:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l * r})
case InstructionDiv:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{l / r})
case InstructionNegate:
v := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&NumberValue{-v})
case InstructionEquals:
vm.stack.Push(
&BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())},
@ -451,40 +527,40 @@ func (vm *VM) Next() bool {
)
case InstructionNot:
b := vm.stack.Pop().(*BoolValue).bool
b := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{!b})
case InstructionAnd:
r := vm.stack.Pop().(*BoolValue).bool
l := vm.stack.Pop().(*BoolValue).bool
r := vm.stack.Pop().(*BoolValue).Boolean
l := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{l && r})
case InstructionOr:
r := vm.stack.Pop().(*BoolValue).bool
l := vm.stack.Pop().(*BoolValue).bool
r := vm.stack.Pop().(*BoolValue).Boolean
l := vm.stack.Pop().(*BoolValue).Boolean
vm.stack.Push(&BoolValue{l || r})
case InstructionLess:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l < r})
case InstructionLessOrEqual:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l <= r})
case InstructionGreater:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l > r})
case InstructionGreaterOrEqual:
r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(*NumberValue).float64
r := vm.stack.Pop().(*NumberValue).Number
l := vm.stack.Pop().(*NumberValue).Number
vm.stack.Push(&BoolValue{l >= r})
@ -503,7 +579,7 @@ func (vm *VM) Next() bool {
for i := len(f.Params) - 1; i >= 0; i-- {
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
vm.stack.items[p] = &VariableValue{
f.Params[i].name,
f.Params[i].Name,
vm.stack.items[p],
vm.scope,
}
@ -543,12 +619,12 @@ func (vm *VM) Next() bool {
case InstructionJumpFalse:
n := vm.NextU16()
if !vm.stack.Pop().(*BoolValue).bool {
if !vm.stack.Pop().(*BoolValue).Boolean {
vm.ip += Pos(n)
}
case InstructionGetLocal:
name := vm.GetConstant(vm.NextByte()).(*StringValue).string
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
if v == nil {
@ -560,7 +636,7 @@ func (vm *VM) Next() bool {
case InstructionSetLocal:
value := vm.stack.Pop().(Value)
name := vm.GetConstant(vm.NextByte()).(*StringValue).string
name := vm.GetConstant(vm.NextByte()).(*StringValue).Text
v := vm.getVar(name)
@ -568,19 +644,19 @@ func (vm *VM) Next() bool {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
}
v.value = value
v.value = value.Clone()
case InstructionDeclareLocal:
vm.addVar(
vm.GetConstant(vm.NextByte()).(*StringValue).string,
vm.stack.Pop().(Value),
vm.GetConstant(vm.NextByte()).(*StringValue).Text,
vm.stack.Pop().Clone(),
)
case InstructionGetGlobal:
vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string])
vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text])
case InstructionSetGlobal:
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string] = vm.stack.Pop()
vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).Text] = vm.stack.Pop()
case InstructionTrue:
vm.stack.Push(&BoolValue{true})
@ -594,18 +670,22 @@ func (vm *VM) Next() bool {
case InstructionFormList:
n := int(vm.NextU16())
items := make([]Value, n+1)
for i := 0; i <= n; i++ {
items[n-i] = vm.stack.Pop()
items := make([]Value, n)
for i := n - 1; i >= 0; i-- {
items[i] = vm.stack.Pop()
}
vm.stack.Push(&ListValue{
items,
})
case InstructionNewList:
vm.stack.Push(&ListValue{[]Value{}})
case InstructionAppend:
value := vm.stack.Pop()
list := vm.stack.Pop().(*ListValue)
list.items = append(list.items, value)
list.Items = append(list.Items, value)
vm.stack.Push(list)
case InstructionDescend:
@ -619,8 +699,8 @@ func (vm *VM) Next() bool {
vm.stack.Push(&StringValue{v.String()})
case InstructionStringConcatenation:
r := vm.stack.Pop().(*StringValue).string
l := vm.stack.Pop().(*StringValue).string
r := vm.stack.Pop().(*StringValue).Text
l := vm.stack.Pop().(*StringValue).Text
vm.stack.Push(&StringValue{l + r})
@ -669,7 +749,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
})
for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i].name, args[i])
vm.addVar(f.Params[i].Name, args[i])
}
if f.Parent != nil {

View file

@ -18,7 +18,7 @@ while n <= terms {
tot = tot * 6
# get the absolute value of a number
func abs(x) {
func abs(x: number) number {
if x < 0 {
return -x
}
@ -30,7 +30,7 @@ func abs(x) {
# see: https://en.wikipedia.org/wiki/Newton's_method
# The required accuracy
SQRT_ACC := 0.00000001
func sqrt(x) {
func sqrt(x: number) number {
pg := 0 # previous guess
g := 1 # current guess
@ -45,4 +45,4 @@ func sqrt(x) {
tot = sqrt(tot)
# output the result
write(tot)
write(str(tot))

14
lib/assert.ang Normal file
View file

@ -0,0 +1,14 @@
func assertEqual(a: any, b: any) {
if a != b {
write(format("assertion error: % should (but doesn't) equal %", [a, b]))
exit(1)
}
}
func assertNotEqual(a: any, b: any) {
if a == b {
write(format("assertion error: % shouldn't (but does) equal %", [a, b]))
exit(1)
}
}

View file

@ -68,50 +68,6 @@ func round(x: number) number {
return f
}
# sin(x)
# x: number; an angle in radians
# Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
# TODO: use hashmap with precomputed values and linear interpolation
func sin(x: number) number {
f := 1
x = mod(x, 2*PI)
if x > PI {
x = -x
f = -1
}
# compute sine with a taylor series mock function of sine (valid between -pi and +pi)
tot := x
l := 1
i := 1
s := -1
while i <= 19 {
i = i + 2
l = s * l * x / i / (i-1)
tot = tot + l
s = -s
}
return tot*f
}
# cos(x)
# x: number; an angle in radians
# Get the cosine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
func cos(x: number) number {
# todo
}
# tan(x)
# x: number; an angle in radians
# Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent
func tan(x: number) number {
# todo
}
# mod(x, n)
# x: number; any number
# n: number; the number to divide by
@ -134,23 +90,6 @@ func mod(x: number, n: number) {
return x
}
# ln(x)
# x: number; any number
# Get the approximate value of the natural logarithm
# This function uses newton's method to approximate.
LN_ACC := 0.000000001
func ln(x: number) number {
pg := 0
g := 1
while abs(pg - g) > LN_ACC {
pg = g
g = pg + x / exp(pg) - 1
}
return g
}
# sm_exp(x)
# x: number; any number between 0 and 1
# Get an approximate value of e raised to the power of x.
@ -204,3 +143,64 @@ func exp(x: number) number {
func pow(x: number, p: number) number {
return exp(p*ln(x))
}
# ln(x)
# x: number; any number
# Get the approximate value of the natural logarithm
# This function uses newton's method to approximate.
LN_ACC := 0.000000001
func ln(x: number) number {
pg := 0
g := 1
while abs(pg - g) > LN_ACC {
pg = g
g = pg + x / exp(pg) - 1
}
return g
}
# sin(x)
# x: number; an angle in radians
# Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
# TODO: use hashmap with precomputed values and linear interpolation
func sin(x: number) number {
f := 1
x = mod(x, 2*PI)
if x > PI {
x = PI - x
f = -1
}
# compute sine with a taylor series mock function of sine (valid between -pi and +pi)
tot := x
l := 1
i := 1
s := -1
while i <= 19 {
i = i + 2
l = s * l * x / i / (i-1)
tot = tot + l
s = -s
}
return tot*f
}
# cos(x)
# x: number; an angle in radians
# Get the cosine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
func cos(x: number) number {
# todo
}
# tan(x)
# x: number; an angle in radians
# Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent
func tan(x: number) number {
# todo
}

View file

@ -1,22 +1,48 @@
#!/bin/zsh
echo '== Building CLI =='
cd cli
go build .
cd ..
echo '== Testing anglais =='
for file in ./tests/*.ang; do
echo "-- Test-running file $file --"
if ! ./cli/cli run "$file"; then
echo "-- Error --"
echo '=== Building CLI ==='
(
cd cli || exit 1
if ! go build .; then
echo "=== Had error building CLI ==="
exit 1
else
echo "-- Success --"
echo "=+= Successfully built CLI =+="
fi
)
echo "=== Running go core tests ==="
(
cd core || exit 1
if ! go test .; then
echo "=x= Core testing failed =x= "
exit 1
else
echo "=+= Successfully ran core tests =+="
fi
)
errors=()
echo '=== Testing anglais ==='
# read files
for file in $(find tests -type f); do
echo "-v- Test-running file $file -v-"
if ! ./cli/cli run "$file"; then
echo "-x- Error -x-"
errors+=("$file")
else
echo "-+- Success -+-"
fi
done
echo '== Successfully ran all tests =='
if [ 0 -ne "$(wc -w <<< "${errors[@]}")" ]; then
echo "== Errors occured while executing =="
echo "erroring files: $(printf '%s ' "${errors[@]}")"
exit 1
else
echo '== Successfully ran all tests =='
fi

3
tests/hex.ang Normal file
View file

@ -0,0 +1,3 @@
assertEq(0x00, 0)
assertEq(0xFF, 255)

View file

@ -2,8 +2,7 @@
fibonacci_numbers := [
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
46368, 75025, 121393, 196418, 317811, 514229, 832040,
1346269, 2178309, 3524578, 5702887, 9227465, 14930352
46368, 75025, 121393, 196418, 317811, 514229, 832040
]
func fib(n: number) number {
@ -16,11 +15,12 @@ func fib(n: number) number {
n := 0
while n < fibonacci_numbers.length() {
print("_")
print("-")
n = n + 1
}
write("")
# return to start of line
print(format("%[%D", [char(0x1B), n]))
x := 0
while x < fibonacci_numbers.length() {

13
tests/refs.ang Normal file
View file

@ -0,0 +1,13 @@
list := []
list.append(1)
list.append(2)
assertEq(list, [1, 2])
other := list
other.append(3)
assertEq(other, [1, 2, 3])
assertEq(list, [1, 2])