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")
|
||||
}
|
||||
|
||||
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() {
|
||||
case StringNodeType:
|
||||
n := tree.(*StringNode)
|
||||
|
|
@ -236,19 +248,23 @@ func (c *Compiler) compile(tree Node) error {
|
|||
c.add(InstructionConstant)
|
||||
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:
|
||||
l := tree.(*ListNode)
|
||||
|
||||
if len(l.items) == 0 {
|
||||
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 {
|
||||
for _, n := range l.items {
|
||||
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])
|
||||
case AssignNodeType:
|
||||
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:
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
if !p.accept(tokenType) {
|
||||
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:
|
||||
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 {
|
||||
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 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:
|
||||
p.advance()
|
||||
|
|
@ -831,6 +881,9 @@ func (p *Parser) factor() (Node, error) {
|
|||
p.prev.End,
|
||||
}, nil
|
||||
|
||||
case TokenOpenBrace:
|
||||
return p.expression(true)
|
||||
|
||||
default:
|
||||
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
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil {
|
||||
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) {
|
||||
tokenData := GetTokenTestData()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const (
|
|||
TypeBoolean
|
||||
TypeNil
|
||||
TypeList
|
||||
TypeTuple
|
||||
TypeObject
|
||||
TypeFunction
|
||||
TypeAny
|
||||
|
|
@ -35,6 +36,8 @@ func (t Type) String() string {
|
|||
return "nil"
|
||||
case TypeList:
|
||||
return "list"
|
||||
case TypeTuple:
|
||||
return "tuple"
|
||||
case TypeObject:
|
||||
return "object"
|
||||
case TypeFunction:
|
||||
|
|
@ -92,6 +95,16 @@ func SignatureOf(v Value) TypeSignature {
|
|||
}
|
||||
case *BuiltinFunctionValue:
|
||||
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))
|
||||
|
|
@ -217,6 +230,58 @@ func (s *ListSignature) String() string {
|
|||
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 {
|
||||
Members map[string]TypeSignature
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ValueType int
|
||||
|
|
@ -17,6 +18,7 @@ const (
|
|||
IntegerValueType
|
||||
StringValueType
|
||||
ListValueType
|
||||
TupleValueType
|
||||
ObjectValueType
|
||||
FunctionValueType
|
||||
BuiltinFunctionValueType
|
||||
|
|
@ -39,6 +41,8 @@ func (v ValueType) String() string {
|
|||
return "string"
|
||||
case ListValueType:
|
||||
return "list"
|
||||
case TupleValueType:
|
||||
return "tuple"
|
||||
case FunctionValueType:
|
||||
return "function"
|
||||
case BuiltinFunctionValueType:
|
||||
|
|
@ -278,7 +282,12 @@ func (v *FloatValue) Type() ValueType {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -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 {
|
||||
Name string
|
||||
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
|
||||
|
||||
// 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
|
||||
)
|
||||
|
|
@ -992,6 +996,18 @@ func (vm *VM) Next() bool {
|
|||
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:
|
||||
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