diff options
-rw-r--r-- | GRAMMAR.txt | 8 | ||||
-rw-r--r-- | TYPE_SYSTEM.md | 24 | ||||
-rw-r--r-- | decl.go | 25 | ||||
-rw-r--r-- | expression.go | 33 | ||||
-rw-r--r-- | generate.go | 38 | ||||
-rw-r--r-- | generate_error.go | 16 | ||||
-rw-r--r-- | lex.go | 4 | ||||
-rw-r--r-- | parse.go | 107 | ||||
-rw-r--r-- | token.go | 6 |
9 files changed, 255 insertions, 6 deletions
diff --git a/GRAMMAR.txt b/GRAMMAR.txt index 9e30c24..4882ff8 100644 --- a/GRAMMAR.txt +++ b/GRAMMAR.txt @@ -10,9 +10,15 @@ param -> IDENTIFIER IDENTIFIER ( "," param )? block -> "{" ( statement )* "}" -statement -> ( return ) ";" +statement -> ( return | declaration | assignment ) ";" return -> "return" expression +declaration -> constant | mutable +constant -> "const" IDENTIFIER "=" expression +mutable -> "mut" IDENTIFIER "=" expression + +assignment -> "assign" IDENTIFIER "=" expression + expression -> equality equality -> comparison ( ( "==" | "!=" ) comparison )* comparison -> term ( ( "<" | "<=" | ">" | ">=" ) term )? diff --git a/TYPE_SYSTEM.md b/TYPE_SYSTEM.md new file mode 100644 index 0000000..56baa91 --- /dev/null +++ b/TYPE_SYSTEM.md @@ -0,0 +1,24 @@ +<!-- +SPDX-FileCopyrightText: 2024 Himbeer <himbeer@disroot.org> + +SPDX-License-Identifier: CC-BY-NC-SA-4.0 +--> + +# Primitive types + +* int8, uint8 -> QBE b, w +* int16, uint16 -> QBE h, w +* int32, uint32 -> QBE w, w +* int64, uint64 -> QBE l, l + +# Coercion + +* Integer widening + +# Declaration and mutability + +* Variables and constants need to be declared before being used +* All declarations (including globals and implicit declarations from imports) must be above their first use +* Redeclarations ("shadowing") are forbidden +* Variables may be changed using the "assign" statement +* Constants cannot be changed @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 Himbeer <himbeer@disroot.org> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package main + +import ( + "fmt" +) + +type mutInfo struct { + id string + nameIndex int +} + +func (m mutInfo) name() string { + return fmt.Sprintf("%%%s%d", m.id, m.nameIndex-1) +} + +func (m *mutInfo) nextName() string { + defer func() { m.nameIndex++ }() + return fmt.Sprintf("%%%s%d", m.id, m.nameIndex) +} + +var locals = map[string]map[string]mutInfo{} diff --git a/expression.go b/expression.go index c62918c..7693f79 100644 --- a/expression.go +++ b/expression.go @@ -101,6 +101,39 @@ func (r *returnStmt) markStmt() {} func (r *returnStmt) line() int { return r.ln } +type declStmt interface { + statementExpr + markDecl() +} + +type constStmt struct { + name string + initial exprExpr + ln int +} + +func (c *constStmt) markExpr() {} + +func (c *constStmt) markStmt() {} + +func (c *constStmt) markDecl() {} + +func (c *constStmt) line() int { return c.ln } + +type mutStmt struct { + name string + initial exprExpr + ln int +} + +func (m *mutStmt) markExpr() {} + +func (m *mutStmt) markStmt() {} + +func (m *mutStmt) markDecl() {} + +func (m *mutStmt) line() int { return m.ln } + type exprExpr interface { expression markExprExpr() diff --git a/generate.go b/generate.go index 94f577d..432bd25 100644 --- a/generate.go +++ b/generate.go @@ -11,6 +11,8 @@ import ( "strings" ) +var currentFunc string + var funcRegIndex int func allocReg() string { @@ -78,6 +80,8 @@ func generateToplevel(toplevel expression, w io.Writer) error { func generateFunction(function *functionExpr, w io.Writer) error { resetRegs() + currentFunc = function.name + locals[function.name] = map[string]mutInfo{} if function.link != defaultLinkage { fmt.Fprintf(w, "%s ", function.link) @@ -108,6 +112,10 @@ func generateStatement(stmt statementExpr, w io.Writer) error { switch s := stmt.(type) { case *returnStmt: return generateReturnStmt(s, w) + case *constStmt: + return generateConstStmt(s, false, w) + case *mutStmt: + return generateMutStmt(s, false, w) } return nil @@ -124,6 +132,36 @@ func generateReturnStmt(stmt *returnStmt, w io.Writer) error { return nil } +func generateConstStmt(stmt *constStmt, toplevel bool, w io.Writer) error { + value, err := stmt.initial.generate(w) + if err != nil { + return err + } + + fmt.Fprintf(w, "%%%s =w add %s, 0\n", stmt.name, value) + return nil +} + +func generateMutStmt(stmt *mutStmt, toplevel bool, w io.Writer) error { + value, err := stmt.initial.generate(w) + if err != nil { + return err + } + + if _, ok := locals[currentFunc][stmt.name]; ok { + return errAlreadyDeclared{name: stmt.name, line: stmt.line()} + } + + mutable := mutInfo{ + id: stmt.name, + } + locals[currentFunc][stmt.name] = mutable + + fmt.Fprintf(w, "%s =l alloc4 8\n", mutable.nextName()) + fmt.Fprintf(w, "storew %s, %s\n", value, mutable.name()) + return nil +} + func (e *equalityExpr) generate(w io.Writer) (string, error) { lhs, err := e.lhs.generate(w) if err != nil { diff --git a/generate_error.go b/generate_error.go new file mode 100644 index 0000000..7884151 --- /dev/null +++ b/generate_error.go @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Himbeer <himbeer@disroot.org> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package main + +import "fmt" + +type errAlreadyDeclared struct { + name string + line int +} + +func (e errAlreadyDeclared) Error() string { + return fmt.Sprintf("%d: redeclaration of %q\n", e.line, e.name) +} @@ -296,6 +296,10 @@ func addKeywordOrIdentifier(name string, line int) token { return token{kind: function, line: line} case "return": return token{kind: ret, line: line} + case "const": + return token{kind: constKeyword, line: line} + case "mut": + return token{kind: mut, line: line} default: return token{kind: identifier, value: name, line: line} } @@ -236,7 +236,6 @@ func parseBlock(toks *tokens) (*blockExpr, error) { return nil, err } if stmt == nil { - expectStmt = true break } @@ -255,18 +254,38 @@ func parseBlock(toks *tokens) (*blockExpr, error) { } func parseStatement(toks *tokens) (statementExpr, error) { + stmt, err := parseStatementContents(toks) + if err != nil { + return nil, err + } + if stmt == nil { + return nil, nil + } + + if err := toks.mustMatch(semicolon); err != nil { + return nil, err + } + + return stmt, nil +} + +func parseStatementContents(toks *tokens) (statementExpr, error) { stmt, err := parseReturnStmt(toks) if err != nil { return nil, err } if stmt != nil { - if err := toks.mustMatch(semicolon); err != nil { - return nil, err - } - return stmt, nil } + decl, err := parseDeclaration(toks) + if err != nil { + return nil, err + } + if decl != nil { + return decl, nil + } + return nil, nil } @@ -288,6 +307,84 @@ func parseReturnStmt(toks *tokens) (*returnStmt, error) { return &returnStmt{value: expr, ln: toks.current().line}, nil } +func parseDeclaration(toks *tokens) (declStmt, error) { + constant, err := parseConstStmt(toks) + if err != nil { + return nil, err + } + if constant != nil { + return constant, nil + } + + mutable, err := parseMutStmt(toks) + if err != nil { + return nil, err + } + if mutable != nil { + return mutable, nil + } + + return nil, nil +} + +func parseConstStmt(toks *tokens) (*constStmt, error) { + ok, err := toks.match(constKeyword) + if err != nil { + return nil, err + } + if !ok { + toks.unreadToken() + return nil, nil + } + + line := toks.current().line + + nameTok, ok := toks.consumeToken() + if !ok { + return nil, unexpectedEOF + } + + if err := toks.mustMatch(equals); err != nil { + return nil, err + } + + expr, err := parseExpression(toks) + if err != nil { + return nil, err + } + + return &constStmt{name: nameTok.value, initial: expr, ln: line}, nil +} + +func parseMutStmt(toks *tokens) (*mutStmt, error) { + ok, err := toks.match(mut) + if err != nil { + return nil, err + } + if !ok { + toks.unreadToken() + return nil, nil + } + + line := toks.current().line + + nameTok, ok := toks.consumeToken() + if !ok { + return nil, unexpectedEOF + } + + if err := toks.mustMatch(equals); err != nil { + return nil, err + } + + expr, err := parseExpression(toks) + if err != nil { + return nil, err + } + + return &mutStmt{name: nameTok.value, initial: expr, ln: line}, nil +} + func parseExpression(toks *tokens) (exprExpr, error) { return parseEquality(toks) } @@ -100,6 +100,10 @@ func (k tokenKind) String() string { return "func" case ret: return "return" + case constKeyword: + return "const" + case mut: + return "mut" } return "invalid token" @@ -153,6 +157,8 @@ const ( export function ret + constKeyword + mut ) type token struct { |