diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 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 deleted file mode 100644 index 11646b4..0000000 --- a/.idea/anglais.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 6c7658f..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 36d0426..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/core/compiler.go b/core/compiler.go index 617871e..83098d5 100644 --- a/core/compiler.go +++ b/core/compiler.go @@ -265,6 +265,30 @@ 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) @@ -953,6 +977,41 @@ 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) } @@ -1196,6 +1255,13 @@ 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 ba504b8..1701479 100644 --- a/core/lexer.go +++ b/core/lexer.go @@ -7,7 +7,7 @@ import ( ) type Token struct { - Type TokenKind + Kind 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.Type.String(), t.Lexeme, t.Start, t.End, t.Line) + return fmt.Sprintf("token %s, '%s' %d -> %d, line %d", t.Kind.String(), t.Lexeme, t.Start, t.End, t.Line) } type TokenKind uint64 @@ -185,6 +185,8 @@ func (t TokenKind) String() string { return "for" case TokenIn: return "in" + case TokenPercent: + return "percent" } panic("UNDEFINED TOKENTYPE STRING CONVERSION") @@ -415,7 +417,7 @@ func (l *Lexer) NextToken() (Token, error) { func NewToken(t TokenKind, start Pos, end Pos, line Pos, lexeme string) Token { return Token{ - Type: t, + Kind: t, Start: start, End: end, Line: line, @@ -430,7 +432,7 @@ func (l *Lexer) Tokenize() ([]Token, error) { for ; err == nil; tok, err = l.NextToken() { tokens = append(tokens, tok) - if tok.Type == TokenEOF { + if tok.Kind == TokenEOF { break } } diff --git a/core/lexer_test.go b/core/lexer_test.go index c8158b3..a20a0c1 100644 --- a/core/lexer_test.go +++ b/core/lexer_test.go @@ -148,8 +148,8 @@ func TestLexer_NextToken(t *testing.T) { continue } - if tok.Type != expectedType { - t.Errorf("Expected token type '%s' but got '%s'", expectedType, tok.Type) + if tok.Kind != expectedType { + t.Errorf("Expected token type '%s' but got '%s'", expectedType, tok.Kind) } 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.Type != TokenEOF { + for err == nil && tok.Kind != 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.Type != TokenEOF { + for err == nil && tok.Kind != TokenEOF { tok, err = lex.NextToken() } } diff --git a/core/nodes.go b/core/nodes.go index 056b206..0a04c44 100644 --- a/core/nodes.go +++ b/core/nodes.go @@ -29,6 +29,7 @@ const ( NilNodeType ListNodeType TupleNodeType + RecordNodeType BinaryNodeType UnaryNodeType BlockNodeType @@ -93,6 +94,12 @@ 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" } @@ -237,6 +244,36 @@ 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 @@ -269,6 +306,8 @@ func (n BinaryOperation) String() string { return "multiply" case BinaryDivision: return "divide" + case BinaryModulo: + return "modulo" case BinaryEquality: return "equality" case BinaryInequality: @@ -499,7 +538,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 otheriwise %s", n.condition.String(), n.do.String(), n.otherwise.String()) + return fmt.Sprintf("if %s then %s otherwise %s", n.condition.String(), n.do.String(), n.otherwise.String()) } func (n ConditionalNode) Bounds() (Pos, Pos) { diff --git a/core/parser.go b/core/parser.go index 6462b7a..ca360af 100644 --- a/core/parser.go +++ b/core/parser.go @@ -76,9 +76,13 @@ func (p ParsingError) Format() string { } type Parser struct { - source string - trace []string - tokens []Token + source string + trace []string + tokens []Token + state ParserState +} + +type ParserState struct { prev *Token curr *Token pos Pos @@ -90,7 +94,9 @@ func NewParser(source string, trace []string, tokens []Token) *Parser { source: source, trace: trace, tokens: tokens, - pos: 0, + state: ParserState{ + pos: 0, + }, } } @@ -116,11 +122,11 @@ func (p *Parser) Parse(path string) (*Program, error) { // initialize current p.advance() - for int(p.pos) < len(p.tokens) && p.curr.Type != TokenEOF { + for int(p.state.pos) < len(p.tokens) && p.state.curr.Kind != TokenEOF { for p.accept(TokenNewLine) { } - if p.curr.Type == TokenEOF { + if p.state.curr.Kind == TokenEOF { break } @@ -139,25 +145,25 @@ func (p *Parser) Parse(path string) (*Program, error) { &BlockNode{ statements, 0, - p.curr.End, + p.state.curr.End, }, path, }, nil } func (p *Parser) accept(tokenType TokenKind) bool { - if p.curr == nil { + if p.state.curr == nil { log.Fatal("unexpected current token nil") return false } - if p.ignoreNewLine && tokenType != TokenNewLine { - for p.curr.Type == TokenNewLine { + if p.state.ignoreNewLine && tokenType != TokenNewLine { + for p.state.curr.Kind == TokenNewLine { p.advance() } } - if (*p.curr).Type == tokenType { + if (*p.state.curr).Kind == tokenType { p.advance() return true } @@ -165,46 +171,52 @@ func (p *Parser) accept(tokenType TokenKind) bool { return false } -func (p *Parser) acceptAll(tokenTypes ...TokenKind) bool { - if int(p.pos)+len(tokenTypes) > len(p.tokens) { - return false - } +func (p *Parser) getState() ParserState { + return p.state +} - for i, tokenType := range tokenTypes { - if p.tokens[int(p.pos)+i].Type != tokenType { +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) 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.curr.Type, reason), p.curr) + return p.error(fmt.Sprintf("Expected token %s, got %s; %s", tokenType, p.state.curr.Kind, reason), p.state.curr) } return nil } func (p *Parser) peek() (Token, error) { - if p.pos >= Pos(len(p.tokens)) { + if p.state.pos >= Pos(len(p.tokens)) { return Token{}, errors.New("cannot peek beyond tokens") } - return p.tokens[p.pos], nil + return p.tokens[p.state.pos], nil } func (p *Parser) advance() { - p.prev = p.curr + p.state.prev = p.state.curr - if p.pos < Pos(len(p.tokens)) { - p.curr = &p.tokens[p.pos] + if p.state.pos < Pos(len(p.tokens)) { + p.state.curr = &p.tokens[p.state.pos] } else { - p.curr = nil + p.state.curr = nil } - p.pos++ + p.state.pos++ } func (p *Parser) error(error string, causer *Token) error { @@ -224,10 +236,10 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { } } - oldIgnoreNewline := p.ignoreNewLine - p.ignoreNewLine = false + oldIgnoreNewline := p.state.ignoreNewLine + p.state.ignoreNewLine = false - start := p.prev.Start + start := p.state.prev.Start var statements []Node for !p.accept(TokenCloseBrace) { @@ -251,22 +263,22 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { } } - p.ignoreNewLine = oldIgnoreNewline + p.state.ignoreNewLine = oldIgnoreNewline - return &BlockNode{statements, start, p.prev.End}, nil + return &BlockNode{statements, start, p.state.prev.End}, nil } - t := p.curr - switch t.Type { + t := p.state.curr + switch t.Kind { case TokenType: p.advance() - start := p.prev.Start + start := p.state.prev.Start if err := p.expect(TokenName, "types must have a name"); err != nil { return nil, err } - name := p.prev + name := p.state.prev if err := p.expect(TokenAssign, "type aliases must be defined with an assign"); err != nil { return nil, err @@ -282,7 +294,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { sig, start, - p.prev.End, + p.state.prev.End, }, nil case TokenIf: @@ -300,7 +312,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { var otherwise Node if p.accept(TokenElse) { - otherwise, err = p.expression(p.curr.Type != TokenIf) + otherwise, err = p.expression(p.state.curr.Kind != TokenIf) if err != nil { return nil, err } @@ -316,7 +328,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { case TokenReturn: p.advance() - start := p.prev.Start + start := p.state.prev.Start v, err := p.expression(false) if err != nil { @@ -326,12 +338,12 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { return &ReturnNode{ v, start, - p.prev.End, + p.state.prev.End, }, nil case TokenWhile: p.advance() - start := p.prev.Start + start := p.state.prev.Start cond, err := p.expression(false) if err != nil { @@ -347,12 +359,12 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { cond, logic, start, - p.prev.End, + p.state.prev.End, }, nil case TokenFor: p.advance() - start := p.prev.Start + start := p.state.prev.Start counter, err := p.expression(false) if err != nil { @@ -379,12 +391,12 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { logic, start, - p.prev.End, + p.state.prev.End, }, nil case TokenInclude: p.advance() - start := p.prev.Start + start := p.state.prev.Start if err := p.expect(TokenString, "import requires a path/name to include"); err != nil { return nil, err @@ -392,13 +404,13 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { return &IncludeNode{ &StringNode{ - p.prev.Lexeme[1 : len(p.prev.Lexeme)-1], - p.prev.Lexeme, - p.prev.Start, - p.prev.End, + p.state.prev.Lexeme[1 : len(p.state.prev.Lexeme)-1], + p.state.prev.Lexeme, + p.state.prev.Start, + p.state.prev.End, }, start, - p.prev.End, + p.state.prev.End, }, nil default: @@ -408,7 +420,7 @@ func (p *Parser) expression(mustBeBlock bool) (Node, error) { } if p.accept(TokenDeclare) || p.accept(TokenAssign) { - isDeclaration := p.prev.Type == TokenDeclare + isDeclaration := p.state.prev.Kind == TokenDeclare // possibly assign tuples; not implemented yet v, err := p.expression(false) @@ -509,7 +521,7 @@ func (p *Parser) binary() (Node, error) { r := values.Pop() l := values.Pop() opToken := ops.Pop() - op := tokenToBinaryOperation(opToken.Type) + op := tokenToBinaryOperation(opToken.Kind) start, _ := l.Bounds() _, end := r.Bounds() @@ -524,12 +536,12 @@ func (p *Parser) binary() (Node, error) { }) } - for isBinaryOperator(p.curr.Type) { - for ops.Current > 0 && binaryPrecedence(p.curr.Type) <= binaryPrecedence(ops.Peek().Type) { + for isBinaryOperator(p.state.curr.Kind) { + for ops.Current > 0 && binaryPrecedence(p.state.curr.Kind) <= binaryPrecedence(ops.Peek().Kind) { reduce() } - ops.Push(p.curr) + ops.Push(p.state.curr) p.advance() v, err := p.chain() @@ -558,16 +570,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.prev + name := p.state.prev f = &AccessNode{ f, - p.prev, + p.state.prev, name.Start, name.End, } - if p.curr.Type == TokenOpenParenthesis { + if p.state.curr.Kind == TokenOpenParenthesis { args, err := p.parseArgs() if err != nil { return nil, err @@ -577,11 +589,11 @@ func (p *Parser) chain() (Node, error) { f, args, name.Start, - p.prev.End, + p.state.prev.End, } } - } else if p.curr.Type == TokenOpenParenthesis { - start := p.curr.Start + } else if p.state.curr.Kind == TokenOpenParenthesis { + start := p.state.curr.Start args, err := p.parseArgs() if err != nil { return nil, err @@ -592,10 +604,10 @@ func (p *Parser) chain() (Node, error) { args, start, - p.prev.End, + p.state.prev.End, } } else if p.accept(TokenOpenBracket) { - start := p.prev.Start + start := p.state.prev.Start index, err := p.expression(false) if err != nil { @@ -610,7 +622,7 @@ func (p *Parser) chain() (Node, error) { f, index, start, - p.prev.End, + p.state.prev.End, } } else { break @@ -621,71 +633,71 @@ func (p *Parser) chain() (Node, error) { } func (p *Parser) factor() (Node, error) { - switch (*p.curr).Type { + switch (*p.state.curr).Kind { case TokenString: p.advance() return &StringNode{ - (*p.prev).Lexeme[1 : len((*p.prev).Lexeme)-1], - (*p.prev).Lexeme, - p.prev.Start, - p.prev.End, + (*p.state.prev).Lexeme[1 : len((*p.state.prev).Lexeme)-1], + (*p.state.prev).Lexeme, + p.state.prev.Start, + p.state.prev.End, }, nil case TokenInteger: p.advance() - num, success := new(big.Int).SetString(p.prev.Lexeme, 10) + num, success := new(big.Int).SetString(p.state.prev.Lexeme, 10) if !success { - return nil, p.error(fmt.Sprintf("cannot parse integer base 10: %s", p.prev.Lexeme), p.prev) + return nil, p.error(fmt.Sprintf("cannot parse integer base 10: %s", p.state.prev.Lexeme), p.state.prev) } return &IntegerNode{ num, - p.prev.Start, - p.prev.End, + p.state.prev.Start, + p.state.prev.End, }, nil case TokenFloat: p.advance() - num, err := strconv.ParseFloat((*p.prev).Lexeme, FloatSize) + num, err := strconv.ParseFloat((*p.state.prev).Lexeme, FloatSize) if err != nil { - return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.prev) + return nil, p.error(fmt.Sprintf("Error parsing number: %v", err), p.state.prev) } return &FloatNode{ num, - p.prev.Start, - p.prev.End, + p.state.prev.Start, + p.state.prev.End, }, nil case TokenHexadecimal: p.advance() - start := (*p.prev).Start - num, ok := new(big.Int).SetString(p.prev.Lexeme[2:], 16) + start := (*p.state.prev).Start + num, ok := new(big.Int).SetString(p.state.prev.Lexeme[2:], 16) if !ok { - return nil, p.error(fmt.Sprintf("cannot parse hexadecimal: %v", p.prev.Lexeme), p.prev) + return nil, p.error(fmt.Sprintf("cannot parse hexadecimal: %v", p.state.prev.Lexeme), p.state.prev) } return &IntegerNode{ num, start, - p.prev.End, + p.state.prev.End, }, nil case TokenTrue: p.advance() return &BooleanNode{ true, - p.prev.Start, - p.prev.End, + p.state.prev.Start, + p.state.prev.End, }, nil case TokenFalse: p.advance() return &BooleanNode{ false, - p.prev.Start, - p.prev.End, + p.state.prev.Start, + p.state.prev.End, }, nil case TokenNil: @@ -694,7 +706,7 @@ func (p *Parser) factor() (Node, error) { case TokenOpenBracket: p.advance() - start := p.prev.Start + start := p.state.prev.Start // TODO: find better solution; current one is messy // Maybe perform better analysis to determine the kind of the list... @@ -708,12 +720,12 @@ func (p *Parser) factor() (Node, error) { []Node{}, s, start, - p.prev.End, + p.state.prev.End, }, nil } - oldIgnoreNewline := p.ignoreNewLine - p.ignoreNewLine = true + oldIgnoreNewline := p.state.ignoreNewLine + p.state.ignoreNewLine = true var values []Node for !p.accept(TokenCloseBracket) { @@ -731,19 +743,19 @@ func (p *Parser) factor() (Node, error) { values = append(values, value) } - p.ignoreNewLine = oldIgnoreNewline + p.state.ignoreNewLine = oldIgnoreNewline return &ListNode{ values, nil, start, - p.prev.End, + p.state.prev.End, }, nil // unary minus case TokenMinus: p.advance() - op := p.prev + op := p.state.prev f, err := p.factor() if err != nil { @@ -754,12 +766,12 @@ func (p *Parser) factor() (Node, error) { f, op, op.Start, - p.prev.End, + p.state.prev.End, }, nil case TokenBang: p.advance() - op := p.prev + op := p.state.prev v, err := p.factor() if err != nil { @@ -771,16 +783,16 @@ func (p *Parser) factor() (Node, error) { v, op, op.Start, - p.prev.End, + p.state.prev.End, }, nil case TokenName: p.advance() - name := (*p.prev).Lexeme - start := p.prev.Start - nameEnd := p.prev.End + name := (*p.state.prev).Lexeme + start := p.state.prev.Start + nameEnd := p.state.prev.End - if p.curr.Type == TokenOpenParenthesis { + if p.state.curr.Kind == TokenOpenParenthesis { args, err := p.parseArgs() if err != nil { return nil, err @@ -794,7 +806,7 @@ func (p *Parser) factor() (Node, error) { }, args, start, - p.prev.End, + p.state.prev.End, }, nil } @@ -806,11 +818,11 @@ func (p *Parser) factor() (Node, error) { case TokenFunc: p.advance() - start := p.prev.Start + start := p.state.prev.Start var name *Token if p.accept(TokenName) { // can be unnamed, but accept name if it is named - name = p.prev + name = p.state.prev } params, err := p.parseParams() @@ -842,7 +854,7 @@ func (p *Parser) factor() (Node, error) { yield, logic, start, - p.prev.End, + p.state.prev.End, } if name != nil { @@ -851,7 +863,7 @@ func (p *Parser) factor() (Node, error) { fn, true, start, - p.prev.End, + p.state.prev.End, }, nil } @@ -859,11 +871,67 @@ func (p *Parser) factor() (Node, error) { case TokenOpenParenthesis: p.advance() - start := p.prev.Start + start := p.state.prev.Start + oldCare := p.state.ignoreNewLine + p.state.ignoreNewLine = true + p.skipNewLines() // we're inside an object - if p.acceptAll(TokenName, TokenColon) { - return nil, p.error("objects are not implemented yet (TBD)", p.prev) + 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 } v, err := p.expression(false) @@ -876,6 +944,7 @@ func (p *Parser) factor() (Node, error) { return nil, err } + p.state.ignoreNewLine = oldCare return v, nil } @@ -897,26 +966,27 @@ func (p *Parser) factor() (Node, error) { } } + p.state.ignoreNewLine = oldCare return &TupleNode{ items, start, - p.prev.End, + p.state.prev.End, }, nil case TokenBreakpoint: p.advance() return &BreakpointNode{ - p.prev.Start, - p.prev.End, + p.state.prev.Start, + p.state.prev.End, }, nil case TokenOpenBrace: return p.expression(true) default: - return nil, p.error(fmt.Sprintf("invalid factor %s", p.curr), p.curr) + return nil, p.error(fmt.Sprintf("invalid factor %s", p.state.curr), p.state.curr) } } @@ -956,7 +1026,7 @@ func (p *Parser) parseParams() ([]FunctionParameter, error) { params := make([]FunctionParameter, 0) if p.accept(TokenName) { - name := (*p.prev).Lexeme + name := (*p.state.prev).Lexeme if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil { return nil, err } @@ -977,7 +1047,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.prev).Lexeme + name = (*p.state.prev).Lexeme if err := p.expect(TokenColon, "parameters must have a type separated by a colon"); err != nil { return nil, err } @@ -1005,9 +1075,42 @@ 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 - if p.acceptAll(TokenName, TokenColon) { - return nil, p.error("objects are not implemented yet (TBD)", p.prev) + 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 } v, err := p.parseSignature() @@ -1043,6 +1146,8 @@ 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 @@ -1095,7 +1200,7 @@ func (p *Parser) parseSignature() (TypeSignature, error) { if err := p.expect(TokenName, "type must be a name"); err != nil { return nil, err } - name := (*p.prev).Lexeme + name := (*p.state.prev).Lexeme switch name { case "str": @@ -1130,3 +1235,8 @@ 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 79b60c9..b58330b 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.pos != 0 { + if p.state.pos != 0 { t.Error("parser should initialize position at 0") } @@ -715,6 +715,95 @@ 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, + }, + }, } } @@ -912,6 +1001,23 @@ 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") } @@ -939,25 +1045,55 @@ func TestParser_Parse(t *testing.T) { } } -func TestParser_AcceptAll(t *testing.T) { +func TestParser_AcceptSeq(t *testing.T) { p := NewParser("a:", []string{}, []Token{ NewToken(TokenName, 0, 1, 0, "a"), - NewToken(TokenColon, 1, 2, 0, "a"), + NewToken(TokenColon, 1, 2, 0, ":"), + NewToken(TokenEOF, 2, 2, 0, ""), }) - if !p.acceptAll(TokenName, TokenColon) { + // initialize + p.advance() + + if !p.acceptSeq(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{ +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, ""), }) - if p.acceptAll(TokenName, TokenColon) { + // 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) { + p := NewParser("a", []string{}, []Token{ + NewToken(TokenName, 0, 1, 0, "a"), + NewToken(TokenEOF, 1, 1, 0, ""), + }) + + // initialize + p.advance() + + if p.acceptSeq(TokenName, TokenColon) { t.Fatalf("tokens were incorrectly accepted") } diff --git a/core/types.go b/core/types.go index 13f9e1b..81dafd8 100644 --- a/core/types.go +++ b/core/types.go @@ -21,6 +21,7 @@ const ( TypeComposite TypeInner TypeNamed + TypeRecord ) func (t Type) String() string { @@ -49,9 +50,11 @@ 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 { @@ -484,6 +487,27 @@ 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 { @@ -521,3 +545,56 @@ 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 94c666f..efb5f41 100644 --- a/core/values.go +++ b/core/values.go @@ -22,7 +22,7 @@ const ( ObjectValueType FunctionValueType BuiltinFunctionValueType - VariableValueType + RecordValueType ) func (v ValueType) String() string { @@ -47,8 +47,8 @@ func (v ValueType) String() string { return "function" case BuiltinFunctionValueType: return "builtin function" - case VariableValueType: - return "variable" + case RecordValueType: + return "record" } return "undefined" @@ -117,8 +117,12 @@ type Value interface { // Get a member from the value. An error is returned if the member does not exist Get(string) (Value, error) - // 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 + // 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 } type NilValue struct{} @@ -143,7 +147,7 @@ func (v *NilValue) Get(_ string) (Value, error) { return nil, errors.New("nil has no properties") } -func (v *NilValue) Clone() Value { +func (v *NilValue) Copy() Value { return &NilValue{} } @@ -175,7 +179,7 @@ func (v *BoolValue) Get(_ string) (Value, error) { return nil, errors.New("booleans have no properties") } -func (v *BoolValue) Clone() Value { +func (v *BoolValue) Copy() Value { return &BoolValue{ v.Boolean, } @@ -258,11 +262,11 @@ func (v *ObjectValue) Get(key string) (Value, error) { } } -func (v *ObjectValue) Clone() Value { +func (v *ObjectValue) Copy() Value { m := make(map[string]Value, len(v.Members)) for name, mem := range v.Members { - m[name] = mem.Clone() + m[name] = mem.Copy() } return &ObjectValue{ @@ -303,7 +307,7 @@ func (v *FloatValue) Get(_ string) (Value, error) { return nil, errors.New("numbers have no properties") } -func (v *FloatValue) Clone() Value { +func (v *FloatValue) Copy() Value { return &FloatValue{ v.Number, } @@ -334,7 +338,7 @@ func (v *IntegerValue) Get(_ string) (Value, error) { return nil, errors.New("numbers have no properties") } -func (v *IntegerValue) Clone() Value { +func (v *IntegerValue) Copy() Value { return &IntegerValue{ new(big.Int).Set(v.Number), } @@ -383,7 +387,7 @@ var StringPrototype = map[string]*BuiltinFunctionValue{ } if prev != len(str) { - out = append(out, &StringValue{str[prev:len(str)]}) + out = append(out, &StringValue{str[prev:]}) } return &ListValue{out}, nil @@ -427,7 +431,7 @@ func (v *StringValue) Get(key string) (Value, error) { return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key)) } -func (v *StringValue) Clone() Value { +func (v *StringValue) Copy() Value { return &StringValue{ v.Text, } @@ -594,15 +598,9 @@ func (v *ListValue) Get(key string) (Value, error) { return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key)) } -func (v *ListValue) Clone() Value { - n := make([]Value, len(v.Items)) - - for i, item := range v.Items { - n[i] = item.Clone() - } - +func (v *ListValue) Copy() Value { return &ListValue{ - n, + v.Items, } } @@ -635,13 +633,9 @@ 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() - } +func (v *TupleValue) Copy() Value { return &TupleValue{ - n, + v.Items, } } @@ -661,7 +655,7 @@ func (v *TupleValue) Equals(other Value) bool { } var TuplePrototype = map[string]*BuiltinFunctionValue{ - "at": &BuiltinFunctionValue{ + "at": { "at", &FunctionSignature{ []TypeSignature{&IntegerSignature{}}, @@ -718,7 +712,7 @@ func (v *FunctionValue) Get(_ string) (Value, error) { return nil, errors.New("functions have no properties") } -func (v *FunctionValue) Clone() Value { +func (v *FunctionValue) Copy() Value { return &FunctionValue{ v.Name, v.Params, @@ -758,7 +752,7 @@ func (v *BuiltinFunctionValue) Get(_ string) (Value, error) { return nil, errors.New("functions have no properties") } -func (v *BuiltinFunctionValue) Clone() Value { +func (v *BuiltinFunctionValue) Copy() Value { return &BuiltinFunctionValue{ v.Name, v.Signature, @@ -767,3 +761,82 @@ func (v *BuiltinFunctionValue) Clone() 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 17a1a62..f07890f 100644 --- a/core/vm.go +++ b/core/vm.go @@ -126,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. @@ -139,6 +139,12 @@ 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 @@ -146,10 +152,14 @@ 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 the character - // at the position + // is the index. [..., container, index] -> [..., item]. Produces a new string with only the character + // at the indexed position InstructionIndexString + // InstructionSetIndexList set the item at a given index in a list. + // [..., item, container, index] -> [..., item] + InstructionSetIndexList + // InstructionBreakpoint for debugging purposes InstructionBreakpoint ) @@ -262,6 +272,14 @@ 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" } @@ -624,7 +642,7 @@ var DefaultGlobals = map[string]Value{ n, _ := v.Number.Float64() return &FloatValue{n}, nil case *FloatValue: - return v.Clone(), nil + return v.Copy(), nil case *StringValue: num, err := strconv.ParseFloat(v.Text, FloatSize) if err != nil { @@ -964,7 +982,7 @@ func (vm *VM) Next() bool { vm.Stack.Push(v) case InstructionSetLocal: - value := vm.Stack.Peek().Clone() + value := vm.Stack.Peek().Copy() name := vm.GetConstant(vm.NextByte()).(*StringValue).Text vm.setVar(name, value) @@ -972,7 +990,7 @@ func (vm *VM) Next() bool { case InstructionDeclareLocal: vm.addVar( vm.GetConstant(vm.NextByte()).(*StringValue).Text, - vm.Stack.Peek().Clone(), + vm.Stack.Peek().Copy(), ) case InstructionGetGlobal: @@ -1056,7 +1074,7 @@ func (vm *VM) Next() bool { vm.Stack.Push(r, l) case InstructionDuplicate: - vm.Stack.Push(vm.Stack.Peek().Clone()) + vm.Stack.Push(vm.Stack.Peek().Copy()) case InstructionAccessProperty: source := vm.Stack.Pop() @@ -1076,6 +1094,18 @@ 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) @@ -1086,7 +1116,7 @@ func (vm *VM) Next() bool { vm.error(fmt.Sprintf("index %d out of bounds", n)) } - vm.Stack.Push(l.Items[n].Clone()) + vm.Stack.Push(l.Items[n].Copy()) case InstructionIndexTuple: i := vm.Stack.Pop().(*IntegerValue) @@ -1098,7 +1128,7 @@ func (vm *VM) Next() bool { vm.error(fmt.Sprintf("index %d out of bounds", n)) } - vm.Stack.Push(t.Items[n].Clone()) + vm.Stack.Push(t.Items[n].Copy()) case InstructionIndexString: i := vm.Stack.Pop().(*IntegerValue) @@ -1112,6 +1142,18 @@ 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 @@ -1258,7 +1300,7 @@ func (vm *VM) HasNext() bool { } func (vm *VM) GetConstant(id Bytecode) Value { - return vm.chunk.Constants[id].Clone() + return vm.chunk.Constants[id].Copy() } func (vm *VM) ReadConstant() Value { diff --git a/examples/fib.ang b/examples/fib.ang index 63a432a..4860a1e 100644 --- a/examples/fib.ang +++ b/examples/fib.ang @@ -3,14 +3,9 @@ fn range(from: int, to: int) -> (fn() -> (int, bool)) { i := from - 1 - end := to - 1 fn() -> (int, bool) { - if i < end { - (i = i+1, true) - } else { - (-1, false) - } + (i = i+1, i+1 < to) } } diff --git a/examples/list.ang b/examples/list.ang index 9866d3e..d34887e 100644 --- a/examples/list.ang +++ b/examples/list.ang @@ -1,6 +1,6 @@ # Empty list -println([]) +println([]any) # 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 := [] +a := []int -a = a + [1] -a = a + [2] +a.push(1) +a.push(2) println(a) -list := [] +list := []int x := 0 for n in 0..100 { x = x + 2*n + 1 - list = list + [x] + list.push(x) } println(list) -println(list.map(func(a) { +println(list.map(fn(a: int) -> int { return a - 1 })) println(list.length()) diff --git a/examples/recursive.ang b/examples/recursive.ang index caa0e37..f30109f 100644 --- a/examples/recursive.ang +++ b/examples/recursive.ang @@ -1,14 +1,15 @@ # This program computes the fibonacci numbers using recursion (O(2^n)) # It is very slow -func fib(x: number) number { +fn fib(x: number) number { if x <= 1 { - return x + x + } else { + fib(x - 1) + fib(x - 2) } - return fib(x - 1) + fib(x - 2) } n := 0 while n < 100 { - write(str(fib(n))) + println(fib(n)) n = n + 1 } diff --git a/foo.ang b/foo.ang new file mode 100644 index 0000000..e69de29 diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..2d35219 --- /dev/null +++ b/go.work.sum @@ -0,0 +1 @@ +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= diff --git a/lib/list.ang b/lib/list.ang index 254912d..792751f 100644 --- a/lib/list.ang +++ b/lib/list.ang @@ -1,5 +1,5 @@ -fn ([T]) map(f: fn(T) -> R) -> [R] { +fn map(list: [T], f: fn(T) -> R) -> [R] { out := [] for v in list.iter() { diff --git a/lib/math.ang b/lib/math.ang index 120e60e..9dac01a 100644 --- a/lib/math.ang +++ b/lib/math.ang @@ -249,3 +249,5 @@ 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 59aed2c..82e7b77 100644 --- a/lib/testing.ang +++ b/lib/testing.ang @@ -1,25 +1,25 @@ NAMESPACE := "" -func namespace(name: string, test: func()) { +fn namespace(name: str, test: fn()) { NAMESPACE = name test() } -func assertEqual(a: any, b: any) { +fn eq(a: T, b: T) { if a != b { - write(format("assertion error: % should (but doesn't) equal %", [a, b])) + println(format("assertion error: % should (but doesn't) equal %", [a, b])) exit(1) } else if env("DEBUG") != "" { - write(format("assertion success: % equals %", [a, b])) + println(format("assertion success: % equals %", [a, b])) } } -func assertNotEqual(a: any, b: any) { +fn neq(a: T, b: T) { if a == b { - write(format("assertion error: % shouldn't (but does) equal %", [a, b])) + println(format("assertion error: % shouldn't (but does) equal %", [a, b])) exit(1) } else if env("DEBUG") != "" { - write(format("assertion success: % doesn't equal %", [a, b])) + println(format("assertion success: % doesn't equal %", [a, b])) } } diff --git a/records.ang b/records.ang new file mode 100644 index 0000000..198475c --- /dev/null +++ b/records.ang @@ -0,0 +1,29 @@ + +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 9905f24..a6bdd01 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/list.ang b/tests/list.ang index 8554f14..e98fcf8 100644 --- a/tests/list.ang +++ b/tests/list.ang @@ -34,3 +34,10 @@ 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 81aa508..65e99e9 100644 --- a/tests/tuple.ang +++ b/tests/tuple.ang @@ -9,3 +9,10 @@ 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)