Basic type system working

This commit is contained in:
Neemek 2025-03-16 22:46:49 +01:00
parent 3f260e7ffd
commit 84cc845748
Signed by: neemek
GPG key ID: 28360A8951CD0E9B
19 changed files with 900 additions and 131 deletions

View file

@ -22,13 +22,22 @@ func GetAllTestCases() map[string]AllTestCase {
}, },
}, },
"func": { "func": {
"func sum(a, b) {\n\treturn a + b\n}\nsum(1, 2)", "func sum(a: number, b: number) {\n\treturn a + b\n}\nsum(1, 2)",
[]Value{ []Value{
&VariableValue{ &VariableValue{
"sum", "sum",
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: &Chunk{ Chunk: &Chunk{
Bytecode: []Bytecode{ Bytecode: []Bytecode{
InstructionDescend, InstructionDescend,

View file

@ -1,6 +1,7 @@
package core package core
import ( import (
"errors"
"fmt" "fmt"
) )
@ -20,17 +21,19 @@ type ImportsResolver interface {
} }
type LocalVariable struct { type LocalVariable struct {
name string name string
scope int signature TypeSignature
scope int
} }
func NewCompiler() *Compiler { func NewCompiler() *Compiler {
c := &Compiler{ c := &Compiler{
Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)), NewChunk(make([]Bytecode, 0), make([]Value, 0)),
ip: 0, 0,
scope: 0, 0,
stack: NewStack[LocalVariable](256), make(map[string]Node),
imports: make(map[string]Node), nil,
NewStack[LocalVariable](256),
} }
return c return c
@ -253,8 +256,8 @@ func (c *Compiler) Compile(tree Node) error {
// reset instruction pointer (ip) // reset instruction pointer (ip)
c.ip = 0 c.ip = 0
for _, p := range n.params { for _, p := range n.parameters {
c.registerVar(p) c.registerVar(p.name, p.signature)
} }
err := c.Compile(n.logic) err := c.Compile(n.logic)
@ -268,7 +271,7 @@ func (c *Compiler) Compile(tree Node) error {
mc.Constants[fi] = &FunctionValue{ mc.Constants[fi] = &FunctionValue{
n.name, n.name,
n.params, n.parameters,
c.Chunk, c.Chunk,
nil, nil,
} }
@ -365,6 +368,149 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
return nil return nil
} }
func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
switch tree.Type() {
case StringNodeType:
return &StringSignature{}, nil
case NumberNodeType:
return &NumberSignature{}, nil
case ReferenceNodeType:
return c.getVarSignature(tree.(*ReferenceNode).name)
case BooleanNodeType:
return &BooleanSignature{}, nil
case NilNodeType:
return &NilSignature{}, nil
case ListNodeType:
return &ListSignature{}, nil
case BinaryNodeType:
n := tree.(*BinaryNode)
l, err := c.deduceSignature(n.Left)
if err != nil {
return nil, err
}
r, err := c.deduceSignature(n.Right)
if err != nil {
return nil, err
}
if l != r {
return nil, errors.New(fmt.Sprintf("cannot perform binary %s on different types: %s and %s", n.BinaryOperation, l, r))
}
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 &NumberSignature{}, nil
case BinaryAddition:
switch l.Type() {
case TypeString:
return &StringSignature{}, nil
case TypeNumber:
return &NumberSignature{}, nil
default:
return nil, errors.New(fmt.Sprintf("cannot perform binary addition on type %s", l))
}
case BinaryAnd, BinaryOr:
if l.Type() != TypeBoolean {
return nil, errors.New(fmt.Sprintf("cannot perform binary %s on type %s", l, n.BinaryOperation))
}
return &BooleanSignature{}, nil
case BinaryEquality, BinaryInequality:
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 &BooleanSignature{}, nil
}
case AccessNodeType:
n := tree.(*AccessNode)
sig, err := c.deduceSignature(n.source)
if err != nil {
return nil, err
}
switch sig.Type() {
case TypeString, TypeList:
case TypeObject:
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))
}
case CallNodeType:
n := tree.(*CallNode)
sig, err := c.deduceSignature(n.source)
if err != nil {
return nil, err
}
if sig.Type() != TypeFunction {
return nil, errors.New(fmt.Sprintf("cannot call value of type %s", sig.Type()))
}
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)))
}
// type check arguments
for i, arg := range n.args {
sig, err := c.deduceSignature(arg)
if err != nil {
return nil, err
}
if !sig.Matches(f.in[i]) {
}
}
return f.out, nil
case FunctionNodeType:
n := tree.(*FunctionNode)
sigs := make([]TypeSignature, len(n.parameters))
for i, p := range n.parameters {
sigs[i] = p.signature
}
return &FunctionSignature{
sigs,
n.yield,
}, nil
default:
panic("unhandled default case")
}
panic(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()))
}
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
}
}
return nil, errors.New(fmt.Sprintf("variable %s not defined", name))
}
func (c *Compiler) getVar(name string) { func (c *Compiler) getVar(name string) {
if c.isGlobal(name) { if c.isGlobal(name) {
c.add(InstructionGetGlobal) c.add(InstructionGetGlobal)
@ -387,7 +533,11 @@ func (c *Compiler) setVar(name string, value Node, declare bool) error {
if declare { if declare {
c.add(InstructionDeclareLocal) c.add(InstructionDeclareLocal)
c.registerVar(name) t, err := c.deduceSignature(value)
if err != nil {
return err
}
c.registerVar(name, t)
} else { } else {
c.add(InstructionSetLocal) c.add(InstructionSetLocal)
} }
@ -400,9 +550,10 @@ func (c *Compiler) setVar(name string, value Node, declare bool) error {
} }
// keep track that a variable is declared but doesn't necessarily have a deducible type // keep track that a variable is declared but doesn't necessarily have a deducible type
func (c *Compiler) registerVar(name string) { func (c *Compiler) registerVar(name string, t TypeSignature) {
c.stack.Push(LocalVariable{ c.stack.Push(LocalVariable{
name, name,
t,
int(c.scope), int(c.scope),
}) })
} }

View file

@ -231,7 +231,17 @@ func GetCompileTestData() map[string]CompileTestData {
"sum", "sum",
&FunctionNode{ &FunctionNode{
"sum", "sum",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -254,7 +264,16 @@ func GetCompileTestData() map[string]CompileTestData {
&FunctionValue{ &FunctionValue{
"sum", "sum",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionDescend, InstructionDescend,
@ -281,7 +300,8 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&FunctionNode{ &FunctionNode{
"a", "a",
[]string{}, []FunctionParameter{},
&NilSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&AssignNode{ &AssignNode{
@ -311,7 +331,7 @@ func GetCompileTestData() map[string]CompileTestData {
"a", "a",
&FunctionValue{ &FunctionValue{
"a", "a",
[]string{}, []FunctionParameter{},
NewChunk( NewChunk(
[]Bytecode{ []Bytecode{
InstructionDescend, InstructionDescend,

View file

@ -53,6 +53,7 @@ const (
TokenComma TokenComma
TokenDot TokenDot
TokenColon
TokenAssign TokenAssign
TokenDeclare TokenDeclare
@ -153,6 +154,8 @@ func (t TokenType) String() string {
return "close bracket" return "close bracket"
case TokenImport: case TokenImport:
return "import" return "import"
case TokenColon:
return "colon"
} }
return "UNDEFINED TOKENTYPE STRING CONVERSION" return "UNDEFINED TOKENTYPE STRING CONVERSION"
@ -234,11 +237,11 @@ func (l *Lexer) NextToken() (Token, error) {
case '.': case '.':
return l.makeToken(TokenDot), nil return l.makeToken(TokenDot), nil
case ':': case ':':
if !l.accept('=') { if l.accept('=') {
return l.makeToken(TokenError), errors.New("malformed token (got ':', expected '=' to follow)") return l.makeToken(TokenDeclare), nil
} }
return l.makeToken(TokenDeclare), nil return l.makeToken(TokenColon), nil
case '!': case '!':
if l.accept('=') { if l.accept('=') {
return l.makeToken(TokenBangEquals), nil return l.makeToken(TokenBangEquals), nil

View file

@ -330,9 +330,15 @@ func (n CallNode) String() string {
// FunctionNode definition of function // FunctionNode definition of function
type FunctionNode struct { type FunctionNode struct {
name string name string
params []string parameters []FunctionParameter
logic Node yield TypeSignature
logic Node
}
type FunctionParameter struct {
name string
signature TypeSignature
} }
func (n FunctionNode) Type() NodeType { func (n FunctionNode) Type() NodeType {

View file

@ -243,6 +243,11 @@ func (p *Parser) factor() (Node, error) {
return nil, err return nil, err
} }
sig, err := p.parseSignature()
if err != nil {
return nil, err
}
b, err := p.block(false) b, err := p.block(false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,6 +256,7 @@ func (p *Parser) factor() (Node, error) {
return &FunctionNode{ return &FunctionNode{
"*", "*",
params, params,
sig,
b, b,
}, nil }, nil
@ -564,6 +570,8 @@ func (p *Parser) statement() (Node, error) {
return nil, err return nil, err
} }
yield, err := p.parseSignature()
b, err := p.block(false) b, err := p.block(false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -574,6 +582,7 @@ func (p *Parser) statement() (Node, error) {
&FunctionNode{ &FunctionNode{
name, name,
params, params,
yield,
b, b,
}, },
true, true,
@ -678,15 +687,27 @@ func (p *Parser) parseArgs() ([]Node, error) {
} }
// parseParams parse parameters and parentheses // parseParams parse parameters and parentheses
func (p *Parser) parseParams() ([]string, error) { func (p *Parser) parseParams() ([]FunctionParameter, error) {
if err := p.expect(TokenOpenParenthesis); err != nil { if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err return nil, err
} }
params := make([]string, 0) params := make([]FunctionParameter, 0)
if p.accept(TokenName) { if p.accept(TokenName) {
name := (*p.prev).Lexeme name := (*p.prev).Lexeme
params = append(params, name) if err := p.expect(TokenColon); err != nil {
return nil, err
}
t, err := p.parseSignature()
if err != nil {
return nil, err
}
params = append(params, FunctionParameter{
name,
t,
})
for !p.accept(TokenCloseParenthesis) { for !p.accept(TokenCloseParenthesis) {
if err := p.expect(TokenComma); err != nil { if err := p.expect(TokenComma); err != nil {
return nil, err return nil, err
@ -695,7 +716,19 @@ func (p *Parser) parseParams() ([]string, error) {
return nil, err return nil, err
} }
name = (*p.prev).Lexeme name = (*p.prev).Lexeme
params = append(params, name) if err := p.expect(TokenColon); err != nil {
return nil, err
}
t, err := p.parseSignature()
if err != nil {
return nil, err
}
params = append(params, FunctionParameter{
name,
t,
})
} }
} else { } else {
if err := p.expect(TokenCloseParenthesis); err != nil { if err := p.expect(TokenCloseParenthesis); err != nil {
@ -705,3 +738,70 @@ func (p *Parser) parseParams() ([]string, error) {
return params, nil return params, nil
} }
func (p *Parser) parseSignature() (TypeSignature, error) {
if p.accept(TokenFunc) {
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
var in []TypeSignature
for !p.accept(TokenCloseParenthesis) && (len(in) == 0 || p.accept(TokenComma)) {
sig, err := p.parseSignature()
if err != nil {
return nil, err
}
in = append(in, sig)
}
if err := p.expect(TokenCloseParenthesis); err != nil {
return nil, err
}
out, err := p.parseSignature()
if err != nil {
return nil, err
}
return &FunctionSignature{
in,
out,
}, nil
}
if err := p.expect(TokenName); err != nil {
return nil, err
}
name := (*p.prev).Lexeme
switch name {
case "string":
return &StringSignature{}, nil
case "number":
return &NumberSignature{}, nil
case "boolean":
return &BooleanSignature{}, nil
case "list":
if err := p.expect(TokenOpenBracket); err != nil {
return nil, err
}
contents, err := p.parseSignature()
if err != nil {
return nil, err
}
if err := p.expect(TokenCloseBracket); err != nil {
return nil, err
}
return &ListSignature{
contents,
}, nil
}
panic("unsupported type: " + name)
}

View file

@ -1,7 +1,9 @@
package core package core
import ( import (
"fmt"
"strconv" "strconv"
"strings"
"testing" "testing"
) )
@ -338,9 +340,14 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenFunc, 3, 4, 0, "func"), NewToken(TokenFunc, 3, 4, 0, "func"),
NewToken(TokenOpenParenthesis, 7, 1, 0, "("), NewToken(TokenOpenParenthesis, 7, 1, 0, "("),
NewToken(TokenName, 8, 1, 0, "a"), NewToken(TokenName, 8, 1, 0, "a"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenComma, 9, 1, 0, ","), NewToken(TokenComma, 9, 1, 0, ","),
NewToken(TokenName, 10, 1, 0, "b"), NewToken(TokenName, 10, 1, 0, "b"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"), NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenOpenBrace, 12, 1, 1, "{"), NewToken(TokenOpenBrace, 12, 1, 1, "{"),
NewToken(TokenReturn, 13, 6, 1, "return"), NewToken(TokenReturn, 13, 6, 1, "return"),
@ -357,7 +364,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
&FunctionNode{ &FunctionNode{
"*", "*",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -404,7 +421,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
&FunctionNode{ &FunctionNode{
"a", "a",
[]string{"a", "b"}, []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{ &BlockNode{
[]Node{ []Node{
&ReturnNode{ &ReturnNode{
@ -642,15 +669,17 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
t.Logf("Function node names match (%s)", n.name) t.Logf("Function node names match (%s)", n.name)
} }
if len(n.params) != len(m.params) { if len(n.parameters) != len(m.parameters) {
t.Fatalf("Function node parameters count does not match (%d and %d)", len(n.params), len(m.params)) t.Fatalf("Function node parameters count does not match (%d and %d)", len(n.parameters), len(m.parameters))
} else { } else {
t.Logf("Function node parameters count is equal (%d) ", len(n.params)) t.Logf("Function node parameters count is equal (%d) ", len(n.parameters))
} }
for i, p := range m.params { for i, p := range m.parameters {
if n.params[i] != p { if !n.parameters[i].signature.Matches(p.signature) {
t.Errorf("Function node parameter %d does not match: %s and %s", i, p, m.params) 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 { } else {
t.Logf("Function node parameter %d matches (%s)", i, p) t.Logf("Function node parameter %d matches (%s)", i, p)
} }
@ -665,6 +694,101 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
} }
} }
func SerializeTokens(tokens []Token) string {
out := strings.Builder{}
level := 0
for _, token := range tokens {
switch token.Type {
case TokenPlus:
out.WriteString(" + ")
case TokenMinus:
out.WriteString(" - ")
case TokenStar:
out.WriteString("*")
case TokenSlash:
out.WriteString("/")
case TokenBang:
out.WriteString("!")
case TokenSemicolon:
out.WriteString(";")
case TokenNumber:
out.WriteString(token.Lexeme)
case TokenString:
out.WriteString(fmt.Sprintf("\"%s\"", token.Lexeme))
case TokenName:
out.WriteString(token.Lexeme)
case TokenOpenParenthesis:
out.WriteString("(")
case TokenCloseParenthesis:
out.WriteString(")")
case TokenOpenBracket:
out.WriteString("[")
case TokenCloseBracket:
out.WriteString("]")
case TokenOpenBrace:
out.WriteString("{")
level = level + 1
case TokenCloseBrace:
out.WriteString("}")
level = level - 1
case TokenTrue:
out.WriteString("true")
case TokenFalse:
out.WriteString("false")
case TokenNil:
out.WriteString("nil")
case TokenFunc:
out.WriteString("func")
case TokenReturn:
out.WriteString("return ")
case TokenWhile:
out.WriteString("while ")
case TokenVar:
out.WriteString("var ")
case TokenIf:
out.WriteString("if ")
case TokenElse:
out.WriteString(" else ")
case TokenImport:
out.WriteString("import ")
case TokenComma:
out.WriteString(", ")
case TokenDot:
out.WriteString(".")
case TokenColon:
out.WriteString(": ")
case TokenAssign:
out.WriteString(" = ")
case TokenDeclare:
out.WriteString(" := ")
case TokenBangEquals:
out.WriteString(" != ")
case TokenEquals:
out.WriteString(" == ")
case TokenGreaterThan:
out.WriteString(" > ")
case TokenLessThan:
out.WriteString(" < ")
case TokenGreaterThanOrEqual:
out.WriteString(" >= ")
case TokenLessThanOrEqual:
out.WriteString(" <= ")
case TokenDoubleAmpersand:
out.WriteString(" && ")
case TokenDoublePipe:
out.WriteString(" || ")
case TokenBreakpoint:
out.WriteString("breakpoint")
case TokenEOF:
out.WriteString(fmt.Sprintf("<error: \"%s\">", token.Lexeme))
case TokenError:
}
}
return out.String()
}
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {
t.Logf("Getting test data") t.Logf("Getting test data")
tokenData := GetTokenTestData() tokenData := GetTokenTestData()
@ -682,7 +806,7 @@ func TestParser_Parse(t *testing.T) {
tree, err := p.Parse() tree, err := p.Parse()
if err != nil { if err != nil {
t.Fatalf("Unexpected error(s): %s", err.(*ParsingError).Format([]rune{})) t.Fatalf("Unexpected error(s): %s", err.(*ParsingError).Format([]rune(SerializeTokens(data.tokens))))
} }
t.Logf("Checking parsed tree") t.Logf("Checking parsed tree")

224
core/types.go Normal file
View file

@ -0,0 +1,224 @@
package core
import "fmt"
type Type int
const (
TypeString Type = iota
TypeNumber
TypeBoolean
TypeNil
TypeList
TypeObject
TypeFunction
TypeAny
)
func (t Type) String() string {
switch t {
case TypeString:
return "String"
case TypeNumber:
return "Number"
case TypeBoolean:
return "Boolean"
case TypeNil:
return "Nil"
case TypeList:
return "List"
case TypeObject:
return "Object"
case TypeFunction:
return "Function"
}
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:
return &StringSignature{}
case *NumberValue:
return &NumberSignature{}
case *BoolValue:
return &BooleanSignature{}
case *ListValue:
return &ListSignature{}
case *ObjectValue:
return &ObjectSignature{}
case *FunctionValue:
return &FunctionSignature{}
case *BuiltinFunctionValue:
return t.Signature
}
panic(fmt.Sprintf("unknown value; cannot get signature of %s", v))
}
type TypeSignature interface {
Type() Type
// Matches check if this type signature matches another.
Matches(TypeSignature) bool
}
type NilSignature struct{}
func (*NilSignature) Type() Type {
return TypeNil
}
func (*NilSignature) Matches(other TypeSignature) bool {
return other.Type() == TypeAny || other.Type() == TypeNil
}
type StringSignature struct{}
func (*StringSignature) Type() Type {
return TypeString
}
func (*StringSignature) Matches(other TypeSignature) bool {
return other.Type() == TypeAny || other.Type() == TypeString
}
type NumberSignature struct{}
func (*NumberSignature) Type() Type {
return TypeNumber
}
func (*NumberSignature) Matches(other TypeSignature) bool {
return other.Type() == TypeAny || other.Type() == TypeNumber
}
type BooleanSignature struct{}
func (*BooleanSignature) Type() Type {
return TypeBoolean
}
func (*BooleanSignature) Matches(other TypeSignature) bool {
return other.Type() == TypeAny || other.Type() == TypeBoolean
}
type ListSignature struct {
contents TypeSignature
}
func (*ListSignature) Type() Type {
return TypeList
}
func (s *ListSignature) Matches(other TypeSignature) bool {
return other.Type() == TypeAny || other.Type() == TypeList && other.(*ListSignature).contents.Matches(s.contents)
}
type ObjectSignature struct {
members map[string]TypeSignature
}
func (*ObjectSignature) Type() Type {
return TypeObject
}
func (s *ObjectSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeAny {
return true
}
if other.Type() != TypeObject {
return false
}
o := other.(*ObjectSignature)
if len(o.members) != len(s.members) {
return false
}
for name, member := range s.members {
v, ok := o.members[name]
if !ok {
return false
}
if !v.Matches(member) {
return false
}
}
return true
}
type FunctionSignature struct {
in []TypeSignature
out TypeSignature
}
func (*FunctionSignature) Type() Type {
return TypeFunction
}
func (s *FunctionSignature) Matches(other TypeSignature) bool {
if other.Type() == TypeAny {
return true
}
if other.Type() != TypeFunction {
return false
}
f := other.(*FunctionSignature)
if !s.out.Matches(f.out) {
return false
}
if len(f.in) != len(s.in) {
return false
}
for i, p := range s.in {
v := f.in[i]
if !p.Matches(v) {
return false
}
}
return true
}
type AnySignature struct{}
func (AnySignature) Type() Type {
return TypeAny
}
func (AnySignature) Matches(_ TypeSignature) bool {
return true
}

View file

@ -203,12 +203,15 @@ func (v *ObjectValue) Equals(other Value) bool {
var ObjectPrototype = map[string]Value{ var ObjectPrototype = map[string]Value{
"set": &BuiltinFunctionValue{ "set": &BuiltinFunctionValue{
"set", "set",
[]string{"property", "value"}, &FunctionSignature{
func(vm *VM, _this Value, params map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}, &ListSignature{}},
&NilSignature{},
},
func(vm *VM, _this Value, params []Value) (Value, error) {
this := _this.(*ObjectValue) this := _this.(*ObjectValue)
p := params["property"] p := params[1]
v, ok := params["value"].(*StringValue) v, ok := params[0].(*StringValue)
if !ok { if !ok {
return nil, errors.New("property is not a string") return nil, errors.New("property is not a string")
} }
@ -282,10 +285,13 @@ func (v *StringValue) Equals(other Value) bool {
var StringPrototype = map[string]*BuiltinFunctionValue{ var StringPrototype = map[string]*BuiltinFunctionValue{
"split": { "split": {
"split", "split",
[]string{"seperator"}, &FunctionSignature{
func(vm *VM, this Value, m map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}},
&NilSignature{},
},
func(vm *VM, this Value, v []Value) (Value, error) {
str := this.(*StringValue).String() str := this.(*StringValue).String()
sep := m["seperator"].(*StringValue).String() sep := v[0].(*StringValue).String()
var out []string var out []string
tmp := strings.Builder{} tmp := strings.Builder{}
@ -361,19 +367,27 @@ func (v *ListValue) Equals(other Value) bool {
var ListPrototype = map[string]*BuiltinFunctionValue{ var ListPrototype = map[string]*BuiltinFunctionValue{
"append": { "append": {
"append", "append",
[]string{"item"}, &FunctionSignature{
func(_ *VM, this Value, p map[string]Value) (Value, error) { []TypeSignature{&AnySignature{}},
this.(*ListValue).items = append(this.(*ListValue).items, p["item"]) &NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
this.(*ListValue).items = append(this.(*ListValue).items, v[0])
return &NilValue{}, nil return &NilValue{}, nil
}, },
nil, nil,
}, },
"at": { "at": {
"at", "at",
[]string{"index"}, &FunctionSignature{
func(_ *VM, this Value, p map[string]Value) (Value, error) { []TypeSignature{
&NumberSignature{},
},
&AnySignature{},
},
func(_ *VM, this Value, p []Value) (Value, error) {
items := this.(*ListValue).items items := this.(*ListValue).items
index := int(p["index"].(*NumberValue).float64) index := int(p[0].(*NumberValue).float64)
if index >= len(items) { if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index)) return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -385,19 +399,32 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
}, },
"length": { "length": {
"length", "length",
[]string{}, &FunctionSignature{
func(_ *VM, this Value, p map[string]Value) (Value, error) { []TypeSignature{},
&NumberSignature{},
},
func(_ *VM, this Value, _ []Value) (Value, error) {
return GoToValue(len(this.(*ListValue).items)), nil return GoToValue(len(this.(*ListValue).items)), nil
}, },
nil, nil,
}, },
"map": { "map": {
"map", "map",
[]string{"f"}, &FunctionSignature{
func(vm *VM, value Value, m map[string]Value) (Value, error) { []TypeSignature{
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
},
&AnySignature{},
},
},
&ListSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
list := value.(*ListValue) list := value.(*ListValue)
v := m["f"] v := m[0]
var f Value var f Value
f, ok := v.(*FunctionValue) f, ok := v.(*FunctionValue)
if !ok { if !ok {
@ -425,11 +452,23 @@ var ListPrototype = map[string]*BuiltinFunctionValue{
}, },
"reduce": { "reduce": {
"reduce", "reduce",
[]string{"f", "start"}, &FunctionSignature{
func(vm *VM, value Value, m map[string]Value) (Value, error) { []TypeSignature{
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&AnySignature{},
},
&AnySignature{},
},
&AnySignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
list := value.(*ListValue) list := value.(*ListValue)
f := m["f"] f := m[0]
sum := m["start"] sum := m[1]
for _, v := range list.items { for _, v := range list.items {
result, err := vm.Call(f, []Value{sum, v}) result, err := vm.Call(f, []Value{sum, v})
@ -455,7 +494,7 @@ func (v *ListValue) Get(key string) (Value, error) {
type FunctionValue struct { type FunctionValue struct {
Name string Name string
Params []string Params []FunctionParameter
Chunk *Chunk Chunk *Chunk
Parent Value Parent Value
} }
@ -483,10 +522,10 @@ func (v *FunctionValue) Get(_ string) (Value, error) {
} }
type BuiltinFunctionValue struct { type BuiltinFunctionValue struct {
Name string Name string
Parameters []string Signature *FunctionSignature
F func(*VM, Value, map[string]Value) (Value, error) F func(*VM, Value, []Value) (Value, error)
Parent Value Parent Value
} }
func (v *BuiltinFunctionValue) Type() ValueType { func (v *BuiltinFunctionValue) Type() ValueType {

View file

@ -60,19 +60,10 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name) t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name)
} }
if len(n.Parameters) != len(m.Parameters) { if !n.Signature.Matches(m.Signature) {
t.Errorf("builtin function parameter count mismatch: got %v, want %v", n.Parameters, m.Parameters) t.Errorf("builtin function parameter count mismatch: got %v, want %v", n, m)
} }
for i, v := range n.Parameters {
if v != m.Parameters[i] {
t.Errorf("builtin function parameter %d mismatch: got %v, want %v", i, v, m.Parameters[i])
}
}
if &n.F != &m.F {
t.Errorf("builtin function f mismatch: got %v, want %v", &n.F, &m.F)
}
case VariableValueType: case VariableValueType:
n := got.(*VariableValue) n := got.(*VariableValue)
m := want.(*VariableValue) m := want.(*VariableValue)

View file

@ -287,38 +287,56 @@ type Call struct {
var DefaultGlobals = map[string]Value{ var DefaultGlobals = map[string]Value{
"write": &BuiltinFunctionValue{ "write": &BuiltinFunctionValue{
"write", // always remember where you come from... "write", // always remember where you come from...
[]string{"value"}, &FunctionSignature{
func(_ *VM, this Value, v map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}},
println(v["value"].String()) &NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return nil, nil return nil, nil
}, },
nil, nil,
}, },
"print": &BuiltinFunctionValue{ "print": &BuiltinFunctionValue{
"print", "print",
[]string{"value"}, &FunctionSignature{
func(_ *VM, this Value, v map[string]Value) (Value, error) { []TypeSignature{&StringSignature{}},
print(v["value"].String()) &NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return nil, nil return nil, nil
}, },
nil, nil,
}, },
"format": &BuiltinFunctionValue{ "format": &BuiltinFunctionValue{
"format", "format",
[]string{"format_string", "values"}, &FunctionSignature{
func(vm *VM, value Value, m map[string]Value) (Value, error) { []TypeSignature{
valuies := m["values"].(*ListValue).items &StringSignature{},
&StringSignature{},
},
&StringSignature{},
},
func(vm *VM, value Value, m []Value) (Value, error) {
valuies := m[1].(*ListValue).items
return GoToValue(fmt.Sprintf(m["format_string"].String(), valuies)), nil return GoToValue(fmt.Sprintf(m[0].String(), valuies)), nil
}, },
nil, nil,
}, },
"assertEq": &BuiltinFunctionValue{ "assertEq": &BuiltinFunctionValue{
"assertEq", "assertEq",
[]string{"a", "b"}, &FunctionSignature{
func(vm *VM, this Value, params map[string]Value) (Value, error) { []TypeSignature{
a := params["a"] &AnySignature{},
b := params["b"] &AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if !a.Equals(b) { if !a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b)) return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
@ -330,10 +348,16 @@ var DefaultGlobals = map[string]Value{
}, },
"assertNotEq": &BuiltinFunctionValue{ "assertNotEq": &BuiltinFunctionValue{
"assertNotEq", "assertNotEq",
[]string{"a", "b"}, &FunctionSignature{
func(vm *VM, this Value, params map[string]Value) (Value, error) { []TypeSignature{
a := params["a"] &AnySignature{},
b := params["b"] &AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if a.Equals(b) { if a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b)) return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
@ -479,7 +503,7 @@ func (vm *VM) Next() bool {
for i := len(f.Params) - 1; i >= 0; i-- { for i := len(f.Params) - 1; i >= 0; i-- {
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i) p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
vm.stack.items[p] = &VariableValue{ vm.stack.items[p] = &VariableValue{
f.Params[i], f.Params[i].name,
vm.stack.items[p], vm.stack.items[p],
vm.scope, vm.scope,
} }
@ -494,10 +518,10 @@ func (vm *VM) Next() bool {
vm.chunk = f.Chunk vm.chunk = f.Chunk
vm.ip = 0 vm.ip = 0
case *BuiltinFunctionValue: case *BuiltinFunctionValue:
args := map[string]Value{} args := make([]Value, len(f.Signature.in))
for i := len(f.Parameters) - 1; i >= 0; i-- { for i := len(f.Signature.in) - 1; i >= 0; i-- {
args[f.Parameters[i]] = vm.stack.Pop() args[i] = vm.stack.Pop()
} }
v, err := f.F(vm, f.Parent, args) v, err := f.F(vm, f.Parent, args)
@ -645,7 +669,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
}) })
for i := 0; i < len(f.Params); i++ { for i := 0; i < len(f.Params); i++ {
vm.addVar(f.Params[i], args[i]) vm.addVar(f.Params[i].name, args[i])
} }
if f.Parent != nil { if f.Parent != nil {
@ -667,13 +691,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
return vm.stack.Pop(), nil return vm.stack.Pop(), nil
case *BuiltinFunctionValue: case *BuiltinFunctionValue:
argies := map[string]Value{} return f.F(vm, f.Parent, args)
for i, arg := range args {
argies[f.Parameters[i]] = arg
}
return f.F(vm, f.Parent, argies)
} }
return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString())) return nil, errors.New(fmt.Sprintf("value is not a function (%s)", v.DebugString()))

View file

@ -441,8 +441,17 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{1}, &NumberValue{1},
&NumberValue{2}, &NumberValue{2},
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -475,8 +484,17 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{1}, &NumberValue{1},
&NumberValue{2}, &NumberValue{2},
&FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -492,8 +510,13 @@ func GetExecutionTestData() map[string]struct {
), ),
}, },
&FunctionValue{ &FunctionValue{
Name: "square", Name: "square",
Params: []string{"n"}, Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,
@ -513,8 +536,13 @@ func GetExecutionTestData() map[string]struct {
&VariableValue{ &VariableValue{
"square", "square",
&FunctionValue{ &FunctionValue{
Name: "square", Name: "square",
Params: []string{"n"}, Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk( Chunk: NewChunk(
[]Bytecode{ []Bytecode{
InstructionGetLocal, 0, InstructionGetLocal, 0,

36
examples/pi-approx.py Normal file
View file

@ -0,0 +1,36 @@
terms = 100000
tot = 0
n = 1
while n <= terms:
tot = tot + 1 / (n*n)
n = n + 1
tot = tot * 6
# get the absolute value of a number
def abs(x):
if x < 0:
return -x
return x
# calculate an approximation of the square root of tot using
# newton's method.
# see: https://en.wikipedia.org/wiki/Newton's_method
# The required accuracy
SQRT_ACC = 0.00000001
def sqrt(x):
pg = 0 # previous guess
g = 1 # current guess
while abs(pg - g) >= SQRT_ACC:
pg = g
g = (pg + tot/pg)/2
return g
tot = sqrt(tot)
# output the result
print(tot)

16
examples/pøck.ang Normal file
View file

@ -0,0 +1,16 @@
import "math.ang"
func r_x(t) {
return 8*(exp(-t) - t)
}
func r_y(t) {
return 5*(exp(-t) - t)
}
func r(t) {
return format("(%s, %s)", [r_x(t), r_y(t)])
}
write(r(1))
write()

4
lib/util.ang Normal file
View file

@ -0,0 +1,4 @@
func memoize(f) {
}

View file

@ -3,14 +3,14 @@ list := []
x := 1 x := 1
while x <= 1000 { while x <= 1000 {
list.append(x) list.append(x)
assertEq(list.reduce(func(tot, a){ assertEq(list.reduce(func(tot: number, a: number) number {
return tot + a return tot + a
}, 0), x*(x + 1)/2) }, 0), x*(x + 1)/2)
x = x + 1 x = x + 1
} }
func sum(a, b) { func sum(a: number, b: number) number {
return a + b return a + b
} }

View file

@ -7,7 +7,7 @@ E := 2.718281828459045235360287471352
# Get the absolute value of a number. If x is negative, the returned # Get the absolute value of a number. If x is negative, the returned
# value is positive and equal to `-x`. If x is positive or zero, the # value is positive and equal to `-x`. If x is positive or zero, the
# returned value is x. # returned value is x.
func abs(x) { func abs(x: number) number {
# if the number is negative # if the number is negative
if x < 0 { if x < 0 {
# negate it so it's positive # negate it so it's positive
@ -23,7 +23,7 @@ MAX_SQRT_DX := 0.0000001
# x: number # x: number
# Calculate the approximate square root using newton's method until # Calculate the approximate square root using newton's method until
# the accuracy has increased by less than the variable `MAX_SQRT_DX`. # the accuracy has increased by less than the variable `MAX_SQRT_DX`.
func sqrt(x) { func sqrt(x: number) number {
ng := x ng := x
g := 1 g := 1
@ -42,7 +42,7 @@ func sqrt(x) {
# Return the whole number part of the number. if x is a whole number, # Return the whole number part of the number. if x is a whole number,
# the returned value is x. If x is not a whole number, the closest # the returned value is x. If x is not a whole number, the closest
# whole number which is less than or equal to x is returned. # whole number which is less than or equal to x is returned.
func floor(x) { func floor(x: number) number {
# todo # todo
} }
@ -51,14 +51,14 @@ func floor(x) {
# Return the whole number part of the number. if x is a whole number, # Return the whole number part of the number. if x is a whole number,
# the returned value is x. If x is not a whole number, the closest # the returned value is x. If x is not a whole number, the closest
# whole number which is greater than or equal to x is returned. # whole number which is greater than or equal to x is returned.
func ceil(x) { func ceil(x: number) number {
# todo # todo
} }
# round(x) # round(x)
# x: number # x: number
# Return the closest whole number to the value x. # Return the closest whole number to the value x.
func round(x) { func round(x: number) number {
f := floor(x) f := floor(x)
if x - f > 0.5 { if x - f > 0.5 {
@ -72,7 +72,7 @@ func round(x) {
# x: number; an angle in radians # x: number; an angle in radians
# Get the sine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine # 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 # TODO: use hashmap with precomputed values and linear interpolation
func sin(x) { func sin(x: number) number {
f := 1 f := 1
x = mod(x, 2*PI) x = mod(x, 2*PI)
if x > PI { if x > PI {
@ -101,14 +101,14 @@ func sin(x) {
# cos(x) # cos(x)
# x: number; an angle in radians # x: number; an angle in radians
# Get the cosine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine # Get the cosine of an angle (in radians). https://en.wikipedia.org/wiki/Sine_and_cosine
func cos(x) { func cos(x: number) number {
# todo # todo
} }
# tan(x) # tan(x)
# x: number; an angle in radians # x: number; an angle in radians
# Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent # Get the tangent of an angle. https://en.wikipedia.org/wiki/Tangent
func tan(x) { func tan(x: number) number {
# todo # todo
} }
@ -116,7 +116,7 @@ func tan(x) {
# x: number; any number # x: number; any number
# n: number; the number to divide by # n: number; the number to divide by
# Return the rest from a division of x by n. # Return the rest from a division of x by n.
func mod(x, n) { func mod(x: number, n: number) {
if x == 0 { if x == 0 {
return 0 return 0
} }
@ -139,7 +139,7 @@ func mod(x, n) {
# Get the approximate value of the natural logarithm # Get the approximate value of the natural logarithm
# This function uses newton's method to approximate. # This function uses newton's method to approximate.
LN_ACC := 0.000000001 LN_ACC := 0.000000001
func ln(x) { func ln(x: number) number {
pg := 0 pg := 0
g := 1 g := 1
@ -157,7 +157,7 @@ func ln(x) {
# This value is only reasonable if 0<x<1. # This value is only reasonable if 0<x<1.
# It is approximated using the taylor series of e**x. # It is approximated using the taylor series of e**x.
SM_EXP_ACC := 0.00000000001 SM_EXP_ACC := 0.00000000001
func sm_exp(x) { func sm_exp(x: number) number {
p_tot := 0 p_tot := 0
tot := 1 tot := 1
n := 1 n := 1
@ -178,7 +178,7 @@ func sm_exp(x) {
# exp(x) # exp(x)
# x: number; any number # x: number; any number
# Get an approximate value of e raised to the power of x. # Get an approximate value of e raised to the power of x.
func exp(x) { func exp(x: number) number {
n := abs(x) n := abs(x)
tot := 1 tot := 1
while n >= 1 { while n >= 1 {
@ -201,6 +201,6 @@ func exp(x) {
# x: number; any number. The base # x: number; any number. The base
# p: number; the value of the exponent # p: number; the value of the exponent
# Raise any number to any power (x^p) # Raise any number to any power (x^p)
func pow(x, p) { func pow(x: number, p: number) number {
return exp(p*ln(x)) return exp(p*ln(x))
} }

View file

@ -6,7 +6,7 @@ fibonacci_numbers := [
1346269, 2178309, 3524578, 5702887, 9227465, 14930352 1346269, 2178309, 3524578, 5702887, 9227465, 14930352
] ]
func fib(n) { func fib(n: number) number {
if n < 2 { if n < 2 {
return n return n
} }

View file

@ -1,5 +1,5 @@
func sum(a, b) { func sum(a: number, b: number) number {
return a + b return a + b
} }