diff --git a/ast/struct.go b/ast/struct.go index 744f871..fef55e5 100644 --- a/ast/struct.go +++ b/ast/struct.go @@ -101,3 +101,23 @@ func (s *StructFieldAccess) TokenLiteral() string { func (s *StructFieldAccess) String() string { return s.Left.String() + "." + s.Field.String() } + +type StructFieldAssignment struct { + Token token.Token + Left *StructFieldAccess + Right Expression +} + +func (sfa *StructFieldAssignment) expressionNode() {} + +func (sfa *StructFieldAssignment) TokenLiteral() string { + return sfa.Token.Literal +} + +func (sfa *StructFieldAssignment) String() string { + var out bytes.Buffer + out.WriteString(sfa.Left.String()) + out.WriteString(" = ") + out.WriteString(sfa.Right.String()) + return out.String() +} diff --git a/emitters/js/js.go b/emitters/js/js.go index 4e285f3..3d217cb 100644 --- a/emitters/js/js.go +++ b/emitters/js/js.go @@ -170,6 +170,9 @@ func (t *Transpiler) transpileExpression(expr ast.Expression) string { case *ast.StructFieldAccess: return t.transpileStructFieldAccess(expr) + case *ast.StructFieldAssignment: + return t.transpileStructFieldAssignment(expr) + case *ast.ListLiteral: return t.transpileListLiteral(expr) @@ -352,6 +355,14 @@ func (t *Transpiler) transpileStructFieldAccess(expr *ast.StructFieldAccess) str ) } +func (t *Transpiler) transpileStructFieldAssignment(expr *ast.StructFieldAssignment) string { + return fmt.Sprintf("%s.%s = %s", + t.transpileExpression(expr.Left.Left), + expr.Left.Field.String(), + t.transpileExpression(expr.Right), + ) +} + func (t *Transpiler) transpileVariableDeclaration(stmt *ast.VariableDeclaration) string { if stmt.Type.Type == token.IDENTIFIER && t.definedStructs[stmt.Type.Literal] { return fmt.Sprintf("const %s = new %s(%s);", diff --git a/examples/if.pun b/examples/if.pun index 600d2e6..a1d7e27 100644 --- a/examples/if.pun +++ b/examples/if.pun @@ -3,9 +3,14 @@ pkg main pub fn check_number(i32 x) { if x > 0 { println(x, "is positive") + } else { + println(x, "is negative") } } -check_number(5) +fn main() { + check_number(5) + check_number(-3) +} -check_number(-3) +main() diff --git a/examples/struct.pun b/examples/struct.pun index eb9c9b2..cdbad05 100644 --- a/examples/struct.pun +++ b/examples/struct.pun @@ -56,10 +56,20 @@ message get_message() { }, }, } + msg.sender = msg.sender + 8; + send_message() + println(msg.sender) return msg } -//message msg = get_message() -//println(msg) -send_message() + +fn init() { + //message msg = get_message() + //println(msg) + get_message(); + send_message(); +} + +init(); + diff --git a/lexer/lexer.go b/lexer/lexer.go index 3460c3d..e56b031 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -303,12 +303,16 @@ func (l *Lexer) evaluateKeyword(literal string) token.Type { return token.RETURN case token.Keywords[token.IF]: return token.IF + case token.Keywords[token.ELSE]: + return token.ELSE case token.Keywords[token.TRUE]: return token.TRUE case token.Keywords[token.FOR]: return token.FOR case token.Keywords[token.FALSE]: return token.FALSE + case token.Keywords[token.BOOL]: + return token.BOOL case token.Keywords[token.PUB]: return token.PUB default: diff --git a/parser/function.go b/parser/function.go index d32a543..582fbc2 100644 --- a/parser/function.go +++ b/parser/function.go @@ -165,13 +165,13 @@ func (p *Parser) parseFunctionCallArguments() ([]ast.Expression, error) { } p.trace("parseFunctionCallArguments: first arg", firstArg.String(), p.curToken.Literal, p.peekToken.Literal) args = append(args, firstArg) - if !p.curTokenIs(token.COMMA) { - // println("not comma", p.curToken.Literal, p.peekToken.Literal) - if p.curTokenIs(token.RPAREN) { - p.nextToken() - } - return args, nil - } + if !p.curTokenIs(token.COMMA) { + // println("not comma", p.curToken.Literal, p.peekToken.Literal) + if p.curTokenIs(token.RPAREN) { + p.nextToken() + } + return args, nil + } for p.curTokenIs(token.COMMA) { p.trace("parseFunctionCallArguments: consume COMMA", p.curToken.Literal, p.peekToken.Literal) diff --git a/parser/parser.go b/parser/parser.go index 2a3a420..a329d47 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -72,7 +72,7 @@ func (p *Parser) registerParseRules() { token.FALSE: {prefixFn: p.parseBooleanLiteral}, token.BANG: {prefixFn: p.parsePrefixExpression}, token.ASSIGN: {infixFn: p.parseAssignmentExpression}, - token.MINUS: {infixFn: p.parseInfixExpression}, + token.MINUS: {infixFn: p.parseInfixExpression, prefixFn: p.parsePrefixExpression}, token.PLUS: {infixFn: p.parseInfixExpression}, token.ASTERISK: {infixFn: p.parseInfixExpression}, token.MOD: {infixFn: p.parseInfixExpression}, @@ -112,7 +112,6 @@ func (p *Parser) registerParseRules() { func (p *Parser) parseExpression(precedence int) (ast.Expression, error) { var err error p.trace("parseExpression", p.curToken.Literal, p.peekToken.Literal) - if p.isBooleanLiteral() { p.trace("parsing bool", p.curToken.Literal, p.peekToken.Literal) b, err := p.parseBooleanLiteral() @@ -157,7 +156,9 @@ func (p *Parser) parseExpression(precedence int) (ast.Expression, error) { p.nextToken() return p.parseInfixExpression(n) } - p.nextToken() + if p.curTokenIs(token.NUMBER) { + p.nextToken() + } return n, nil } @@ -223,8 +224,7 @@ func (p *Parser) parseExpression(precedence int) (ast.Expression, error) { if ident == nil { return nil, p.error("identifier is nil") } - - if p.peekTokenIs(token.DOT) { + if p.isStructAccess() { return p.parseStructFieldAccess(ident.(*ast.Identifier)) } diff --git a/parser/parsers.go b/parser/parsers.go index ca3343f..896e7b0 100644 --- a/parser/parsers.go +++ b/parser/parsers.go @@ -185,12 +185,20 @@ func (p *Parser) parseExpressionStatement() (*ast.ExpressionStatement, error) { } if stmt.Expression != nil { p.trace("after parsing expression statement", stmt.Expression.String()) + if p.curTokenIs(token.ASSIGN) { + assignment, err := p.parseStructFieldAssignment(stmt.Expression) + if err != nil { + p.error(err.Error()) + } + stmt.Expression = assignment + } if p.curTokenIs(token.RPAREN) { p.nextToken() } if p.curTokenIs(token.SEMICOLON) { p.nextToken() } + return stmt, nil } return stmt, nil @@ -289,6 +297,8 @@ func (p *Parser) parseInfixExpression(left ast.Expression) (ast.Expression, erro } precedence := p.curPrecedence() + + // consume the operator p.nextToken() expression.Right, err = p.parseExpression(precedence) return expression, err @@ -318,7 +328,10 @@ func (p *Parser) parseIfStatement() (*ast.IfStatement, error) { return nil, err } p.trace("parsing if statement", p.curToken.Literal, p.peekToken.Literal) + p.trace("parsing else block") + if p.peekTokenIs(token.ELSE) { + p.nextToken() // consume } p.nextToken() // consume else if !p.expectCurrentTokenIs(token.LBRACE) { @@ -424,6 +437,9 @@ func (p *Parser) parseAssignmentExpression(left ast.Expression) (ast.Expression, p.nextToken() expression.Right, err = p.parseExpression(LOWEST) + if err != nil { + return nil, err + } return expression, err } @@ -449,6 +465,7 @@ func (p *Parser) parseTypeBasedVariableDeclaration() (ast.Statement, error) { return decl, nil } varType := p.curToken + // if not an identifier, it could be a struct member if !p.expectPeek(token.IDENTIFIER) { return nil, p.error("expected identifier") } diff --git a/parser/predicate.go b/parser/predicate.go index b562882..320bbc8 100644 --- a/parser/predicate.go +++ b/parser/predicate.go @@ -108,5 +108,9 @@ func (p *Parser) isNumber() bool { } func (p *Parser) isIndexExpression() bool { - return p.curTokenIs(token.IDENTIFIER) && p.peekTokenIs(token.LBRACKET) + return p.curTokenIs(token.IDENTIFIER) && p.peekTokenIs(token.LBRACKET) +} + +func (p *Parser) isStructAccess() bool { + return p.curTokenIs(token.IDENTIFIER) && p.peekTokenIs(token.DOT) && p.peekTokenAfter(token.IDENTIFIER) } diff --git a/parser/struct.go b/parser/struct.go index c593cae..ef3404d 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -137,6 +137,9 @@ func (p *Parser) parseStructLiteral() (ast.Expression, error) { } func (p *Parser) parseStructFieldAccess(left ast.Expression) (ast.Expression, error) { + if !p.expectCurrentTokenIs(token.DOT) { + p.error("expected a dot, but got: ", p.curToken.Literal) + } p.nextToken() // consume the dot if !p.expectPeek(token.IDENTIFIER) { @@ -154,6 +157,34 @@ func (p *Parser) parseStructFieldAccess(left ast.Expression) (ast.Expression, er if p.peekTokenIs(token.DOT) { return p.parseStructFieldAccess(fieldAccess) } + if p.isBinaryOperator() { + p.nextToken() + return p.parseInfixExpression(fieldAccess) + } return fieldAccess, nil } + +func (p *Parser) parseStructFieldAssignment(left ast.Expression) (ast.Expression, error) { + tok := p.curToken + if !p.curTokenIs(token.ASSIGN) { + p.error("we were expecting an assignment operator, but got: ", p.curToken.Literal) + } + p.nextToken() + + right, err := p.parseExpression(LOWEST) + if err != nil { + return nil, p.error("expected expression after assignment operator") + } + + fieldAccess, ok := left.(*ast.StructFieldAccess) + if !ok { + return nil, p.error("left-hand side of assignment must be a struct field access") + } + + return &ast.StructFieldAssignment{ + Token: tok, + Left: fieldAccess, + Right: right, + }, nil +} diff --git a/token/token.go b/token/token.go index a63f361..868a395 100644 --- a/token/token.go +++ b/token/token.go @@ -25,9 +25,9 @@ const ( // Literals STRING = "STRING" - NUMBER = "number" - FLOAT = "float" - BOOL = "bool" + NUMBER = "NUMBER" + FLOAT = "FLOAT" + BOOL = "BOOL" // Operators ASSIGN = "=" @@ -76,18 +76,18 @@ const ( LET = "LET" RETURN = "RETURN" FOR = "FOR" - IF = "if" - ELSE = "else" - PUB = "pub" - PACKAGE = "pkg" - IMPORT = "import" - INTERFACE = "interface" - STRUCT = "struct" - TEST = "test" - ENUM = "enum" - DEFER = "defer" - APPEND = "append" - LEN = "len" + IF = "IF" + ELSE = "ELSE" + PUB = "PUB" + PACKAGE = "PKG" + IMPORT = "IMPORT" + INTERFACE = "INTERFACE" + STRUCT = "STRUCT" + TEST = "TEST" + ENUM = "ENUM" + DEFER = "DEFER" + APPEND = "APPEND" + LEN = "LEN" IDENTIFIER = "IDENTIFIER" @@ -127,6 +127,7 @@ var Keywords = map[Type]string{ TRUE: "true", FALSE: "false", PUB: "pub", + BOOL: "bool", STRING: "str", APPEND: "append", LEN: "len",