Skip to content

Commit

Permalink
support switch-case #19
Browse files Browse the repository at this point in the history
  • Loading branch information
douyixuan committed Aug 16, 2024
1 parent 3d89ea6 commit e68bf5e
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 22 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ test/example:
${CELL} -d -t riscv tests/examples/make-slice.cell && ckb-debugger --bin make-slice | grep "0422"
${CELL} -d -t riscv tests/examples/panic.cell && ckb-debugger --bin panic | grep "runtime panic: hah"
${CELL} -d -t riscv tests/examples/if-cond.cell && ckb-debugger --bin if-cond | grep "100:0:ss"
${CELL} -d -t riscv tests/examples/switch.cell && ckb-debugger --bin switch | grep "five"
${CELL} -d -t riscv tests/examples/return.cell && ckb-debugger --bin return
${CELL} -d -t riscv tests/examples/named-ret-type.cell && ckb-debugger --bin named-ret-type | grep "0"
${CELL} -d -t riscv tests/examples/func.cell && ckb-debugger --bin func | grep "999"
Expand Down
2 changes: 2 additions & 0 deletions compiler/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ func (c *Compiler) compile(instructions []parser.Node) {

// Add to tre mapping
c.currentPackage.DefinePkgType(v.Name, t)
case *parser.SwitchNode:
c.compileSwitchNode(v)

default:
c.compileValue(v)
Expand Down
60 changes: 60 additions & 0 deletions compiler/compiler/switch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package compiler

import (
"github.com/llir/llvm/ir"
"github.com/llir/llvm/ir/constant"

"github.com/cell-labs/cell-script/compiler/compiler/internal"
"github.com/cell-labs/cell-script/compiler/compiler/name"
"github.com/cell-labs/cell-script/compiler/parser"
)

func (c *Compiler) compileSwitchNode(v *parser.SwitchNode) {
switchItem := c.compileValue(v.Item)

var cases []*ir.Case
caseBlocks := make([]*ir.Block, len(v.Cases))

afterSwitch := c.contextBlock.Parent.NewBlock(name.Block() + "after-switch")

// build default case
defaultCase := c.contextBlock.Parent.NewBlock(name.Block() + "switch-default")
if v.DefaultBody != nil {
preDefaultBlock := c.contextBlock
c.contextBlock = defaultCase
c.compile(v.DefaultBody)
c.contextBlock = preDefaultBlock
}
defaultCase.NewBr(afterSwitch)

// Parse all cases
for caseIndex, parseCase := range v.Cases {
preCaseBlock := c.contextBlock
caseBlock := c.contextBlock.Parent.NewBlock(name.Block() + "case")
c.contextBlock = caseBlock
c.compile(parseCase.Body)
c.contextBlock = preCaseBlock

caseBlocks[caseIndex] = caseBlock

for _, cond := range parseCase.Conditions {
item := c.compileValue(cond)
cases = append(cases, ir.NewCase(item.Value.(constant.Constant), caseBlock))
}
}

for caseIndex, parseCase := range v.Cases {
if parseCase.Fallthrough {
// Jump to the next case body
caseBlocks[caseIndex].Term = ir.NewBr(caseBlocks[caseIndex+1])
} else {
// Jump to after switch
caseBlocks[caseIndex].Term = ir.NewBr(afterSwitch)
}
}

val := internal.LoadIfVariable(c.contextBlock, switchItem)
c.contextBlock.Term = c.contextBlock.NewSwitch(val, defaultCase, cases...)

c.contextBlock = afterSwitch
}
47 changes: 26 additions & 21 deletions compiler/lexer/keywords.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
package lexer

var keywords = map[string]struct{}{
"if": {},
"else": {},
"func": {},
"extern": {},
"return": {},
"type": {},
"table": {},
"var": {},
"const": {},
"package": {},
"pragma": {},
"for": {},
"break": {},
"continue": {},
"import": {},
"true": {},
"false": {},
"interface": {},
"range": {},
"make": {},
"panic": {},
"if": {},
"else": {},
"switch": {},
"case": {},
"default": {},
"fallthrough": {},
"func": {},
"extern": {},
"return": {},
"type": {},
"table": {},
"struct": {},
"var": {},
"const": {},
"package": {},
"pragma": {},
"for": {},
"break": {},
"continue": {},
"import": {},
"true": {},
"false": {},
"interface": {},
"range": {},
"make": {},
"panic": {},
}
6 changes: 5 additions & 1 deletion compiler/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,10 @@ func (p *parser) parseOneWithOptions(withAheadParse, withArithAhead, withIdentif
if current.Val == "range" {
return p.parseRange()
}

if current.Val == "switch" {
return p.parseSwitch()
}
}

p.printInput()
Expand Down Expand Up @@ -1160,7 +1164,7 @@ func (p *parser) parseOneType() (TypeNode, error) {
}

// struct parsing
if current.Type == lexer.KEYWORD && current.Val == "table" {
if current.Type == lexer.KEYWORD && (current.Val == "table" || current.Val == "struct") {
p.i++

res := &StructTypeNode{
Expand Down
123 changes: 123 additions & 0 deletions compiler/parser/switch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package parser

import (
"fmt"

"github.com/cell-labs/cell-script/compiler/lexer"
)

type SwitchNode struct {
baseNode
Item Node
Cases []*SwitchCaseNode
DefaultBody []Node // can be null
}

type SwitchCaseNode struct {
baseNode
Conditions []Node
Body []Node
Fallthrough bool
}

func (s SwitchNode) String() string {
return fmt.Sprintf("switch %s", s.Item)
}

func (s SwitchCaseNode) String() string {
return fmt.Sprintf("case %+v", s.Conditions)
}

func (p *parser) parseSwitch() *SwitchNode {
p.i++

s := &SwitchNode{
Item: p.parseOne(true),
Cases: make([]*SwitchCaseNode, 0),
}

p.i++
p.expect(p.lookAhead(0), lexer.Item{Type: lexer.OPERATOR, Val: "{"})
p.i++

for {
next := p.lookAhead(0)

if next.Type == lexer.EOL {
p.i++
continue
}

if next.Type == lexer.KEYWORD && next.Val == "case" {
p.i++
switchCase := SwitchCaseNode{
Conditions: []Node{p.parseOne(true)},
}

p.i++

for {
curr := p.lookAhead(0)
if curr.Type == lexer.OPERATOR && curr.Val == ":" {
p.i++
break
}

if curr.Type == lexer.OPERATOR && curr.Val == "," {
p.i++
switchCase.Conditions = append(append(switchCase.Conditions,
p.parseOne(true),
))
p.i++
continue
}

panic(fmt.Sprintf("Expected : or , in case. Got %+v", curr))
}

var reached lexer.Item
switchCase.Body, reached = p.parseUntilEither(
[]lexer.Item{
{Type: lexer.OPERATOR, Val: "}"},
{Type: lexer.KEYWORD, Val: "case"},
{Type: lexer.KEYWORD, Val: "default"},
{Type: lexer.KEYWORD, Val: "fallthrough"},
},
)

if reached.Type == lexer.KEYWORD && reached.Val == "fallthrough" {
switchCase.Fallthrough = true
p.i++
}

s.Cases = append(s.Cases, &switchCase)

// reached end of switch
if reached.Type == lexer.OPERATOR && reached.Val == "}" {
break
}
}

if next.Type == lexer.KEYWORD && next.Val == "default" {
p.i++
p.expect(p.lookAhead(0), lexer.Item{Type: lexer.OPERATOR, Val: ":"})
p.i++

body, reached := p.parseUntilEither(
[]lexer.Item{
{Type: lexer.OPERATOR, Val: "}"},
{Type: lexer.KEYWORD, Val: "case"},
},
)

s.DefaultBody = body

// reached end of switch
if reached.Type == lexer.OPERATOR && reached.Val == "}" {
break
}
}
}

return s
}
15 changes: 15 additions & 0 deletions compiler/parser/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ func Walk(v Visitor, node Node) (r Node) {
}
case *RangeNode:
n.Item = Walk(v, n.Item)
case *SwitchNode:
n.Item = Walk(v, n.Item)
for i, a := range n.Cases {
n.Cases[i] = Walk(v, a).(*SwitchCaseNode)
}
for i, a := range n.DefaultBody {
n.DefaultBody[i] = Walk(v, a)
}
case *SwitchCaseNode:
for i, a := range n.Conditions {
n.Conditions[i] = Walk(v, a)
}
for i, a := range n.Body {
n.Body[i] = Walk(v, a)
}
case *AssignNode:
for i, a := range n.Target {
n.Target[i] = Walk(v, a)
Expand Down
50 changes: 50 additions & 0 deletions tests/examples/switch.cell
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

package main

import (
"debug"
)

func runIntSwitch(a uint64) {
switch a {
case 3:
debug.Printf("three")
case 6, 7, 8:
debug.Printf("six, seven, eight")
case 4:
debug.Printf("four")
fallthrough
case 5:
debug.Printf("five")
default:
debug.Printf("default")
}
}

func runBoolSwitch(a bool) {
switch a {
case false:
debug.Printf("false")
case true:
debug.Printf("true")
}
}

func main() {
a := 3
runIntSwitch(a) // three

runIntSwitch(100) // default

// four
// five
runIntSwitch(4)

runIntSwitch(6) // six, seven, eight
runIntSwitch(7) // six, seven, eight
runIntSwitch(8) // six, seven, eight

runBoolSwitch(false) // false
runBoolSwitch(true) // true
return 0
}

0 comments on commit e68bf5e

Please sign in to comment.