diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/anglais.iml b/.idea/anglais.iml new file mode 100644 index 0000000..11646b4 --- /dev/null +++ b/.idea/anglais.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6c7658f --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..36d0426 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/core/compiler.go b/core/compiler.go index 928eff0..617871e 100644 --- a/core/compiler.go +++ b/core/compiler.go @@ -3,7 +3,6 @@ package core import ( "errors" "fmt" - "slices" "strings" ) @@ -117,7 +116,8 @@ func (e CompilerError) Format() string { b.WriteString("\nsource trace:") // print import stack trace - for i, p := range slices.Backward(e.Trace) { + for i := len(e.Trace) - 1; i >= 0; i-- { + p := e.Trace[i] b.WriteString(fmt.Sprintf("\n[%d] %s", i, p)) } @@ -180,20 +180,20 @@ func (c *Compiler) Compile(p *Program) (TypeSignature, error) { } func EscapeString(in string) string { - var out strings.Builder + out := "" escaped := false for _, ch := range in { if escaped { switch ch { case 'n': - out.WriteRune('\n') + out += "\n" case 't': - out.WriteRune('\t') + out += "\t" case 'r': - out.WriteRune('\r') + out += "\r" default: - out.WriteString(string(ch)) + out += string(ch) } escaped = false continue @@ -203,11 +203,11 @@ func EscapeString(in string) string { case '\\': escaped = true default: - out.WriteString(string(ch)) + out += string(ch) } } - return out.String() + return out } func (c *Compiler) resolveType(value TypeSignature) TypeSignature { @@ -265,30 +265,6 @@ func (c *Compiler) compile(tree Node) (TypeSignature, error) { return &TupleSignature{contents}, nil - case RecordNodeType: - n := tree.(*RecordNode) - - c.add(InstructionNewRecord) - - contents := map[string]TypeSignature{} - for k, v := range n.entries { - t, err := c.compile(v) - if err != nil { - return nil, err - } - - c.add(InstructionSetRecordItem) - c.addConstant(&StringValue{ - k, - }) - - contents[k] = t - } - - return &RecordSignature{ - contents, - }, nil - case ListNodeType: l := tree.(*ListNode) @@ -968,8 +944,8 @@ func (c *Compiler) compileAssignFromStack(to Node, sig TypeSignature, declare bo c.add(InstructionDestructureTuple) // iterate from top to bottom - for i, v := range slices.Backward(t.items) { - _, err := c.compileAssignFromStack(v, tsig.Contents[i], declare) + for i := len(t.items) - 1; i >= 0; i-- { + _, err := c.compileAssignFromStack(t.items[i], tsig.Contents[i], declare) if err != nil { return nil, err } @@ -977,41 +953,6 @@ func (c *Compiler) compileAssignFromStack(to Node, sig TypeSignature, declare bo } return sig, nil - case IndexNodeType: - if declare { - return nil, c.error(fmt.Sprintf("cannot declare an indexed item"), to) - } - - n := to.(*IndexNode) - - ssig, err := c.compile(n.source) - if err != nil { - return nil, err - } - - isig, err := c.compile(n.index) - if err != nil { - return nil, err - } - - if isig.Type() != TypeInteger { - return nil, c.error(fmt.Sprintf("cannot index into list with non-integer (%s)", isig), n.index) - } - - switch ssig.Type() { - case TypeList: - lsig := ssig.(*ListSignature) - if !c.typeMatches(sig, lsig.Contents) { - return nil, c.error(fmt.Sprintf("cannot assign value of type %s to list with items of type %s", sig, lsig.Contents), to) - } - - c.add(InstructionSetIndexList) - default: - return nil, c.error(fmt.Sprintf("cannot set index of %s", ssig.Type()), n.source) - } - - return sig, nil - default: return nil, c.error(fmt.Sprintf("cannot assign to %s", to.Type()), to) } @@ -1255,13 +1196,6 @@ func (c *Compiler) getPropertySignature(source TypeSignature, property string) ( return &CompositeSignature{ at, bt, }, nil - case TypeRecord: - prop, ok := sig.(*RecordSignature).Entries[property] - if !ok { - return nil, errors.New(fmt.Sprintf("cannot record has no property \"%s\"", property)) - } - - return prop, nil default: } diff --git a/core/lexer.go b/core/lexer.go index 1701479..ba504b8 100644 --- a/core/lexer.go +++ b/core/lexer.go @@ -7,7 +7,7 @@ import ( ) type Token struct { - Kind TokenKind + Type TokenKind Start Pos End Pos Line Pos @@ -19,7 +19,7 @@ func (t Token) Bounds() (Pos, Pos) { } func (t Token) String() string { - return fmt.Sprintf("token %s, '%s' %d -> %d, line %d", t.Kind.String(), t.Lexeme, t.Start, t.End, t.Line) + return fmt.Sprintf("token %s, '%s' %d -> %d, line %d", t.Type.String(), t.Lexeme, t.Start, t.End, t.Line) } type TokenKind uint64 @@ -185,8 +185,6 @@ func (t TokenKind) String() string { return "for" case TokenIn: return "in" - case TokenPercent: - return "percent" } panic("UNDEFINED TOKENTYPE STRING CONVERSION") @@ -417,7 +415,7 @@ func (l *Lexer) NextToken() (Token, error) { func NewToken(t TokenKind, start Pos, end Pos, line Pos, lexeme string) Token { return Token{ - Kind: t, + Type: t, Start: start, End: end, Line: line, @@ -432,7 +430,7 @@ func (l *Lexer) Tokenize() ([]Token, error) { for ; err == nil; tok, err = l.NextToken() { tokens = append(tokens, tok) - if tok.Kind == TokenEOF { + if tok.Type == TokenEOF { break } } diff --git a/core/lexer_test.go b/core/lexer_test.go index a20a0c1..c8158b3 100644 --- a/core/lexer_test.go +++ b/core/lexer_test.go @@ -148,8 +148,8 @@ func TestLexer_NextToken(t *testing.T) { continue } - if tok.Kind != expectedType { - t.Errorf("Expected token type '%s' but got '%s'", expectedType, tok.Kind) + if tok.Type != expectedType { + t.Errorf("Expected token type '%s' but got '%s'", expectedType, tok.Type) } else { t.Logf("Got expected token type '%s'", expectedType) } @@ -193,7 +193,7 @@ func TestLexer_NextTokenErrors(t *testing.T) { lex := NewLexer(code) tok, err := lex.NextToken() - for err == nil && tok.Kind != TokenEOF { + for err == nil && tok.Type != TokenEOF { tok, err = lex.NextToken() } @@ -220,7 +220,7 @@ func BenchmarkLexer_NextToken(b *testing.B) { lex := NewLexer(tc.source) tok, err := lex.NextToken() - for err == nil && tok.Kind != TokenEOF { + for err == nil && tok.Type != TokenEOF { tok, err = lex.NextToken() } } diff --git a/core/nodes.go b/core/nodes.go index 0a04c44..056b206 100644 --- a/core/nodes.go +++ b/core/nodes.go @@ -29,7 +29,6 @@ const ( NilNodeType ListNodeType TupleNodeType - RecordNodeType BinaryNodeType UnaryNodeType BlockNodeType @@ -94,12 +93,6 @@ func (n NodeType) String() string { return "Alias" case IndexNodeType: return "Index" - case RecordNodeType: - return "Record" - case ForNodeType: - return "For" - case IncludeNodeType: - return "Include" } return "Invalid Node Type" } @@ -244,36 +237,6 @@ func (n TupleNode) Bounds() (Pos, Pos) { return n.start, n.end } -type RecordNode struct { - entries map[string]Node - - start Pos - end Pos -} - -func (n RecordNode) Type() NodeType { - return RecordNodeType -} - -func (n RecordNode) String() string { - sb := strings.Builder{} - - sb.WriteString("(") - for name, item := range n.entries { - sb.WriteString(name) - sb.WriteString(": ") - sb.WriteString(item.String()) - sb.WriteString(",") - } - sb.WriteString(")") - - return sb.String() -} - -func (n RecordNode) Bounds() (Pos, Pos) { - return n.start, n.end -} - type AccessNode struct { source Node property *Token @@ -306,8 +269,6 @@ func (n BinaryOperation) String() string { return "multiply" case BinaryDivision: return "divide" - case BinaryModulo: - return "modulo" case BinaryEquality: return "equality" case BinaryInequality: @@ -538,7 +499,7 @@ func (n ConditionalNode) String() string { return fmt.Sprintf("if %s then %s", n.condition.String(), n.do.String()) } - return fmt.Sprintf("if %s then %s otherwise %s", n.condition.String(), n.do.String(), n.otherwise.String()) + return fmt.Sprintf("if %s then %s otheriwise %s", n.condition.String(), n.do.String(), n.otherwise.String()) } func (n ConditionalNode) Bounds() (Pos, Pos) { diff --git a/core/optimizer.go b/core/optimizer.go new file mode 100644 index 0000000..fe08fec --- /dev/null +++ b/core/optimizer.go @@ -0,0 +1,250 @@ +package core + +import ( + "fmt" + "math/big" +) + +type TreeOptimizer struct{} + +func (t *TreeOptimizer) Optimize(node *Node) { + panic("unimplemented") +} + +func (t *TreeOptimizer) error(message string, causer Bounded) error { + panic("unimplemented") +} + +// isTreeConstant check if a node tree is constant (predictable) +func (t *TreeOptimizer) isTreeConstant(tree Node) bool { + switch tree.Type() { + case StringNodeType, FloatNodeType, IntegerNodeType, BooleanNodeType, NilNodeType: + return true + case ListNodeType: + for _, item := range tree.(*ListNode).items { + if !t.isTreeConstant(item) { + return false + } + } + + return true + case UnaryNodeType: + return t.isTreeConstant(tree.(*UnaryNode).value) + case BinaryNodeType: + return t.isTreeConstant(tree.(*BinaryNode).Left) && t.isTreeConstant(tree.(*BinaryNode).Right) + case InvokeNodeType: + for _, arg := range tree.(*InvokeNode).args { + if !t.isTreeConstant(arg) { + return false + } + } + return t.isTreeConstant(tree.(*InvokeNode).source) + case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, FunctionNodeType, + ReturnNodeType, AccessNodeType, BreakpointNodeType, ReferenceNodeType: + return false + default: + panic(fmt.Sprintf("unexpected node %s", tree)) + } +} + +func (t *TreeOptimizer) compute(tree Node) (Value, error) { + switch n := tree.(type) { + case *StringNode: + return &StringValue{ + EscapeString(n.value), + }, nil + + case *FloatNode: + return &FloatValue{ + n.value, + }, nil + + case *IntegerNode: + return &IntegerValue{ + n.value, + }, nil + + case *BooleanNode: + return &BoolValue{ + n.Boolean, + }, nil + + case *NilNode: + return &NilValue{}, nil + + case *ListNode: + items := make([]Value, len(n.items)) + var err error + for i, item := range n.items { + items[i], err = t.compute(item) + + if err != nil { + return nil, err + } + } + return &ListValue{ + items, + }, nil + + case *BinaryNode: + return t.computeBinary(n) + + case *UnaryNode: + v, err := t.compute(n.value) + if err != nil { + return nil, err + } + + switch n.UnaryOperation { + case UnaryNegate: + if v.Type() == FloatValueType { + return &FloatValue{ + -v.(*FloatValue).Number, + }, nil + } else if v.Type() == IntegerValueType { + return &IntegerValue{ + new(big.Int).Neg(v.(*IntegerValue).Number), + }, nil + } + + return nil, t.error(fmt.Sprintf("cannot negate %s value (not a number)", v.Type()), n) + case UnaryNot: + if v.Type() != BoolValueType { + return nil, t.error(fmt.Sprintf("cannot invert %s value (not a boolean)", v.Type()), n) + } + + return &BoolValue{ + !v.(*BoolValue).Boolean, + }, nil + } + + return nil, t.error(fmt.Sprintf("unimplemented unary %s", v.Type()), n) + + case *InvokeNode: + source, err := t.compute(n.source) + if err != nil { + return nil, err + } + + f, ok := source.(*BuiltinFunctionValue) + if !ok { + return nil, nil + } + + if !f.Constant { + return nil, nil + } + + args := make([]Value, len(f.Signature.In)) + for i, arg := range n.args { + args[i], err = t.compute(arg) + if err != nil { + return nil, err + } + } + + return f.F(nil, nil, args) + + default: + panic(fmt.Sprintf("unexpected node %s, %T", tree.String(), tree)) + } +} + +func (t *TreeOptimizer) computeBinary(n *BinaryNode) (Value, error) { + l, err := t.compute(n.Left) + if err != nil { + return nil, err + } + r, err := t.compute(n.Right) + if err != nil { + return nil, err + } + + if l.Type() != r.Type() { + return nil, t.error(fmt.Sprintf("cannot %s different types %s and %s", n.BinaryOperation, l.Type(), r.Type()), n) + } + + // perform type check + switch n.BinaryOperation { + case BinarySubtraction, BinaryMultiplication, BinaryDivision, BinaryLess, BinaryGreater, BinaryLessEqual, BinaryGreaterEqual: + if l.Type() != FloatValueType && l.Type() != IntegerValueType { + return nil, t.error(fmt.Sprintf("cannot %s values of non-number type %s", n.BinaryOperation, l.Type()), n) + } + case BinaryBooleanAnd, BinaryBooleanOr: + if l.Type() != BoolValueType { + return nil, t.error(fmt.Sprintf("cannot %s values of non-boolean type %s", n.BinaryOperation, l.Type()), n) + } + case BinaryEquality, BinaryInequality: + // can compare all types with themselves + default: + } + + var v interface{} + switch n.BinaryOperation { + case BinaryAddition: + switch l.Type() { + case FloatValueType: + v = l.(*FloatValue).Number + r.(*FloatValue).Number + case StringValueType: + v = l.(*StringValue).Text + r.(*StringValue).Text + case ListValueType: + v = append(l.(*ListValue).Items, r.(*ListValue).Items...) + case IntegerValueType: + v = new(big.Int).Add(l.(*IntegerValue).Number, r.(*IntegerValue).Number) + default: + return nil, t.error(fmt.Sprintf("cannot add values of type %s", l.Type()), n) + } + case BinarySubtraction: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number - r.(*FloatValue).Number + } else { + v = new(big.Int).Sub(l.(*IntegerValue).Number, r.(*IntegerValue).Number) + } + case BinaryMultiplication: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number * r.(*FloatValue).Number + } else { + v = new(big.Int).Mul(l.(*IntegerValue).Number, r.(*IntegerValue).Number) + } + case BinaryDivision: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number / r.(*FloatValue).Number + } else { + v = new(big.Int).Div(l.(*IntegerValue).Number, r.(*IntegerValue).Number) + } + case BinaryBooleanAnd: + v = l.(*BoolValue).Boolean && r.(*BoolValue).Boolean + case BinaryBooleanOr: + v = l.(*BoolValue).Boolean || r.(*BoolValue).Boolean + case BinaryEquality: + v = l.Equals(r) + case BinaryInequality: + v = !l.Equals(r) + case BinaryLess: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number < r.(*FloatValue).Number + } else { + v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) == -1 + } + case BinaryGreater: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number > r.(*FloatValue).Number + } else { + v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) == 1 + } + case BinaryLessEqual: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number <= r.(*FloatValue).Number + } else { + v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) != 1 + } + case BinaryGreaterEqual: + if l.Type() == FloatValueType { + v = l.(*FloatValue).Number >= r.(*FloatValue).Number + } else { + v = l.(*IntegerValue).Number.Cmp(r.(*IntegerValue).Number) != 1 + } + } + + return GoToValue(v), nil +} diff --git a/core/parser.go b/core/parser.go index 7383f51..6462b7a 100644 --- a/core/parser.go +++ b/core/parser.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "math/big" - "slices" "strconv" "strings" ) @@ -69,21 +68,17 @@ func (p ParsingError) Format() string { b.WriteRune('\n') b.WriteRune('\n') - for i, v := range slices.Backward(p.Trace) { - b.WriteString(fmt.Sprintf("[%d] %s\n", i, v)) + for i := len(p.Trace) - 1; i >= 0; i-- { + b.WriteString(fmt.Sprintf("[%d] %s\n", i, p.Trace[i])) } return b.String() } type Parser struct { - source string - trace []string - tokens []Token - state ParserState -} - -type ParserState struct { + source string + trace []string + tokens []Token prev *Token curr *Token pos Pos @@ -95,9 +90,7 @@ func NewParser(source string, trace []string, tokens []Token) *Parser { source: source, trace: trace, tokens: tokens, - state: ParserState{ - pos: 0, - }, + pos: 0, } } @@ -123,11 +116,11 @@ func (p *Parser) Parse(path string) (*Program, error) { // initialize current p.advance() - for int(p.state.pos) < len(p.tokens) && p.state.curr.Kind != TokenEOF { + for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF { for p.accept(TokenNewLine) { } - if p.state.curr.Kind == TokenEOF { + if p.curr.Type == TokenEOF { break } @@ -146,25 +139,25 @@ func (p *Parser) Parse(path string) (*Program, error) { &BlockNode{ statements, 0, - p.state.curr.End, + p.curr.End, }, path, }, nil } func (p *Parser) accept(tokenType TokenKind) bool { - if p.state.curr == nil { + if p.curr == nil { log.Fatal("unexpected current token nil") return false } - if p.state.ignoreNewLine && tokenType != TokenNewLine { - for p.state.curr.Kind == TokenNewLine { + if p.ignoreNewLine && tokenType != TokenNewLine { + for p.curr.Type == TokenNewLine { p.advance() } } - if (*p.state.curr).Kind == tokenType { + if (*p.curr).Type == tokenType { p.advance() return true } @@ -172,52 +165,46 @@ func (p *Parser) accept(tokenType TokenKind) bool { return false } -func (p *Parser) getState() ParserState { - return p.state -} +func (p *Parser) acceptAll(tokenTypes ...TokenKind) bool { + if int(p.pos)+len(tokenTypes) > len(p.tokens) { + return false + } -func (p *Parser) restoreState(state ParserState) { - p.state = state -} - -func (p *Parser) acceptSeq(tokenTypes ...TokenKind) bool { - state := p.getState() - - for _, t := range tokenTypes { - if !p.accept(t) { - p.restoreState(state) + 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 TokenKind, reason string) error { if !p.accept(tokenType) { - return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.state.curr.Kind, reason), p.state.curr) + return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.curr.Type, reason), p.curr) } return nil } func (p *Parser) peek() (Token, error) { - if p.state.pos >= Pos(len(p.tokens)) { + if p.pos >= Pos(len(p.tokens)) { return Token{}, errors.New("cannot peek beyond tokens") } - return p.tokens[p.state.pos], nil + return p.tokens[p.pos], nil } func (p *Parser) advance() { - p.state.prev = p.state.curr + p.prev = p.curr - if p.state.pos < Pos(len(p.tokens)) { - p.state.curr = &p.tokens[p.state.pos] + if p.pos < Pos(len(p.tokens)) { + p.curr = &p.tokens[p.pos] } else { - p.state.curr = nil + p.curr = nil } - p.state.pos++ + p.pos++ } func (p *Parser) error(error string, causer *Token) error { @@ -237,10 +224,10 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { } } - oldIgnoreNewline := p.state.ignoreNewLine - p.state.ignoreNewLine = false + oldIgnoreNewline := p.ignoreNewLine + p.ignoreNewLine = false - start := p.state.prev.Start + start := p.prev.Start var statements []Node for !p.accept(TokenCloseBrace) { @@ -264,22 +251,22 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { } } - p.state.ignoreNewLine = oldIgnoreNewline + p.ignoreNewLine = oldIgnoreNewline - return &BlockNode{statements, start, p.state.prev.End}, nil + return &BlockNode{statements, start, p.prev.End}, nil } - t := p.state.curr - switch t.Kind { + t := p.curr + switch t.Type { case TokenType: p.advance() - start := p.state.prev.Start + start := p.prev.Start if err := p.expect(TokenName, "types must have a name"); err != nil { return nil, err } - name := p.state.prev + name := p.prev if err := p.expect(TokenAssign, "type aliases must be defined with an assign"); err != nil { return nil, err @@ -295,7 +282,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { sig, start, - p.state.prev.End, + p.prev.End, }, nil case TokenIf: @@ -313,7 +300,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { var otherwise Node if p.accept(TokenElse) { - otherwise, err = p.expression(p.state.curr.Kind != TokenIf) + otherwise, err = p.expression(p.curr.Type != TokenIf) if err != nil { return nil, err } @@ -329,7 +316,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { case TokenReturn: p.advance() - start := p.state.prev.Start + start := p.prev.Start v, err := p.expression(false) if err != nil { @@ -339,12 +326,12 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { return &ReturnNode{ v, start, - p.state.prev.End, + p.prev.End, }, nil case TokenWhile: p.advance() - start := p.state.prev.Start + start := p.prev.Start cond, err := p.expression(false) if err != nil { @@ -360,12 +347,12 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { cond, logic, start, - p.state.prev.End, + p.prev.End, }, nil case TokenFor: p.advance() - start := p.state.prev.Start + start := p.prev.Start counter, err := p.expression(false) if err != nil { @@ -392,12 +379,12 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { logic, start, - p.state.prev.End, + p.prev.End, }, nil case TokenInclude: p.advance() - start := p.state.prev.Start + start := p.prev.Start if err := p.expect(TokenString, "import requires a path/name to include"); err != nil { return nil, err @@ -405,13 +392,13 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { return &IncludeNode{ &StringNode{ - p.state.prev.Lexeme[1 : len(p.state.prev.Lexeme)-1], - p.state.prev.Lexeme, - p.state.prev.Start, - p.state.prev.End, + p.prev.Lexeme[1 : len(p.prev.Lexeme)-1], + p.prev.Lexeme, + p.prev.Start, + p.prev.End, }, start, - p.state.prev.End, + p.prev.End, }, nil default: @@ -421,7 +408,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { } if p.accept(TokenDeclare) || p.accept(TokenAssign) { - isDeclaration := p.state.prev.Kind == TokenDeclare + isDeclaration := p.prev.Type == TokenDeclare // possibly assign tuples; not implemented yet v, err := p.expression(false) @@ -522,7 +509,7 @@ func (p *Parser) binary() (Node, error) { r := values.Pop() l := values.Pop() opToken := ops.Pop() - op := tokenToBinaryOperation(opToken.Kind) + op := tokenToBinaryOperation(opToken.Type) start, _ := l.Bounds() _, end := r.Bounds() @@ -537,12 +524,12 @@ func (p *Parser) binary() (Node, error) { }) } - for isBinaryOperator(p.state.curr.Kind) { - for ops.Current > 0 && binaryPrecedence(p.state.curr.Kind) <= binaryPrecedence(ops.Peek().Kind) { + for isBinaryOperator(p.curr.Type) { + for ops.Current > 0 && binaryPrecedence(p.curr.Type) <= binaryPrecedence(ops.Peek().Type) { reduce() } - ops.Push(p.state.curr) + ops.Push(p.curr) p.advance() v, err := p.chain() @@ -571,16 +558,16 @@ func (p *Parser) chain() (Node, error) { if err = p.expect(TokenName, "can only access properties by name"); err != nil { return nil, err } - name := p.state.prev + name := p.prev f = &AccessNode{ f, - p.state.prev, + p.prev, name.Start, name.End, } - if p.state.curr.Kind == TokenOpenParenthesis { + if p.curr.Type == TokenOpenParenthesis { args, err := p.parseArgs() if err != nil { return nil, err @@ -590,11 +577,11 @@ func (p *Parser) chain() (Node, error) { f, args, name.Start, - p.state.prev.End, + p.prev.End, } } - } else if p.state.curr.Kind == TokenOpenParenthesis { - start := p.state.curr.Start + } else if p.curr.Type == TokenOpenParenthesis { + start := p.curr.Start args, err := p.parseArgs() if err != nil { return nil, err @@ -605,10 +592,10 @@ func (p *Parser) chain() (Node, error) { args, start, - p.state.prev.End, + p.prev.End, } } else if p.accept(TokenOpenBracket) { - start := p.state.prev.Start + start := p.prev.Start index, err := p.expression(false) if err != nil { @@ -623,7 +610,7 @@ func (p *Parser) chain() (Node, error) { f, index, start, - p.state.prev.End, + p.prev.End, } } else { break @@ -634,71 +621,71 @@ func (p *Parser) chain() (Node, error) { } func (p *Parser) factor() (Node, error) { - switch (*p.state.curr).Kind { + switch (*p.curr).Type { case TokenString: p.advance() return &StringNode{ - (*p.state.prev).Lexeme[1 : len((*p.state.prev).Lexeme)-1], - (*p.state.prev).Lexeme, - p.state.prev.Start, - p.state.prev.End, + (*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1], + (*p.prev).Lexeme, + p.prev.Start, + p.prev.End, }, nil case TokenInteger: p.advance() - num, success := new(big.Int).SetString(p.state.prev.Lexeme, 10) + num, success := new(big.Int).SetString(p.prev.Lexeme, 10) if !success { - return nil, p.error(fmt.Sprintf("cannot parse integer base 10: %s", p.state.prev.Lexeme), p.state.prev) + return nil, p.error(fmt.Sprintf("cannot parse integer base 10: %s", p.prev.Lexeme), p.prev) } return &IntegerNode{ num, - p.state.prev.Start, - p.state.prev.End, + p.prev.Start, + p.prev.End, }, nil case TokenFloat: p.advance() - num, err := strconv.ParseFloat((*p.state.prev).Lexeme, FloatSize) + num, err := strconv.ParseFloat((*p.prev).Lexeme, FloatSize) if err != nil { - return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.state.prev) + return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev) } return &FloatNode{ num, - p.state.prev.Start, - p.state.prev.End, + p.prev.Start, + p.prev.End, }, nil case TokenHexadecimal: p.advance() - start := (*p.state.prev).Start - num, ok := new(big.Int).SetString(p.state.prev.Lexeme[2:], 16) + start := (*p.prev).Start + num, ok := new(big.Int).SetString(p.prev.Lexeme[2:], 16) if !ok { - return nil, p.error(fmt.Sprintf("cannot parse hexadecimal: %v", p.state.prev.Lexeme), p.state.prev) + return nil, p.error(fmt.Sprintf("cannot parse hexadecimal: %v", p.prev.Lexeme), p.prev) } return &IntegerNode{ num, start, - p.state.prev.End, + p.prev.End, }, nil case TokenTrue: p.advance() return &BooleanNode{ true, - p.state.prev.Start, - p.state.prev.End, + p.prev.Start, + p.prev.End, }, nil case TokenFalse: p.advance() return &BooleanNode{ false, - p.state.prev.Start, - p.state.prev.End, + p.prev.Start, + p.prev.End, }, nil case TokenNil: @@ -707,7 +694,7 @@ func (p *Parser) factor() (Node, error) { case TokenOpenBracket: p.advance() - start := p.state.prev.Start + start := p.prev.Start // TODO: find better solution; current one is messy // Maybe perform better analysis to determine the kind of the list... @@ -721,12 +708,12 @@ func (p *Parser) factor() (Node, error) { []Node{}, s, start, - p.state.prev.End, + p.prev.End, }, nil } - oldIgnoreNewline := p.state.ignoreNewLine - p.state.ignoreNewLine = true + oldIgnoreNewline := p.ignoreNewLine + p.ignoreNewLine = true var values []Node for !p.accept(TokenCloseBracket) { @@ -744,19 +731,19 @@ func (p *Parser) factor() (Node, error) { values = append(values, value) } - p.state.ignoreNewLine = oldIgnoreNewline + p.ignoreNewLine = oldIgnoreNewline return &ListNode{ values, nil, start, - p.state.prev.End, + p.prev.End, }, nil // unary minus case TokenMinus: p.advance() - op := p.state.prev + op := p.prev f, err := p.factor() if err != nil { @@ -767,12 +754,12 @@ func (p *Parser) factor() (Node, error) { f, op, op.Start, - p.state.prev.End, + p.prev.End, }, nil case TokenBang: p.advance() - op := p.state.prev + op := p.prev v, err := p.factor() if err != nil { @@ -784,16 +771,16 @@ func (p *Parser) factor() (Node, error) { v, op, op.Start, - p.state.prev.End, + p.prev.End, }, nil case TokenName: p.advance() - name := (*p.state.prev).Lexeme - start := p.state.prev.Start - nameEnd := p.state.prev.End + name := (*p.prev).Lexeme + start := p.prev.Start + nameEnd := p.prev.End - if p.state.curr.Kind == TokenOpenParenthesis { + if p.curr.Type == TokenOpenParenthesis { args, err := p.parseArgs() if err != nil { return nil, err @@ -807,7 +794,7 @@ func (p *Parser) factor() (Node, error) { }, args, start, - p.state.prev.End, + p.prev.End, }, nil } @@ -819,11 +806,11 @@ func (p *Parser) factor() (Node, error) { case TokenFunc: p.advance() - start := p.state.prev.Start + start := p.prev.Start var name *Token if p.accept(TokenName) { // can be unnamed, but accept name if it is named - name = p.state.prev + name = p.prev } params, err := p.parseParams() @@ -855,7 +842,7 @@ func (p *Parser) factor() (Node, error) { yield, logic, start, - p.state.prev.End, + p.prev.End, } if name != nil { @@ -864,7 +851,7 @@ func (p *Parser) factor() (Node, error) { fn, true, start, - p.state.prev.End, + p.prev.End, }, nil } @@ -872,67 +859,11 @@ func (p *Parser) factor() (Node, error) { case TokenOpenParenthesis: p.advance() - start := p.state.prev.Start - oldCare := p.state.ignoreNewLine - p.state.ignoreNewLine = true - p.skipNewLines() + start := p.prev.Start // we're inside an object - key := p.state.curr - if p.acceptSeq(TokenName, TokenColon) { - entries := map[string]Node{} - - for len(entries) == 0 || !p.accept(TokenCloseParenthesis) { - if len(entries) != 0 { - p.skipNewLines() - key = p.state.curr - if !p.acceptSeq(TokenName, TokenColon) { - return nil, p.error("expected a record name", key) - } - } - - name := key.Lexeme - - if _, ok := entries[name]; ok { - return nil, p.error("duplicate key; already defined.", key) - } - - if p.accept(TokenComma) || p.accept(TokenCloseParenthesis) { - entries[name] = &ReferenceNode{ - name, - key.Start, - key.End, - } - - if p.state.prev.Kind == TokenCloseParenthesis { - break - } - - continue - } else { - x, err := p.expression(false) - if err != nil { - return nil, err - } - - entries[name] = x - } - - if !p.accept(TokenComma) { - if err := p.expect(TokenCloseParenthesis, "record must be closed"); err != nil { - return nil, err - } - - break - } - } - - p.state.ignoreNewLine = oldCare - return &RecordNode{ - entries, - start, - p.state.prev.End, - }, nil + if p.acceptAll(TokenName, TokenColon) { + return nil, p.error("objects are not implemented yet (TBD)", p.prev) } v, err := p.expression(false) @@ -945,7 +876,6 @@ func (p *Parser) factor() (Node, error) { return nil, err } - p.state.ignoreNewLine = oldCare return v, nil } @@ -967,27 +897,26 @@ func (p *Parser) factor() (Node, error) { } } - p.state.ignoreNewLine = oldCare return &TupleNode{ items, start, - p.state.prev.End, + p.prev.End, }, nil case TokenBreakpoint: p.advance() return &BreakpointNode{ - p.state.prev.Start, - p.state.prev.End, + p.prev.Start, + p.prev.End, }, nil case TokenOpenBrace: return p.expression(true) default: - return nil, p.error(fmt.Sprintf("invalid factor %s", p.state.curr), p.state.curr) + return nil, p.error(fmt.Sprintf("invalid factor %s", p.curr), p.curr) } } @@ -1027,7 +956,7 @@ func (p *Parser) parseParams() ([]FunctionParameter, error) { params := make([]FunctionParameter, 0) if p.accept(TokenName) { - name := (*p.state.prev).Lexeme + name := (*p.prev).Lexeme if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil { return nil, err } @@ -1048,7 +977,7 @@ func (p *Parser) parseParams() ([]FunctionParameter, error) { if err := p.expect(TokenName, "parameters must have a name (cannot have trailing comma)"); err != nil { return nil, err } - name = (*p.state.prev).Lexeme + name = (*p.prev).Lexeme if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil { return nil, err } @@ -1076,42 +1005,9 @@ func (p *Parser) parseSignature() (TypeSignature, error) { var s TypeSignature if p.accept(TokenOpenParenthesis) { - oldCare := p.state.ignoreNewLine - p.state.ignoreNewLine = true - p.skipNewLines() - // we're inside an object - name := p.state.curr - if p.acceptSeq(TokenName, TokenColon) { - entries := map[string]TypeSignature{} - - for len(entries) == 0 || !p.accept(TokenCloseParenthesis) { - if len(entries) != 0 { - p.skipNewLines() - name = p.state.curr - if !p.acceptSeq(TokenName, TokenColon) { - return nil, p.error("expected record member", p.state.curr) - } - } - - sig, err := p.parseSignature() - if err != nil { - return nil, err - } - - entries[name.Lexeme] = sig - - if !p.accept(TokenComma) { - if err := p.expect(TokenCloseParenthesis, "record must be closed"); err != nil { - return nil, err - } - break - } - } - - return &RecordSignature{ - entries, - }, nil + if p.acceptAll(TokenName, TokenColon) { + return nil, p.error("objects are not implemented yet (TBD)", p.prev) } v, err := p.parseSignature() @@ -1147,8 +1043,6 @@ func (p *Parser) parseSignature() (TypeSignature, error) { items, } } - - p.state.ignoreNewLine = oldCare } else if p.accept(TokenFunc) { if err := p.expect(TokenOpenParenthesis, "func signature must have parentheses for parameters"); err != nil { return nil, err @@ -1201,7 +1095,7 @@ func (p *Parser) parseSignature() (TypeSignature, error) { if err := p.expect(TokenName, "type must be a name"); err != nil { return nil, err } - name := (*p.state.prev).Lexeme + name := (*p.prev).Lexeme switch name { case "str": @@ -1236,8 +1130,3 @@ func (p *Parser) parseSignature() (TypeSignature, error) { return s, nil } - -func (p *Parser) skipNewLines() { - for p.accept(TokenNewLine) { - } -} diff --git a/core/parser_test.go b/core/parser_test.go index b58330b..79b60c9 100644 --- a/core/parser_test.go +++ b/core/parser_test.go @@ -15,7 +15,7 @@ func TestNewParser(t *testing.T) { t.Fatal("parser should not be nil") } - if p.state.pos != 0 { + if p.pos != 0 { t.Error("parser should initialize position at 0") } @@ -715,95 +715,6 @@ func GetTokenTestData() map[string]TokenTestData { 0, 0, }, }, - "record/single": { - []Token{ - NewToken(TokenOpenParenthesis, 0, 0, 0, "("), - NewToken(TokenName, 0, 0, 1, "a"), - NewToken(TokenColon, 0, 0, 0, ":"), - NewToken(TokenInteger, 0, 0, 0, "2"), - NewToken(TokenCloseParenthesis, 0, 0, 0, ")"), - NewToken(TokenEOF, 0, 0, 0, ""), - }, - &BlockNode{ - []Node{ - &RecordNode{ - map[string]Node{ - "a": &IntegerNode{ - big.NewInt(2), - 0, 0, - }, - }, - - 0, 0, - }, - }, - 0, 0, - }, - }, - "record/multiple": { - []Token{ - NewToken(TokenOpenParenthesis, 0, 0, 0, "("), - NewToken(TokenName, 0, 0, 1, "a"), - NewToken(TokenColon, 0, 0, 0, ":"), - NewToken(TokenInteger, 0, 0, 0, "2"), - NewToken(TokenComma, 0, 0, 0, ","), - NewToken(TokenName, 0, 0, 1, "b"), - NewToken(TokenColon, 0, 0, 0, ":"), - NewToken(TokenInteger, 0, 0, 0, "4"), - NewToken(TokenCloseParenthesis, 0, 0, 0, ")"), - NewToken(TokenEOF, 0, 0, 0, ""), - }, - &BlockNode{ - []Node{ - &RecordNode{ - map[string]Node{ - "a": &IntegerNode{ - big.NewInt(2), - 0, 0, - }, - "b": &IntegerNode{ - big.NewInt(4), - 0, 0, - }, - }, - - 0, 0, - }, - }, - 0, 0, - }, - }, - "record/shorthand": { - []Token{ - NewToken(TokenOpenParenthesis, 0, 0, 0, "("), - NewToken(TokenName, 0, 0, 1, "a"), - NewToken(TokenColon, 0, 0, 0, ":"), - NewToken(TokenComma, 0, 0, 0, ","), - NewToken(TokenName, 0, 0, 1, "b"), - NewToken(TokenColon, 0, 0, 0, ":"), - NewToken(TokenCloseParenthesis, 0, 0, 0, ")"), - NewToken(TokenEOF, 0, 0, 0, ""), - }, - &BlockNode{ - []Node{ - &RecordNode{ - map[string]Node{ - "a": &ReferenceNode{ - "a", - 0, 0, - }, - "b": &ReferenceNode{ - "b", - 0, 0, - }, - }, - - 0, 0, - }, - }, - 0, 0, - }, - }, } } @@ -1001,23 +912,6 @@ func NodeEquality(t *testing.T, n1 Node, n2 Node) { NodeEquality(t, v1, t2.items[i]) } - case RecordNodeType: - r1 := n1.(*RecordNode) - r2 := n2.(*RecordNode) - - if len(r1.entries) != len(r2.entries) { - t.Fatalf("Record node entries count does not match") - } - - for i, v1 := range r1.entries { - t.Logf("Checking item %s", i) - if v2, ok := r2.entries[i]; ok { - NodeEquality(t, v1, v2) - } else { - t.Errorf("Record node entry %s from first does not exist in other", i) - } - } - default: panic("unimplemented node equality") } @@ -1045,55 +939,25 @@ func TestParser_Parse(t *testing.T) { } } -func TestParser_AcceptSeq(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, ":"), - NewToken(TokenEOF, 2, 2, 0, ""), + NewToken(TokenColon, 1, 2, 0, "a"), }) - // initialize - p.advance() - - if !p.acceptSeq(TokenName, TokenColon) { + if !p.acceptAll(TokenName, TokenColon) { t.Fatalf("tokens were not accepted") } t.Logf("tokens were accepted") } -func TestParser_AcceptSeqAndAfter(t *testing.T) { - p := NewParser("a: 1", []string{}, []Token{ - NewToken(TokenName, 0, 1, 0, "a"), - NewToken(TokenColon, 1, 2, 0, ":"), - NewToken(TokenInteger, 3, 4, 0, "1"), - NewToken(TokenEOF, 4, 4, 0, ""), - }) - - // initialize - p.advance() - - if !p.acceptSeq(TokenName, TokenColon) { - t.Fatalf("seq tokens were not accepted") - } - - if !p.accept(TokenInteger) { - t.Fatalf("integer was not accepted") - } - - t.Logf("tokens were accepted") -} - -func TestParser_AcceptSeq_TooFew(t *testing.T) { +func TestParser_AcceptAll_TooFew(t *testing.T) { p := NewParser("a", []string{}, []Token{ NewToken(TokenName, 0, 1, 0, "a"), - NewToken(TokenEOF, 1, 1, 0, ""), }) - // initialize - p.advance() - - if p.acceptSeq(TokenName, TokenColon) { + if p.acceptAll(TokenName, TokenColon) { t.Fatalf("tokens were incorrectly accepted") } diff --git a/core/stack_test.go b/core/stack_test.go index 2bc9f86..41f3d79 100644 --- a/core/stack_test.go +++ b/core/stack_test.go @@ -2,19 +2,18 @@ package core import ( "fmt" - "slices" "testing" ) func CompareScope(t *testing.T, expectedScope []map[string]Value, actualScope *Scope) { s := actualScope - for _, e := range slices.Backward(expectedScope) { + for i := len(expectedScope) - 1; i >= 0; i-- { if s == nil { t.Fatal("scope cut unexpecetantly short") } - for name, value := range e { + for name, value := range expectedScope[i] { v, ok := s.current[name] if !ok { t.Errorf("variable %s not found in correct scope", name) diff --git a/core/types.go b/core/types.go index 81dafd8..13f9e1b 100644 --- a/core/types.go +++ b/core/types.go @@ -21,7 +21,6 @@ const ( TypeComposite TypeInner TypeNamed - TypeRecord ) func (t Type) String() string { @@ -50,11 +49,9 @@ func (t Type) String() string { return "composite" case TypeInner: return "inner" - case TypeRecord: - return "record" - default: - panic(fmt.Sprintf("unsupported string conversion for type %v", int(t))) } + + panic(fmt.Sprintf("unsupported string conversion for type %v", int(t))) } func SignatureOf(v Value) TypeSignature { @@ -487,27 +484,6 @@ func quickComposite(a ...TypeSignature) TypeSignature { return s } -func simplifyComposite(a TypeSignature) TypeSignature { - var atoms []TypeSignature - - s := NewStack[TypeSignature](16) - s.Push(a) - - for s.Current > 0 { - i := s.Pop() - - c, ok := i.(*CompositeSignature) - if !ok { - - atoms = append(atoms, i) - } else { - s.Push(c.A, c.B) - } - } - - return quickComposite(atoms...) -} - type InnerSignature struct{} func (*InnerSignature) Type() Type { @@ -545,56 +521,3 @@ func (s *NamedSignature) Equal(t TypeSignature) bool { func (s *NamedSignature) String() string { return s.Name } - -type RecordSignature struct { - Entries map[string]TypeSignature -} - -func (*RecordSignature) Type() Type { - return TypeRecord -} - -func (s *RecordSignature) Contains(t TypeSignature) bool { - if t.Type() != TypeRecord { - return false - } - - rs := t.(*RecordSignature) - - /* - // not quite sure about this one - if len(s.Entries) != len(rs.Entries) { - return false - } - */ - - for k, v := range s.Entries { - is, ok := rs.Entries[k] - if !ok { - return false - } - - if !v.Contains(is) { - return false - } - } - - return true -} - -func (s *RecordSignature) Equal(t TypeSignature) bool { - return s.Contains(t) && len(t.(*RecordSignature).Entries) == len(s.Entries) -} - -func (s *RecordSignature) String() string { - b := strings.Builder{} - b.WriteString("(") - - for name, kind := range s.Entries { - b.WriteString(fmt.Sprintf("%s: %s, ", name, kind)) - } - - b.WriteString(")") - - return b.String() -} diff --git a/core/values.go b/core/values.go index e0cee7c..94c666f 100644 --- a/core/values.go +++ b/core/values.go @@ -22,7 +22,7 @@ const ( ObjectValueType FunctionValueType BuiltinFunctionValueType - RecordValueType + VariableValueType ) func (v ValueType) String() string { @@ -47,15 +47,15 @@ func (v ValueType) String() string { return "function" case BuiltinFunctionValueType: return "builtin function" - case RecordValueType: - return "record" + case VariableValueType: + return "variable" } return "undefined" } // GoToValue convert go values to anglais VM-values. Works for some values (nil, bool, float64, int, string, slices, maps) -func GoToValue(gov any) Value { +func GoToValue(gov interface{}) Value { switch v := gov.(type) { case nil: return &NilValue{} @@ -79,7 +79,7 @@ func GoToValue(gov any) Value { return &StringValue{ v, } - case map[string]any: + case map[string]interface{}: values := map[string]Value{} for key, value := range v { values[key] = GoToValue(value) @@ -117,12 +117,8 @@ type Value interface { // Get a member from the value. An error is returned if the member does not exist Get(string) (Value, error) - // Copy create a copy of the value. A copy is a direct copy for small data (numbers) and a copy of pointer - // for bigger data (lists, tuples, dicts) - Copy() Value - - // Clone create a clone of the value. A clone is new data for all data. - //Clone() Value + // Clone create a clone of the value. The returned value is a pointer to a new value of the same type as the value. + Clone() Value } type NilValue struct{} @@ -147,7 +143,7 @@ func (v *NilValue) Get(_ string) (Value, error) { return nil, errors.New("nil has no properties") } -func (v *NilValue) Copy() Value { +func (v *NilValue) Clone() Value { return &NilValue{} } @@ -179,7 +175,7 @@ func (v *BoolValue) Get(_ string) (Value, error) { return nil, errors.New("booleans have no properties") } -func (v *BoolValue) Copy() Value { +func (v *BoolValue) Clone() Value { return &BoolValue{ v.Boolean, } @@ -262,11 +258,11 @@ func (v *ObjectValue) Get(key string) (Value, error) { } } -func (v *ObjectValue) Copy() Value { +func (v *ObjectValue) Clone() Value { m := make(map[string]Value, len(v.Members)) for name, mem := range v.Members { - m[name] = mem.Copy() + m[name] = mem.Clone() } return &ObjectValue{ @@ -307,7 +303,7 @@ func (v *FloatValue) Get(_ string) (Value, error) { return nil, errors.New("numbers have no properties") } -func (v *FloatValue) Copy() Value { +func (v *FloatValue) Clone() Value { return &FloatValue{ v.Number, } @@ -338,7 +334,7 @@ func (v *IntegerValue) Get(_ string) (Value, error) { return nil, errors.New("numbers have no properties") } -func (v *IntegerValue) Copy() Value { +func (v *IntegerValue) Clone() Value { return &IntegerValue{ new(big.Int).Set(v.Number), } @@ -387,7 +383,7 @@ var StringPrototype = map[string]*BuiltinFunctionValue{ } if prev != len(str) { - out = append(out, &StringValue{str[prev:]}) + out = append(out, &StringValue{str[prev:len(str)]}) } return &ListValue{out}, nil @@ -431,7 +427,7 @@ func (v *StringValue) Get(key string) (Value, error) { return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key)) } -func (v *StringValue) Copy() Value { +func (v *StringValue) Clone() Value { return &StringValue{ v.Text, } @@ -447,17 +443,16 @@ func (v *ListValue) Type() ValueType { } func (v *ListValue) String() string { - var out strings.Builder - out.WriteString("[") + out := "[" for i, item := range v.Items { if i != 0 { - out.WriteString(", ") + out += ", " } - out.WriteString(item.DebugString()) + out += item.DebugString() } - out.WriteString("]") + out += "]" - return out.String() + return out } func (v *ListValue) DebugString() string { @@ -599,9 +594,15 @@ func (v *ListValue) Get(key string) (Value, error) { return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key)) } -func (v *ListValue) Copy() Value { +func (v *ListValue) Clone() Value { + n := make([]Value, len(v.Items)) + + for i, item := range v.Items { + n[i] = item.Clone() + } + return &ListValue{ - v.Items, + n, } } @@ -614,30 +615,33 @@ func (v *TupleValue) Type() ValueType { } func (v *TupleValue) String() string { - var out strings.Builder - out.WriteString("(") + out := "(" for i, item := range v.Items { if i != 0 { - out.WriteString(", ") + out += ", " } - out.WriteString(item.DebugString()) + out += item.DebugString() } if len(v.Items) <= 1 { - out.WriteString(",") + out += "," } - out.WriteString(")") + out += ")" - return out.String() + return out } func (v *TupleValue) DebugString() string { return v.String() } -func (v *TupleValue) Copy() Value { +func (v *TupleValue) Clone() Value { + n := make([]Value, len(v.Items)) + for i, item := range v.Items { + n[i] = item.Clone() + } return &TupleValue{ - v.Items, + n, } } @@ -657,7 +661,7 @@ func (v *TupleValue) Equals(other Value) bool { } var TuplePrototype = map[string]*BuiltinFunctionValue{ - "at": { + "at": &BuiltinFunctionValue{ "at", &FunctionSignature{ []TypeSignature{&IntegerSignature{}}, @@ -714,7 +718,7 @@ func (v *FunctionValue) Get(_ string) (Value, error) { return nil, errors.New("functions have no properties") } -func (v *FunctionValue) Copy() Value { +func (v *FunctionValue) Clone() Value { return &FunctionValue{ v.Name, v.Params, @@ -754,7 +758,7 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) { return nil, errors.New("functions have no properties") } -func (v *BuiltinFunctionValue) Copy() Value { +func (v *BuiltinFunctionValue) Clone() Value { return &BuiltinFunctionValue{ v.Name, v.Signature, @@ -763,82 +767,3 @@ func (v *BuiltinFunctionValue) Copy() Value { v.Constant, } } - -type RecordValue struct { - Entries map[string]Value -} - -func (v *RecordValue) Type() ValueType { - return RecordValueType -} - -func (v *RecordValue) String() string { - sb := strings.Builder{} - - sb.WriteString("(") - - n := 0 - for prop, value := range v.Entries { - if n != 0 { - sb.WriteString(", ") - } - - sb.WriteString(prop) - sb.WriteString(": ") - sb.WriteString(value.DebugString()) - - n += 1 - } - - if n == 1 { - sb.WriteString(",") - } - - sb.WriteString(")") - - return sb.String() -} - -func (v *RecordValue) DebugString() string { - return v.String() -} - -func (v *RecordValue) Copy() Value { - return &RecordValue{ - v.Entries, - } -} - -func (v *RecordValue) Equals(other Value) bool { - if other.Type() != RecordValueType { - return false - } - - r := other.(*RecordValue) - - if len(r.Entries) != len(v.Entries) { - return false - } - - for key, val := range v.Entries { - oth, ok := r.Entries[key] - if !ok { - return false - } - - if !val.Equals(oth) { - return false - } - } - - return true -} - -func (v *RecordValue) Get(key string) (Value, error) { - val, ok := v.Entries[key] - if !ok { - return nil, errors.New(fmt.Sprintf("record has no property \"%s\"", key)) - } - - return val, nil -} diff --git a/core/vm.go b/core/vm.go index 068b1c6..17a1a62 100644 --- a/core/vm.go +++ b/core/vm.go @@ -9,7 +9,6 @@ import ( "math" "math/big" "os" - "slices" "strconv" "strings" ) @@ -127,7 +126,7 @@ const ( // InstructionAppend Append to a list. stack: (... > list > item) => (... > list) InstructionAppend // InstructionFormList Form items on the stack into a list. The 2 bytes after the instructions are the amount of - // items to include. The order is reversed compared to on the stack; the top value on the stack is the last in the + // items to include) The order is reversed compared to on the stack; the top value on the stack is the last in the // list. InstructionFormList // InstructionConcatLists concatenate lists, producing a new list with the values of both lists. Pops two lists. @@ -140,12 +139,6 @@ const ( // being the last item in the tuple. InstructionDestructureTuple - // InstructionNewRecord Create a new empty record. - InstructionNewRecord - // InstructionSetRecordItem Set the value of an item in the record, and create it if it does not already exist. - // [..., record, item]; the following byte should be the index of a string constant with the name of the property. - InstructionSetRecordItem - // InstructionIndexList index into a list. The lower item is the container, and the top item // is the index. [..., container, index] -> [..., item] InstructionIndexList @@ -153,14 +146,10 @@ const ( // is the index. [..., container, index] -> [..., item] InstructionIndexTuple // InstructionIndexString index into a string. The lower item is the container, and the top item - // is the index. [..., container, index] -> [..., item]. Produces a new string with only the character - // at the indexed position + // is the index. [..., container, index] -> [..., item]. Produces a new string with the character + // at the position InstructionIndexString - // InstructionSetIndexList set the item at a given index in a list. - // [..., item, container, index] -> [..., item] - InstructionSetIndexList - // InstructionBreakpoint for debugging purposes InstructionBreakpoint ) @@ -273,14 +262,6 @@ func (b Bytecode) String() string { return "INDEX_TUPLE" case InstructionDestructureTuple: return "DESTRUCTURE_TUPLE" - case InstructionModInt: - return "MOD_INT" - case InstructionNewRecord: - return "NEW_RECORD" - case InstructionSetRecordItem: - return "SET_PROPERTY" - case InstructionIndexString: - return "INDEX_STRING" } return "UNDEFINED" } @@ -643,7 +624,7 @@ var DefaultGlobals = map[string]Value{ n, _ := v.Number.Float64() return &FloatValue{n}, nil case *FloatValue: - return v.Copy(), nil + return v.Clone(), nil case *StringValue: num, err := strconv.ParseFloat(v.Text, FloatSize) if err != nil { @@ -927,8 +908,8 @@ func (vm *VM) Next() bool { vm.scope = f.Scope vm.descend() - for _, v := range slices.Backward(f.Params) { - vm.addVar(v.Name, vm.Stack.Pop()) + for i := len(f.Params) - 1; i >= 0; i-- { + vm.addVar(f.Params[i].Name, vm.Stack.Pop()) } if f.Parent != nil { @@ -983,7 +964,7 @@ func (vm *VM) Next() bool { vm.Stack.Push(v) case InstructionSetLocal: - value := vm.Stack.Peek().Copy() + value := vm.Stack.Peek().Clone() name := vm.GetConstant(vm.NextByte()).(*StringValue).Text vm.setVar(name, value) @@ -991,7 +972,7 @@ func (vm *VM) Next() bool { case InstructionDeclareLocal: vm.addVar( vm.GetConstant(vm.NextByte()).(*StringValue).Text, - vm.Stack.Peek().Copy(), + vm.Stack.Peek().Clone(), ) case InstructionGetGlobal: @@ -1075,7 +1056,7 @@ func (vm *VM) Next() bool { vm.Stack.Push(r, l) case InstructionDuplicate: - vm.Stack.Push(vm.Stack.Peek().Copy()) + vm.Stack.Push(vm.Stack.Peek().Clone()) case InstructionAccessProperty: source := vm.Stack.Pop() @@ -1095,18 +1076,6 @@ func (vm *VM) Next() bool { vm.Stack.Push(member) - case InstructionSetRecordItem: - i := vm.Stack.Pop() - prop := vm.ReadConstant().(*StringValue) - r := vm.Stack.Peek().(*RecordValue) - - r.Entries[prop.Text] = i - - case InstructionNewRecord: - vm.Stack.Push(&RecordValue{ - map[string]Value{}, - }) - case InstructionIndexList: i := vm.Stack.Pop().(*IntegerValue) l := vm.Stack.Pop().(*ListValue) @@ -1117,7 +1086,7 @@ func (vm *VM) Next() bool { vm.error(fmt.Sprintf("index %d out of bounds", n)) } - vm.Stack.Push(l.Items[n].Copy()) + vm.Stack.Push(l.Items[n].Clone()) case InstructionIndexTuple: i := vm.Stack.Pop().(*IntegerValue) @@ -1129,7 +1098,7 @@ func (vm *VM) Next() bool { vm.error(fmt.Sprintf("index %d out of bounds", n)) } - vm.Stack.Push(t.Items[n].Copy()) + vm.Stack.Push(t.Items[n].Clone()) case InstructionIndexString: i := vm.Stack.Pop().(*IntegerValue) @@ -1143,18 +1112,6 @@ func (vm *VM) Next() bool { vm.Stack.Push(&StringValue{string(s.Text[n])}) - case InstructionSetIndexList: - n := vm.Stack.Pop().(*IntegerValue) - l := vm.Stack.Pop().(*ListValue) - - i := n.Number.Int64() - - if i < 0 || int64(len(l.Items)) <= i { - vm.error(fmt.Sprintf("index %d out of bounds", i)) - } - - l.Items[i] = vm.Stack.Peek().Copy() - case InstructionBreakpoint: /* // I'm keeping this @@ -1301,7 +1258,7 @@ func (vm *VM) HasNext() bool { } func (vm *VM) GetConstant(id Bytecode) Value { - return vm.chunk.Constants[id].Copy() + return vm.chunk.Constants[id].Clone() } func (vm *VM) ReadConstant() Value { diff --git a/examples/bad.ang b/examples/bad.ang deleted file mode 100644 index 80c9cb7..0000000 --- a/examples/bad.ang +++ /dev/null @@ -1,40 +0,0 @@ -import "lib/math.ang" - -primes := [2] - -func is_prime(x: number) boolean { - i := 0 - while i < primes.length() && primes.at(i)*primes.at(i) < x { - if mod(x, primes.at(i)) == 0 { - return false - } - i = i + 1 - } - - return true -} - -n := 1 -max := 100000 - -while n < max { - n = n + 2 - - if is_prime(n) { - primes.append(n) - - # Update counter - print(char(0x0D)) - print(str(n)) - print("/") - print(str(max)) - print(char(0x09)) - print(str(roundd(n/max*100, 2))) - print("%") - print(char(0x09)) - print(str(primes.length())) - print(" primes") - } -} - -write(str(primes)) diff --git a/examples/blemish.ang b/examples/blemish.ang deleted file mode 100644 index ee25ddf..0000000 --- a/examples/blemish.ang +++ /dev/null @@ -1,20 +0,0 @@ -fn fib(n: int) -> int { - if n < 2 { - n - } else { - fib(n-1) + fib(n-2) - } -} - -println(fib(2)) - -fn other(n: int) -> int { - if n <= 0 { - n - } else { - println(n) - (n - 1) - } -} - -println(other(2)) diff --git a/examples/chars.ang b/examples/chars.ang deleted file mode 100644 index 343ccee..0000000 --- a/examples/chars.ang +++ /dev/null @@ -1,15 +0,0 @@ - -MAX_WIDTH := 16 - -print(" ") -w := 1 -n := 0x21 -while n < 0xA0 { - print(char(n)) - n = n + 1 - w = w + 1 - if w >= MAX_WIDTH { - write("") - w = 0 - } -} diff --git a/examples/codegen.ang b/examples/codegen.ang deleted file mode 100644 index 7d44558..0000000 --- a/examples/codegen.ang +++ /dev/null @@ -1,24 +0,0 @@ - -passphrase := "Hello world!".split("") -start := [0, 0, 0] -modulus := 10 -base := byte("!") - -i := 0 -n := 0 -while n < passphrase.length() { - b := byte(passphrase.at(n)) - - v = start.at(i) + b - base - while v >= modulus { - v = v - modulus - } - - start.put(i, v) - - if i >= 3 { - i = 0 - } - n = n + 1 -} - diff --git a/examples/emoji.ang b/examples/emoji.ang deleted file mode 100644 index 09f0cb1..0000000 --- a/examples/emoji.ang +++ /dev/null @@ -1,2 +0,0 @@ - -write(char(0x12) + char(0x85) + char(0x07)) diff --git a/examples/era3.ang b/examples/era3.ang deleted file mode 100644 index 966a458..0000000 --- a/examples/era3.ang +++ /dev/null @@ -1,11 +0,0 @@ -fn counter() -> (fn() -> int) { - i := 0 - - fn() -> int { i = i + 1 } -} - -next := counter() - -println(next()) -println(next()) -println(next()) diff --git a/examples/fails.ang b/examples/fails.ang deleted file mode 100644 index 8e3174d..0000000 --- a/examples/fails.ang +++ /dev/null @@ -1,4 +0,0 @@ -import "lib/honning.ang" - -write(_bell+_italic+"Hello "+_underline+"world "+_strike+"micheal"+_reset) - diff --git a/examples/fib.ang b/examples/fib.ang index 4860a1e..63a432a 100644 --- a/examples/fib.ang +++ b/examples/fib.ang @@ -3,9 +3,14 @@ fn range(from: int, to: int) -> (fn() -> (int, bool)) { i := from - 1 + end := to - 1 fn() -> (int, bool) { - (i = i+1, i+1 < to) + if i < end { + (i = i+1, true) + } else { + (-1, false) + } } } diff --git a/examples/foo.ang b/examples/foo.ang deleted file mode 100644 index 1047386..0000000 --- a/examples/foo.ang +++ /dev/null @@ -1,8 +0,0 @@ - -fn foo(n: int) -> str { - if n % 2 == 0 { - "foo" - } else { - "bar" - } -} diff --git a/examples/imp.ang b/examples/imp.ang deleted file mode 100644 index 0ccb3ed..0000000 --- a/examples/imp.ang +++ /dev/null @@ -1,15 +0,0 @@ - -func is_cool(x: number|string) boolean { - if x == "cool" { - return true - } else if x == 69 { - return true - } - - return nil -} - -write(str(is_cool("not cool"))) -write(str(is_cool("cool"))) -write(str(is_cool(0))) -write(str(is_cool(69))) diff --git a/examples/inc.ang b/examples/inc.ang deleted file mode 100644 index 8a697a7..0000000 --- a/examples/inc.ang +++ /dev/null @@ -1,2 +0,0 @@ - -foo := include "foo.ang" diff --git a/examples/list.ang b/examples/list.ang index d34887e..9866d3e 100644 --- a/examples/list.ang +++ b/examples/list.ang @@ -1,6 +1,6 @@ # Empty list -println([]any) +println([]) # List with items println([3, 1, 4, 1, 5, 9, 2, 6, 5]) @@ -8,25 +8,25 @@ println([3, 1, 4, 1, 5, 9, 2, 6, 5]) # List with items of different types println(["", "私はかっこいいです。", true, nil, nil, 1, 2]) -a := []int +a := [] -a.push(1) -a.push(2) +a = a + [1] +a = a + [2] println(a) -list := []int +list := [] x := 0 for n in 0..100 { x = x + 2*n + 1 - list.push(x) + list = list + [x] } println(list) -println(list.map(fn(a: int) -> int { +println(list.map(func(a) { return a - 1 })) println(list.length()) diff --git a/examples/massacre.ang b/examples/massacre.ang deleted file mode 100644 index 6dbde3c..0000000 --- a/examples/massacre.ang +++ /dev/null @@ -1,4 +0,0 @@ -a := fn b(n: int) -> int { - n + 1 -} - diff --git a/examples/network.ang b/examples/network.ang deleted file mode 100644 index 86ba041..0000000 --- a/examples/network.ang +++ /dev/null @@ -1,8 +0,0 @@ - -func get(url: string) string { - res := request("GET", url, "") - - return res.text -} - -write(get("https://www.neemek.com/hello.txt")) diff --git a/examples/neutral-pH.ang b/examples/neutral-pH.ang deleted file mode 100644 index f867a72..0000000 --- a/examples/neutral-pH.ang +++ /dev/null @@ -1,18 +0,0 @@ -import "../lib/math.ang" - -# Inputs -c := 1.0*pow(10.0, -2.0) - -println(c) -pH := -log(c, 10.0) - -println(pH) - -if pH == 7.0 { - println("pH-en er nøytral (=7)") -} else if pH < 7.0 { - println("pH-en er sur (<7)") -} else { - println("pH-en er basisk (>7)") -} - diff --git a/examples/pos.ang b/examples/pos.ang deleted file mode 100644 index 2eda099..0000000 --- a/examples/pos.ang +++ /dev/null @@ -1,21 +0,0 @@ - -type Vec2 = (float, float) - -fn add(a: Vec2, b: Vec2) -> Vec2 { - (a[0] + b[0], a[1] + b[1]) -} - -fn sub(a: Vec2, b: Vec2) -> Vec2 { - (a[0] - b[0], a[1] - b[1]) -} - -fn dot(a: Vec2, b: Vec2) -> float { - a[0]*b[0] + a[1]*b[1] -} - -u := (0.0, 1.0) -v := (2.0, 3.0) - -println(add(u, v)) -println(sub(u, v)) -println(dot(u, v)) diff --git a/examples/recursive.ang b/examples/recursive.ang index f30109f..caa0e37 100644 --- a/examples/recursive.ang +++ b/examples/recursive.ang @@ -1,15 +1,14 @@ # This program computes the fibonacci numbers using recursion (O(2^n)) # It is very slow -fn fib(x: number) number { +func fib(x: number) number { if x <= 1 { - x - } else { - fib(x - 1) + fib(x - 2) + return x } + return fib(x - 1) + fib(x - 2) } n := 0 while n < 100 { - println(fib(n)) + write(str(fib(n))) n = n + 1 } diff --git a/examples/sqrt.ang b/examples/sqrt.ang deleted file mode 100755 index f013699..0000000 --- a/examples/sqrt.ang +++ /dev/null @@ -1,6 +0,0 @@ -#!/Users/neemek/Code/anglais/cli/cli run - -import "lib/math.ang" - -x := sqrt(9485739448) -write(str(x)) diff --git a/examples/very_bad.ang b/examples/very_bad.ang deleted file mode 100644 index dd18680..0000000 --- a/examples/very_bad.ang +++ /dev/null @@ -1,8 +0,0 @@ - -a := 0 -while a < 10 { - print(".") - a = a + 1 -} - -write("") diff --git a/examples/warp.ang b/examples/warp.ang deleted file mode 100644 index 3186556..0000000 --- a/examples/warp.ang +++ /dev/null @@ -1,25 +0,0 @@ - -# Aliases -type Receiver = fn() -> any -type Sender = fn(any) - -fn new_channel() -> (Sender, Receiver) { - queue := [] - - fn send(n: any) { - queue.push(n) # mutate in-place - } - - fn recv() -> any | nil { - queue.pop() - } - - (send, recv) -} - -(send, recv) := new_channel() - -( - send:, - recv:, -) diff --git a/examples/worst.ang b/examples/worst.ang deleted file mode 100644 index 47b90db..0000000 --- a/examples/worst.ang +++ /dev/null @@ -1,4 +0,0 @@ -import "worst.ang" - -write("Hello world!") - diff --git a/foo.ang b/foo.ang deleted file mode 100644 index e69de29..0000000 diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index 2d35219..0000000 --- a/go.work.sum +++ /dev/null @@ -1 +0,0 @@ -github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= diff --git a/lib/list.ang b/lib/list.ang index 792751f..254912d 100644 --- a/lib/list.ang +++ b/lib/list.ang @@ -1,5 +1,5 @@ -fn map(list: [T], f: fn(T) -> R) -> [R] { +fn ([T]) map(f: fn(T) -> R) -> [R] { out := [] for v in list.iter() { diff --git a/lib/math.ang b/lib/math.ang index 9dac01a..120e60e 100644 --- a/lib/math.ang +++ b/lib/math.ang @@ -249,5 +249,3 @@ fn tan(x: float) -> float { # todo 0.0 } - -(E:, PI:, sqrt:, log:, ln:, exp:, pow:) diff --git a/lib/testing.ang b/lib/testing.ang index 82e7b77..59aed2c 100644 --- a/lib/testing.ang +++ b/lib/testing.ang @@ -1,25 +1,25 @@ NAMESPACE := "" -fn namespace(name: str, test: fn()) { +func namespace(name: string, test: func()) { NAMESPACE = name test() } -fn eq(a: T, b: T) { +func assertEqual(a: any, b: any) { if a != b { - println(format("assertion error: % should (but doesn't) equal %", [a, b])) + write(format("assertion error: % should (but doesn't) equal %", [a, b])) exit(1) } else if env("DEBUG") != "" { - println(format("assertion success: % equals %", [a, b])) + write(format("assertion success: % equals %", [a, b])) } } -fn neq(a: T, b: T) { +func assertNotEqual(a: any, b: any) { if a == b { - println(format("assertion error: % shouldn't (but does) equal %", [a, b])) + write(format("assertion error: % shouldn't (but does) equal %", [a, b])) exit(1) } else if env("DEBUG") != "" { - println(format("assertion success: % doesn't equal %", [a, b])) + write(format("assertion success: % doesn't equal %", [a, b])) } } diff --git a/records.ang b/records.ang deleted file mode 100644 index 198475c..0000000 --- a/records.ang +++ /dev/null @@ -1,29 +0,0 @@ - -type User = ( - id: int, - name: str, -) - - -user := { - user_id := 0 - all_users := []User - - ( - get_all: fn() -> [User] { all_users }, - new: fn(name: str) -> User { - data := (id: { user_id = user_id + 1 }, name:) - all_users.append(data) - data - }, - fmt: fn(user: User) -> str { - user.name + "(" + str(user.id) + ")" - } - ) -} - -abe := user.new("abe") -lincoln := user.new("lincoln") - -println(abe) -println(lincoln) diff --git a/test_all.sh b/test_all.sh index a6bdd01..9905f24 100755 --- a/test_all.sh +++ b/test_all.sh @@ -2,7 +2,7 @@ echo '=== Building WASM lib ===' cd wasm || exit 1 -if ! GOOS=js GOARCH=wasm go build . "$@"; then +if ! GOOS=js GOARCH=wasm go build .; then echo "=x= Had error building WASM lib =x=" exit 1 else @@ -12,7 +12,7 @@ cd .. echo '=== Building CLI ===' cd cli || exit 1 -if ! go build . "$@"; then +if ! go build .; then echo "=x= Had error building CLI =x=" exit 1 else @@ -22,7 +22,7 @@ cd .. echo "=== Running go core tests ===" cd core || exit 1 -if ! go test . "$@"; then +if ! go test .; then echo "=x= Core testing failed =x= " exit 1 else diff --git a/tests/forp.ang b/tests/forp.ang deleted file mode 100644 index 0891ab6..0000000 --- a/tests/forp.ang +++ /dev/null @@ -1,52 +0,0 @@ - -fn range(from: int, to: int) -> (fn() -> (int, bool)) { - i := from - 1 - - fn() -> (int, bool) { - i = i + 1 - if i >= to { - (-1, false) - } else { - (i, true) - } - } -} - -for i in range(0, 10) { - println(i) -} - -fn chars(s: str) -> (fn() -> (str, bool)) { - i := 0 - - fn() -> (str, bool) { - if i >= s.length() { - ("", false) - } else { - c := s[i] - i = i + 1 - - (c, true) - } - } -} - -for c in chars("hello") { - print(c) - print(" ") -} - -println("") - -fn items(l: [any]) -> (fn() -> (any, bool)) { - i := 0 - - fn() -> (any, bool) { - if i >= l.length() { - (nil, false) - } else { - i = i + 1 - (l[i - 1], true) - } - } -} diff --git a/tests/list.ang b/tests/list.ang index e98fcf8..8554f14 100644 --- a/tests/list.ang +++ b/tests/list.ang @@ -34,10 +34,3 @@ assertEq("the, first, time".split(", "), ["the", "first", "time"]) # list indexing assertEq([1, 2, 3].at(1), 2) assertEq(["a", "b", "c"].at(2), "c") - -# mutating list -a := [1, 2, 3] -assertEq(a, [1, 2, 3]) - -a[1] = 4 -assertEq(a, [1, 4, 3]) diff --git a/tests/tuple.ang b/tests/tuple.ang index 65e99e9..81aa508 100644 --- a/tests/tuple.ang +++ b/tests/tuple.ang @@ -9,10 +9,3 @@ fn neighbours(n: int) -> (int, int) { } assertEq(neighbours(2), (1, 3)) - -a := (1, 2) -assertEq(a, (1, 2)) - -(x, y) := a -assertEq(x, 1) -assertEq(y, 2)