Skip to content

Commit

Permalink
feat: enum pattern matching
Browse files Browse the repository at this point in the history
  • Loading branch information
pmqueiroz committed Dec 1, 2024
1 parent 5e61c16 commit 5cdea0a
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 5 deletions.
50 changes: 50 additions & 0 deletions ast/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,53 @@ func (p *Parser) enumStatement() Statement {
}
}

func (p *Parser) matchStatement() Statement {
expr := p.expression()

p.consume("Expect '{' before match body.", tokens.LEFT_BRACE)

var cases []MatchCase

for !p.check(tokens.RIGHT_BRACE) && !p.isAtEOF() {
caseExpr := p.expression()

var params []MatchCaseParameter

if p.match(tokens.PIPE) && !p.check(tokens.LEFT_BRACE) {
for {
paramName := p.consume("Expect parameter name.", tokens.IDENTIFIER)

params = append(params, MatchCaseParameter{
Name: paramName,
})

if !p.match(tokens.COMMA) {
break
}
}

p.consume("Expect '|' after case parameters", tokens.PIPE)
}

p.consume("Expect '{' before case body", tokens.LEFT_BRACE)

_, body := p.block()

cases = append(cases, MatchCase{
Expression: caseExpr,
Parameters: params,
Body: body,
})
}

p.consume("Expect '}' after match body", tokens.RIGHT_BRACE)

return MatchStatement{
Expression: expr,
Cases: cases,
}
}

func (p *Parser) printStatement(channel PrintChannel) Statement {
value := p.expression()
return PrintStatement{
Expand Down Expand Up @@ -712,6 +759,9 @@ func (p *Parser) statement() Statement {
if p.match(tokens.ENUM) {
return p.enumStatement()
}
if p.match(tokens.MATCH) {
return p.matchStatement()
}
if p.match(tokens.LEFT_BRACE) {
blockStatement, _ := p.block()
return blockStatement
Expand Down
15 changes: 15 additions & 0 deletions ast/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ type FunctionStatement struct {
Body []Statement
}

type MatchCaseParameter struct {
Name tokens.Token
}

type MatchCase struct {
Expression Expression
Parameters []MatchCaseParameter
Body []Statement
}

type MatchStatement struct {
Expression Expression
Cases []MatchCase
}

type IfStatement struct {
Condition Expression
ThenBranch Statement
Expand Down
25 changes: 22 additions & 3 deletions examples/enums.u
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,33 @@ import "io"

enum Event {
Keyboard(str)
Click(num, num)
Close
}

const event Event = Event.Keyboard("k")
const events arr = [
Event.Click(123, 321),
Event.Keyboard("k"),
Event.Close
]

for mut i num = 0, ~events - 1 {
match events[i] {
Event.Keyboard |key| {
io::printLn("keyboard event with key:", key)
}
Event.Click |x, y| {
io::printLn("click event at x:", x, "y:", y)
}
Event.Close {
io::printLn("close event")
}
}
}

io::printLn(event)

## Todo
# - [x] Declare enums
# - [x] Enum with arguments
# - [ ] Enum pattern matching
# - [x] Enum pattern matching
# - [ ] Support for generic enums
1 change: 0 additions & 1 deletion interpreter/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ func Evaluate(expression ast.Expression, env *environment.Environment) (interfac
result, err := parsedCallee(args)
return result, err
case ast.EnumMember:
litter.Dump(parsedCallee)
enrichedArgs := make([]ast.EnumArgument, len(parsedCallee.Arguments))
for i, arg := range parsedCallee.Arguments {
argValue, err := Evaluate(expr.Arguments[i], env)
Expand Down
39 changes: 38 additions & 1 deletion interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"os"
"reflect"
"strings"

"github.com/pmqueiroz/umbra/ast"
Expand Down Expand Up @@ -298,8 +299,44 @@ func Interpret(statement ast.Statement, env *environment.Environment) error {
false,
false,
)
return nil
case ast.MatchStatement:
value, err := Evaluate(stmt.Expression, env)
if err != nil {
return err
}

for _, matchCase := range stmt.Cases {
expr, err := Evaluate(matchCase.Expression, env)

if err != nil {
return err
}

if checkMatch(expr, value) {
caseEnv := environment.NewEnvironment(env)

for i, param := range matchCase.Parameters {
arg := value.(ast.EnumMember).Arguments[i]

caseEnv.Create(param.Name.Lexeme, arg.Value, types.ANY, false, false)
}

for _, stmt := range matchCase.Body {
err := Interpret(stmt, caseEnv)

if err != nil {
return err
}
}

return nil
}
}

return nil
default:
return exception.NewRuntimeError("RT000", litter.Sdump(statement))
litter.Dump("statement", statement)
return exception.NewRuntimeError("RT000", reflect.TypeOf(statement).Name())
}
}
17 changes: 17 additions & 0 deletions interpreter/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package interpreter

import "github.com/pmqueiroz/umbra/ast"

func checkMatch(pattern interface{}, expr interface{}) bool {
switch p := pattern.(type) {
case ast.EnumMember:
if e, ok := expr.(ast.EnumMember); ok {
// deep compare
return p.Name == e.Name && p.Signature == e.Signature
}

return false
default:
return false
}
}
2 changes: 2 additions & 0 deletions tokens/tokenizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ func (t *Tokenizer) scan() error {
t.addNonLiteralToken(TILDE)
case '?':
t.addNonLiteralToken(HOOK)
case '|':
t.addNonLiteralToken(PIPE)
case '.':
if t.match('.') {
if t.match('.') {
Expand Down
1 change: 1 addition & 0 deletions tokens/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const (
TYPE_OF TokenType = "TYPE_OF"
ENUM TokenType = "ENUM"
MATCH TokenType = "MATCH"
PIPE TokenType = "PIPE"
)

var PRIMITIVE_TYPES = []TokenType{
Expand Down

0 comments on commit 5cdea0a

Please sign in to comment.