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 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{
&VariableValue{
"sum",
&FunctionValue{
Name: "sum",
Params: []string{"a", "b"},
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: &Chunk{
Bytecode: []Bytecode{
InstructionDescend,

View file

@ -1,6 +1,7 @@
package core
import (
"errors"
"fmt"
)
@ -20,17 +21,19 @@ type ImportsResolver interface {
}
type LocalVariable struct {
name string
scope int
name string
signature TypeSignature
scope int
}
func NewCompiler() *Compiler {
c := &Compiler{
Chunk: NewChunk(make([]Bytecode, 0), make([]Value, 0)),
ip: 0,
scope: 0,
stack: NewStack[LocalVariable](256),
imports: make(map[string]Node),
NewChunk(make([]Bytecode, 0), make([]Value, 0)),
0,
0,
make(map[string]Node),
nil,
NewStack[LocalVariable](256),
}
return c
@ -253,8 +256,8 @@ func (c *Compiler) Compile(tree Node) error {
// reset instruction pointer (ip)
c.ip = 0
for _, p := range n.params {
c.registerVar(p)
for _, p := range n.parameters {
c.registerVar(p.name, p.signature)
}
err := c.Compile(n.logic)
@ -268,7 +271,7 @@ func (c *Compiler) Compile(tree Node) error {
mc.Constants[fi] = &FunctionValue{
n.name,
n.params,
n.parameters,
c.Chunk,
nil,
}
@ -365,6 +368,149 @@ func (c *Compiler) compileBinary(binary *BinaryNode) error {
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) {
if c.isGlobal(name) {
c.add(InstructionGetGlobal)
@ -387,7 +533,11 @@ func (c *Compiler) setVar(name string, value Node, declare bool) error {
if declare {
c.add(InstructionDeclareLocal)
c.registerVar(name)
t, err := c.deduceSignature(value)
if err != nil {
return err
}
c.registerVar(name, t)
} else {
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
func (c *Compiler) registerVar(name string) {
func (c *Compiler) registerVar(name string, t TypeSignature) {
c.stack.Push(LocalVariable{
name,
t,
int(c.scope),
})
}

View file

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

View file

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

View file

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

View file

@ -243,6 +243,11 @@ func (p *Parser) factor() (Node, error) {
return nil, err
}
sig, err := p.parseSignature()
if err != nil {
return nil, err
}
b, err := p.block(false)
if err != nil {
return nil, err
@ -251,6 +256,7 @@ func (p *Parser) factor() (Node, error) {
return &FunctionNode{
"*",
params,
sig,
b,
}, nil
@ -564,6 +570,8 @@ func (p *Parser) statement() (Node, error) {
return nil, err
}
yield, err := p.parseSignature()
b, err := p.block(false)
if err != nil {
return nil, err
@ -574,6 +582,7 @@ func (p *Parser) statement() (Node, error) {
&FunctionNode{
name,
params,
yield,
b,
},
true,
@ -678,15 +687,27 @@ func (p *Parser) parseArgs() ([]Node, error) {
}
// parseParams parse parameters and parentheses
func (p *Parser) parseParams() ([]string, error) {
func (p *Parser) parseParams() ([]FunctionParameter, error) {
if err := p.expect(TokenOpenParenthesis); err != nil {
return nil, err
}
params := make([]string, 0)
params := make([]FunctionParameter, 0)
if p.accept(TokenName) {
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) {
if err := p.expect(TokenComma); err != nil {
return nil, err
@ -695,7 +716,19 @@ func (p *Parser) parseParams() ([]string, error) {
return nil, err
}
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 {
if err := p.expect(TokenCloseParenthesis); err != nil {
@ -705,3 +738,70 @@ func (p *Parser) parseParams() ([]string, error) {
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
import (
"fmt"
"strconv"
"strings"
"testing"
)
@ -338,9 +340,14 @@ func GetTokenTestData() map[string]TokenTestData {
NewToken(TokenFunc, 3, 4, 0, "func"),
NewToken(TokenOpenParenthesis, 7, 1, 0, "("),
NewToken(TokenName, 8, 1, 0, "a"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenComma, 9, 1, 0, ","),
NewToken(TokenName, 10, 1, 0, "b"),
NewToken(TokenColon, 9, 1, 0, ":"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenCloseParenthesis, 11, 1, 0, ")"),
NewToken(TokenName, 10, 5, 0, "number"),
NewToken(TokenOpenBrace, 12, 1, 1, "{"),
NewToken(TokenReturn, 13, 6, 1, "return"),
@ -357,7 +364,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a",
&FunctionNode{
"*",
[]string{"a", "b"},
[]FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{
[]Node{
&ReturnNode{
@ -404,7 +421,17 @@ func GetTokenTestData() map[string]TokenTestData {
"a",
&FunctionNode{
"a",
[]string{"a", "b"},
[]FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
&NumberSignature{},
&BlockNode{
[]Node{
&ReturnNode{
@ -642,15 +669,17 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) {
t.Logf("Function node names match (%s)", n.name)
}
if len(n.params) != len(m.params) {
t.Fatalf("Function node parameters count does not match (%d and %d)", 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.parameters), len(m.parameters))
} 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 {
if n.params[i] != p {
t.Errorf("Function node parameter %d does not match: %s and %s", i, p, m.params)
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)
} else {
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) {
t.Logf("Getting test data")
tokenData := GetTokenTestData()
@ -682,7 +806,7 @@ func TestParser_Parse(t *testing.T) {
tree, err := p.Parse()
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")

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

View file

@ -287,38 +287,56 @@ type Call struct {
var DefaultGlobals = map[string]Value{
"write": &BuiltinFunctionValue{
"write", // always remember where you come from...
[]string{"value"},
func(_ *VM, this Value, v map[string]Value) (Value, error) {
println(v["value"].String())
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
println(v[0].String())
return nil, nil
},
nil,
},
"print": &BuiltinFunctionValue{
"print",
[]string{"value"},
func(_ *VM, this Value, v map[string]Value) (Value, error) {
print(v["value"].String())
&FunctionSignature{
[]TypeSignature{&StringSignature{}},
&NilSignature{},
},
func(_ *VM, this Value, v []Value) (Value, error) {
print(v[0].String())
return nil, nil
},
nil,
},
"format": &BuiltinFunctionValue{
"format",
[]string{"format_string", "values"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
valuies := m["values"].(*ListValue).items
&FunctionSignature{
[]TypeSignature{
&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,
},
"assertEq": &BuiltinFunctionValue{
"assertEq",
[]string{"a", "b"},
func(vm *VM, this Value, params map[string]Value) (Value, error) {
a := params["a"]
b := params["b"]
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if !a.Equals(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",
[]string{"a", "b"},
func(vm *VM, this Value, params map[string]Value) (Value, error) {
a := params["a"]
b := params["b"]
&FunctionSignature{
[]TypeSignature{
&AnySignature{},
&AnySignature{},
},
&NilSignature{},
},
func(vm *VM, this Value, params []Value) (Value, error) {
a := params[0]
b := params[1]
if a.Equals(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-- {
p := vm.stack.Current - Pos(len(f.Params)) + Pos(i)
vm.stack.items[p] = &VariableValue{
f.Params[i],
f.Params[i].name,
vm.stack.items[p],
vm.scope,
}
@ -494,10 +518,10 @@ func (vm *VM) Next() bool {
vm.chunk = f.Chunk
vm.ip = 0
case *BuiltinFunctionValue:
args := map[string]Value{}
args := make([]Value, len(f.Signature.in))
for i := len(f.Parameters) - 1; i >= 0; i-- {
args[f.Parameters[i]] = vm.stack.Pop()
for i := len(f.Signature.in) - 1; i >= 0; i-- {
args[i] = vm.stack.Pop()
}
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++ {
vm.addVar(f.Params[i], args[i])
vm.addVar(f.Params[i].name, args[i])
}
if f.Parent != nil {
@ -667,13 +691,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
return vm.stack.Pop(), nil
case *BuiltinFunctionValue:
argies := map[string]Value{}
for i, arg := range args {
argies[f.Parameters[i]] = arg
}
return f.F(vm, f.Parent, argies)
return f.F(vm, f.Parent, args)
}
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{2},
&FunctionValue{
Name: "sum",
Params: []string{"a", "b"},
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -475,8 +484,17 @@ func GetExecutionTestData() map[string]struct {
&NumberValue{1},
&NumberValue{2},
&FunctionValue{
Name: "sum",
Params: []string{"a", "b"},
Name: "sum",
Params: []FunctionParameter{
{
"a",
&NumberSignature{},
},
{
"b",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -492,8 +510,13 @@ func GetExecutionTestData() map[string]struct {
),
},
&FunctionValue{
Name: "square",
Params: []string{"n"},
Name: "square",
Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,
@ -513,8 +536,13 @@ func GetExecutionTestData() map[string]struct {
&VariableValue{
"square",
&FunctionValue{
Name: "square",
Params: []string{"n"},
Name: "square",
Params: []FunctionParameter{
{
"n",
&NumberSignature{},
},
},
Chunk: NewChunk(
[]Bytecode{
InstructionGetLocal, 0,