add basic support for tuples
This commit is contained in:
parent
7bb277045c
commit
e03d18c7db
7 changed files with 339 additions and 18 deletions
|
|
@ -193,6 +193,18 @@ func (c *Compiler) compile(tree Node) error {
|
||||||
panic("compile called with nil value")
|
panic("compile called with nil value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.optimize && c.isTreeConstant(tree) {
|
||||||
|
v, err := c.compute(tree)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // this shouldn't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
c.add(InstructionConstant)
|
||||||
|
c.addConstant(v)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
switch tree.Type() {
|
switch tree.Type() {
|
||||||
case StringNodeType:
|
case StringNodeType:
|
||||||
n := tree.(*StringNode)
|
n := tree.(*StringNode)
|
||||||
|
|
@ -236,19 +248,23 @@ func (c *Compiler) compile(tree Node) error {
|
||||||
c.add(InstructionConstant)
|
c.add(InstructionConstant)
|
||||||
c.addConstant(&IntegerValue{tree.(*IntegerNode).value})
|
c.addConstant(&IntegerValue{tree.(*IntegerNode).value})
|
||||||
|
|
||||||
|
case TupleNodeType:
|
||||||
|
n := tree.(*TupleNode)
|
||||||
|
|
||||||
|
for _, n := range n.items {
|
||||||
|
err := c.compile(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.add(InstructionFormTuple)
|
||||||
|
c.addU16(uint16(len(n.items)))
|
||||||
|
|
||||||
case ListNodeType:
|
case ListNodeType:
|
||||||
l := tree.(*ListNode)
|
l := tree.(*ListNode)
|
||||||
|
|
||||||
if len(l.items) == 0 {
|
if len(l.items) == 0 {
|
||||||
c.add(InstructionNewList)
|
c.add(InstructionNewList)
|
||||||
} else if c.optimize && c.isTreeConstant(l) {
|
|
||||||
v, err := c.compute(l)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // this shouldn't happen
|
|
||||||
}
|
|
||||||
|
|
||||||
c.add(InstructionConstant)
|
|
||||||
c.addConstant(v)
|
|
||||||
} else {
|
} else {
|
||||||
for _, n := range l.items {
|
for _, n := range l.items {
|
||||||
err := c.compile(n)
|
err := c.compile(n)
|
||||||
|
|
@ -1001,6 +1017,21 @@ func (c *Compiler) deduceSignature(tree Node) (TypeSignature, error) {
|
||||||
return c.deduceSignature(n.statements[len(n.statements)-1])
|
return c.deduceSignature(n.statements[len(n.statements)-1])
|
||||||
case AssignNodeType:
|
case AssignNodeType:
|
||||||
return c.deduceSignature(tree.(*AssignNode).value)
|
return c.deduceSignature(tree.(*AssignNode).value)
|
||||||
|
case TupleNodeType:
|
||||||
|
n := tree.(*TupleNode)
|
||||||
|
|
||||||
|
var items []TypeSignature
|
||||||
|
for _, i := range n.items {
|
||||||
|
t, err := c.deduceSignature(i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TupleSignature{
|
||||||
|
items,
|
||||||
|
}, nil
|
||||||
default:
|
default:
|
||||||
return nil, c.error(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()), tree)
|
return nil, c.error(fmt.Sprintf("impossible to deduce signature of %s", tree.Type()), tree)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,21 @@ func (p *Parser) accept(tokenType TokenType) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Parser) acceptAll(tokenTypes ...TokenType) bool {
|
||||||
|
if int(p.pos)+len(tokenTypes) > len(p.tokens) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tokenType := range tokenTypes {
|
||||||
|
if p.tokens[int(p.pos)+i].Type != tokenType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pos += Pos(len(tokenTypes))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) expect(tokenType TokenType, reason string) error {
|
func (p *Parser) expect(tokenType TokenType, reason string) error {
|
||||||
if !p.accept(tokenType) {
|
if !p.accept(tokenType) {
|
||||||
return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.curr.Type, reason), p.curr)
|
return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.curr.Type, reason), p.curr)
|
||||||
|
|
@ -813,15 +828,50 @@ func (p *Parser) factor() (Node, error) {
|
||||||
|
|
||||||
case TokenOpenParenthesis:
|
case TokenOpenParenthesis:
|
||||||
p.advance()
|
p.advance()
|
||||||
v, err := p.condition()
|
start := p.prev.Start
|
||||||
|
|
||||||
|
// we're inside an object
|
||||||
|
if p.acceptAll(TokenName, TokenColon) {
|
||||||
|
return nil, p.error("objects are not implemented yet (TBD)", p.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := p.expression(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := p.expect(TokenCloseParenthesis, "an opened parenthesis must be closed"); err != nil {
|
|
||||||
|
if !p.accept(TokenComma) {
|
||||||
|
if err := p.expect(TokenCloseParenthesis, "parenthesis must be closed"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []Node{v}
|
||||||
|
for !p.accept(TokenCloseParenthesis) {
|
||||||
|
i, err := p.expression(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, i)
|
||||||
|
|
||||||
|
if !p.accept(TokenComma) {
|
||||||
|
if err := p.expect(TokenCloseParenthesis, "tuples must be closed"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TupleNode{
|
||||||
|
items,
|
||||||
|
|
||||||
|
start,
|
||||||
|
p.prev.End,
|
||||||
|
}, nil
|
||||||
|
|
||||||
case TokenBreakpoint:
|
case TokenBreakpoint:
|
||||||
p.advance()
|
p.advance()
|
||||||
|
|
@ -831,6 +881,9 @@ func (p *Parser) factor() (Node, error) {
|
||||||
p.prev.End,
|
p.prev.End,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
|
case TokenOpenBrace:
|
||||||
|
return p.expression(true)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, p.error(fmt.Sprintf("invalid factor %s", p.curr), p.curr)
|
return nil, p.error(fmt.Sprintf("invalid factor %s", p.curr), p.curr)
|
||||||
}
|
}
|
||||||
|
|
@ -1355,15 +1408,44 @@ func (p *Parser) parseSignature() (TypeSignature, error) {
|
||||||
var s TypeSignature
|
var s TypeSignature
|
||||||
|
|
||||||
if p.accept(TokenOpenParenthesis) {
|
if p.accept(TokenOpenParenthesis) {
|
||||||
is, err := p.parseSignature()
|
// we're inside an object
|
||||||
|
if p.acceptAll(TokenName, TokenColon) {
|
||||||
|
return nil, p.error("objects are not implemented yet (TBD)", p.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := p.parseSignature()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := p.expect(TokenCloseParenthesis, "expected closing parenthesis"); err != nil {
|
|
||||||
|
if !p.accept(TokenComma) {
|
||||||
|
if err := p.expect(TokenCloseParenthesis, "parenthesis must be closed"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s = is
|
s = v
|
||||||
|
} else {
|
||||||
|
items := []TypeSignature{v}
|
||||||
|
for !p.accept(TokenCloseParenthesis) {
|
||||||
|
i, err := p.parseSignature()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, i)
|
||||||
|
|
||||||
|
if !p.accept(TokenComma) {
|
||||||
|
if err := p.expect(TokenCloseParenthesis, "tuples must be closed"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = &TupleSignature{
|
||||||
|
items,
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if p.accept(TokenFunc) {
|
} else if p.accept(TokenFunc) {
|
||||||
if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil {
|
if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -960,6 +960,31 @@ func TestParser_Parse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParser_AcceptAll(t *testing.T) {
|
||||||
|
p := NewParser("a:", []string{}, []Token{
|
||||||
|
NewToken(TokenName, 0, 1, 0, "a"),
|
||||||
|
NewToken(TokenColon, 1, 2, 0, "a"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if !p.acceptAll(TokenName, TokenColon) {
|
||||||
|
t.Fatalf("tokens were not accepted")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("tokens were accepted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParser_AcceptAll_TooFew(t *testing.T) {
|
||||||
|
p := NewParser("a", []string{}, []Token{
|
||||||
|
NewToken(TokenName, 0, 1, 0, "a"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if p.acceptAll(TokenName, TokenColon) {
|
||||||
|
t.Fatalf("tokens were incorrectly accepted")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("tokens were, as expected, not accepted")
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkParser_Parse(b *testing.B) {
|
func BenchmarkParser_Parse(b *testing.B) {
|
||||||
tokenData := GetTokenTestData()
|
tokenData := GetTokenTestData()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const (
|
||||||
TypeBoolean
|
TypeBoolean
|
||||||
TypeNil
|
TypeNil
|
||||||
TypeList
|
TypeList
|
||||||
|
TypeTuple
|
||||||
TypeObject
|
TypeObject
|
||||||
TypeFunction
|
TypeFunction
|
||||||
TypeAny
|
TypeAny
|
||||||
|
|
@ -35,6 +36,8 @@ func (t Type) String() string {
|
||||||
return "nil"
|
return "nil"
|
||||||
case TypeList:
|
case TypeList:
|
||||||
return "list"
|
return "list"
|
||||||
|
case TypeTuple:
|
||||||
|
return "tuple"
|
||||||
case TypeObject:
|
case TypeObject:
|
||||||
return "object"
|
return "object"
|
||||||
case TypeFunction:
|
case TypeFunction:
|
||||||
|
|
@ -92,6 +95,16 @@ func SignatureOf(v Value) TypeSignature {
|
||||||
}
|
}
|
||||||
case *BuiltinFunctionValue:
|
case *BuiltinFunctionValue:
|
||||||
return t.Signature
|
return t.Signature
|
||||||
|
case *TupleValue:
|
||||||
|
var contains []TypeSignature
|
||||||
|
for _, p := range t.Items {
|
||||||
|
sig := SignatureOf(p)
|
||||||
|
contains = append(contains, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TupleSignature{
|
||||||
|
contains,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(fmt.Sprintf("unknown value; cannot get signature of %s", v))
|
panic(fmt.Sprintf("unknown value; cannot get signature of %s", v))
|
||||||
|
|
@ -217,6 +230,58 @@ func (s *ListSignature) String() string {
|
||||||
return fmt.Sprintf("list[%s]", s.Contents)
|
return fmt.Sprintf("list[%s]", s.Contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TupleSignature struct {
|
||||||
|
Contents []TypeSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TupleSignature) Type() Type {
|
||||||
|
return TypeTuple
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TupleSignature) Matches(other TypeSignature) bool {
|
||||||
|
if other.Type() == TypeComposite {
|
||||||
|
return other.Matches(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Type() == TypeAny {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Type() != TypeTuple || len(other.(*TupleSignature).Contents) != len(s.Contents) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
n := other.(*TupleSignature).Contents
|
||||||
|
for i, c := range s.Contents {
|
||||||
|
if !c.Matches(n[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TupleSignature) String() string {
|
||||||
|
|
||||||
|
sb := strings.Builder{}
|
||||||
|
|
||||||
|
sb.WriteString("(")
|
||||||
|
for i, t := range s.Contents {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(",")
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
sb.WriteString(t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.Contents) <= 1 {
|
||||||
|
sb.WriteString(",")
|
||||||
|
}
|
||||||
|
sb.WriteString(")")
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
type ObjectSignature struct {
|
type ObjectSignature struct {
|
||||||
Members map[string]TypeSignature
|
Members map[string]TypeSignature
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ValueType int
|
type ValueType int
|
||||||
|
|
@ -17,6 +18,7 @@ const (
|
||||||
IntegerValueType
|
IntegerValueType
|
||||||
StringValueType
|
StringValueType
|
||||||
ListValueType
|
ListValueType
|
||||||
|
TupleValueType
|
||||||
ObjectValueType
|
ObjectValueType
|
||||||
FunctionValueType
|
FunctionValueType
|
||||||
BuiltinFunctionValueType
|
BuiltinFunctionValueType
|
||||||
|
|
@ -39,6 +41,8 @@ func (v ValueType) String() string {
|
||||||
return "string"
|
return "string"
|
||||||
case ListValueType:
|
case ListValueType:
|
||||||
return "list"
|
return "list"
|
||||||
|
case TupleValueType:
|
||||||
|
return "tuple"
|
||||||
case FunctionValueType:
|
case FunctionValueType:
|
||||||
return "function"
|
return "function"
|
||||||
case BuiltinFunctionValueType:
|
case BuiltinFunctionValueType:
|
||||||
|
|
@ -278,7 +282,12 @@ func (v *FloatValue) Type() ValueType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *FloatValue) String() string {
|
func (v *FloatValue) String() string {
|
||||||
return strconv.FormatFloat(v.Number, 'g', -1, FloatSize)
|
s := strconv.FormatFloat(v.Number, 'g', -1, FloatSize)
|
||||||
|
if strings.Index(s, ".") == -1 {
|
||||||
|
s += ".0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *FloatValue) DebugString() string {
|
func (v *FloatValue) DebugString() string {
|
||||||
|
|
@ -597,6 +606,88 @@ func (v *ListValue) Clone() Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TupleValue struct {
|
||||||
|
Items []Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TupleValue) Type() ValueType {
|
||||||
|
return TupleValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TupleValue) String() string {
|
||||||
|
out := "("
|
||||||
|
for i, item := range v.Items {
|
||||||
|
if i != 0 {
|
||||||
|
out += ", "
|
||||||
|
}
|
||||||
|
out += item.DebugString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Items) <= 1 {
|
||||||
|
out += ","
|
||||||
|
}
|
||||||
|
out += ")"
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TupleValue) DebugString() string {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TupleValue) Clone() Value {
|
||||||
|
n := make([]Value, len(v.Items))
|
||||||
|
for i, item := range v.Items {
|
||||||
|
n[i] = item.Clone()
|
||||||
|
}
|
||||||
|
return &TupleValue{
|
||||||
|
n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TupleValue) Equals(other Value) bool {
|
||||||
|
if other.Type() != TupleValueType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
t := other.(*TupleValue).Items
|
||||||
|
for i, item := range v.Items {
|
||||||
|
if !item.Equals(t[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var TuplePrototype = map[string]*BuiltinFunctionValue{
|
||||||
|
"at": &BuiltinFunctionValue{
|
||||||
|
"at",
|
||||||
|
&FunctionSignature{
|
||||||
|
[]TypeSignature{&IntegerSignature{}},
|
||||||
|
&InnerSignature{},
|
||||||
|
},
|
||||||
|
func(vm *VM, this Value, args []Value) (Value, error) {
|
||||||
|
i := args[0].(*IntegerValue).Number.Int64()
|
||||||
|
|
||||||
|
if i < 0 || i >= int64(len(this.(*TupleValue).Items)) {
|
||||||
|
return nil, errors.New(fmt.Sprintf("index %x out of range", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.(*TupleValue).Items[i], nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TupleValue) Get(key string) (Value, error) {
|
||||||
|
if prop, ok := TuplePrototype[key]; ok {
|
||||||
|
return prop, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New(fmt.Sprintf("tuple has no property \"%s\"", key))
|
||||||
|
}
|
||||||
|
|
||||||
type FunctionValue struct {
|
type FunctionValue struct {
|
||||||
Name string
|
Name string
|
||||||
Params []FunctionParameter
|
Params []FunctionParameter
|
||||||
|
|
|
||||||
16
core/vm.go
16
core/vm.go
|
|
@ -132,6 +132,10 @@ const (
|
||||||
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
|
// InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists.
|
||||||
InstructionConcatLists
|
InstructionConcatLists
|
||||||
|
|
||||||
|
// InstructionFormTuple pop n+1 (u16) items from the stack, and create a new tuple with the items. The top value
|
||||||
|
// on the stack is the last value in the tuple.
|
||||||
|
InstructionFormTuple
|
||||||
|
|
||||||
// InstructionBreakpoint for debugging purposes
|
// InstructionBreakpoint for debugging purposes
|
||||||
InstructionBreakpoint
|
InstructionBreakpoint
|
||||||
)
|
)
|
||||||
|
|
@ -992,6 +996,18 @@ func (vm *VM) Next() bool {
|
||||||
append(l.Items, r.Items...),
|
append(l.Items, r.Items...),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case InstructionFormTuple:
|
||||||
|
n := int(vm.NextU16())
|
||||||
|
|
||||||
|
items := make([]Value, n)
|
||||||
|
for i := n - 1; i >= 0; i-- {
|
||||||
|
items[i] = vm.Stack.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.Stack.Push(&TupleValue{
|
||||||
|
items,
|
||||||
|
})
|
||||||
|
|
||||||
case InstructionDescend:
|
case InstructionDescend:
|
||||||
vm.descend()
|
vm.descend()
|
||||||
|
|
||||||
|
|
|
||||||
11
tests/tuple.ang
Normal file
11
tests/tuple.ang
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
assertEq((1, 2), (1, 2))
|
||||||
|
|
||||||
|
assertEq(type((1,)), type((1,)))
|
||||||
|
assertEq((1,), (1,))
|
||||||
|
|
||||||
|
fn neighbours(n: int) -> (int, int) {
|
||||||
|
(n-1, n+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEq(neighbours(2), (1, 3))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue