Skip to content

Commit

Permalink
enhancement: Implement tis100 assembly parser with participle (#1)
Browse files Browse the repository at this point in the history
Signed-off-by: Oğuzhan Durgun <oguzhandurgun95@gmail.com>
  • Loading branch information
oguzhand95 committed Aug 28, 2022
1 parent 5f046b3 commit b858476
Show file tree
Hide file tree
Showing 28 changed files with 603 additions and 9 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ jobs:
~/.cache/diofis/bin
key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }}

- name: Generate
run: make generate

- name: Check repo status
run: |-
REPO_STATUS="$(git status --porcelain)"
Expand All @@ -72,7 +69,7 @@ jobs:
exit 1
fi
- name: Test
run: make test-all
run: make test

golangci:
needs: changes
Expand Down
9 changes: 8 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ run:
go: "1.19"
timeout: 300s
build-tags:
- tests
- test

linters-settings:
exhaustive:
default-signifies-exhaustive: true

gci:
sections:
- standard
- default
- prefix(github.com/oguzhand95/tis100)

gofumpt:
extra-rules: true

Expand Down Expand Up @@ -42,6 +48,7 @@ linters:
- exportloopref
- forbidigo
- forcetypeassert
- gci
- goconst
- gocritic
- godot
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ deps:

.PHONY: test
test: $(GOTESTSUM)
@ $(GOTESTSUM) -- -tags=tests $(COVERPROFILE) -cover ./...
@ $(GOTESTSUM) -- -tags=test $(COVERPROFILE) -cover ./...

.PHONY: lint
lint: $(GOLANGCI_LINT)
Expand All @@ -25,7 +25,7 @@ compile:
@ go build -o bin/tis100 cmd/main.go

.PHONY: build
build: lint test package
build: clean-tools lint test package

.PHONY: clean-tools
clean-tools:
Expand Down
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# TIS100

A tool and package to parse TIS100 assembly programs.

## CLI Usage

### Command `tis100 parse <path>`

Parses the TIS100 assembly program in the given `<path>`, and prints the AST result into standard output.

```
MOV 10, ACC
MOV ACC, DOWN
```

```bash
-> tis100 parse ~/program.asm
```

The result for the above execution looks like this;

```yaml
instructions:
- mov:
source:
literal: 10
destination:
register: ACC
- mov:
source:
register: ACC
destination:
register: DOWN
```
## Package Usage
```bash
go get github.com/oguzhand95/tis100
```

```go
import "github.com/oguzhand95/tis100/parser"

func main() {
b, err := os.ReadFile(c.Path)
if err != nil {
return fmt.Errorf("failed to open the file in path %s: %w", c.Path, err)
}

ast, err := parser.Parse(bytes.NewReader(b), filepath.Base(c.Path))
if err != nil {
return fmt.Errorf("failed to parse the program: %w", err)
}

// Use ast struct to some stuff
}
```

# Thanks

- Thanks [Zachtronics](https://www.zachtronics.com/) for developing this awesome [puzzle game](https://store.steampowered.com/app/370360/TIS100/)!
13 changes: 11 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package main

import (
"log"
"github.com/alecthomas/kong"

"github.com/oguzhand95/tis100/cmd/root"
)

func main() {
log.Println("hello")
cli := &root.Cli{}
ctx := kong.Parse(cli,
kong.Name("tis100"),
kong.Description("A command line interface to do TIS100 related jobs"),
kong.UsageOnError(),
)

ctx.FatalIfErrorf(ctx.Run())
}
47 changes: 47 additions & 0 deletions cmd/parse/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package parse

import (
"bytes"
"fmt"
"os"
"path/filepath"

"github.com/alecthomas/kong"
"gopkg.in/yaml.v3"

"github.com/oguzhand95/tis100/parser"
)

const help = `Parse TIS100 assembly program and output the AST tree`

type Cmd struct {
Path string `arg:"" name:"path" help:"path to tis100 assembly program"`
}

func (c *Cmd) Run(k *kong.Kong) error {
b, err := os.ReadFile(c.Path)
if err != nil {
return fmt.Errorf("failed to open the file in path %s: %w", c.Path, err)
}

ast, err := parser.Parse(bytes.NewReader(b), filepath.Base(c.Path))
if err != nil {
return fmt.Errorf("failed to parse the program: %w", err)
}

y, err := yaml.Marshal(ast)
if err != nil {
return fmt.Errorf("failed to marshal AST of the parsed TIS100 program: %w", err)
}

_, err = fmt.Fprint(k.Stdout, string(y))
if err != nil {
return fmt.Errorf("failed to print the AST to the stdout: %w", err)
}

return nil
}

func (c *Cmd) Help() string {
return help
}
7 changes: 7 additions & 0 deletions cmd/root/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package root

import "github.com/oguzhand95/tis100/cmd/parse"

type Cli struct {
Parse parse.Cmd `cmd:"" name:"parse"`
}
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
module github.com/oguzhand95/tis100

go 1.19

require (
github.com/alecthomas/kong v0.6.1
github.com/alecthomas/participle/v2 v2.0.0-beta.5
github.com/stretchr/testify v1.8.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
24 changes: 24 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
github.com/alecthomas/kong v0.6.1 h1:1kNhcFepkR+HmasQpbiKDLylIL8yh5B5y1zPp5bJimA=
github.com/alecthomas/kong v0.6.1/go.mod h1:JfHWDzLmbh/puW6I3V7uWenoh56YNVONW+w8eKeUr9I=
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
62 changes: 62 additions & 0 deletions parser/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package parser

type Program struct {
Instructions []*Instruction `yaml:"instructions" parser:"@@*"`
}

type Instruction struct {
SimpleOp *SimpleOp `yaml:"simpleOp,omitempty" parser:"( @('NOP'|'SWP'|'SAV'|'NEG')"`
Add *Add `yaml:"add,omitempty" parser:"| 'ADD' @@"`
Mov *Mov `yaml:"mov,omitempty" parser:"| 'MOV' @@"`
Jmp *Jmp `yaml:"jmp,omitempty" parser:"| 'JMP' @@"`
Jez *Jez `yaml:"jez,omitempty" parser:"| 'JEZ' @@"`
Jnz *Jnz `yaml:"jnz,omitempty" parser:"| 'JNZ' @@"`
Jgz *Jgz `yaml:"jgz,omitempty" parser:"| 'JGZ' @@"`
Jlz *Jlz `yaml:"jlz,omitempty" parser:"| 'JLZ' @@"`
Jro *Jro `yaml:"jro,omitempty" parser:"| 'JRO' @@"`
Sub *Sub `yaml:"sub,omitempty" parser:"| 'SUB' @@"`
Label string `yaml:"label,omitempty" parser:"| @(Ident) Colon)"`
}

type Mov struct {
Source *Param `yaml:"source" parser:"@@ ParamSep"`
Destination *Param `yaml:"destination" parser:"@@"`
}

type Add struct {
Source *Param `yaml:"source" parser:"@@"`
}

type Sub struct {
Source *Param `yaml:"source" parser:"@@"`
}

type Jmp struct {
Label string `yaml:"label" parser:"@Ident"`
}

type Jez struct {
Label string `yaml:"label" parser:"@Ident"`
}

type Jnz struct {
Label string `yaml:"label" parser:"@Ident"`
}

type Jgz struct {
Label string `yaml:"label" parser:"@Ident"`
}

type Jlz struct {
Label string `yaml:"label" parser:"@Ident"`
}

type Jro struct {
Source *Param `yaml:"source" parser:"@@"`
}

type Param struct {
Register *Register `yaml:"register,omitempty" parser:"( @('ACC'|'BAK'|'NIL'|'LEFT'|'UP'|'RIGHT'|'DOWN'|'ANY'|'LAST') |"`
Literal *int `yaml:"literal,omitempty" parser:"@Literal |"`
Label *string `yaml:"label,omitempty" parser:"@Ident )"`
}
24 changes: 24 additions & 0 deletions parser/ast_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package parser

type SimpleOp string

const (
OpNop SimpleOp = "NOP"
OpSwp SimpleOp = "SWP"
OpSav SimpleOp = "SAV"
OpNeg SimpleOp = "NEG"
)

type Register string

const (
RegisterAcc Register = "ACC"
RegisterBak Register = "BAK"
RegisterNil Register = "NIL"
RegisterLeft Register = "LEFT"
RegisterUp Register = "UP"
RegisterRight Register = "RIGHT"
RegisterDown Register = "DOWN"
RegisterAny Register = "ANY"
RegisterLast Register = "LAST"
)
43 changes: 43 additions & 0 deletions parser/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package parser

import (
"fmt"
"io"

"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
)

const lookahead = 2

var l = lexer.MustSimple([]lexer.SimpleRule{
{Name: "Ident", Pattern: `[A-Z]+`},
{Name: "Colon", Pattern: `:`},
{Name: "Literal", Pattern: `([-]?[0-9]+)`},
{Name: "ParamSep", Pattern: `,([ ]+)?`},
{Name: "Whitespace", Pattern: `[ \t]+`},
{Name: "EOL", Pattern: `[\n\r]+`},
})

func Parse(r io.Reader, name string) (*Program, error) {
p, err := participle.Build[Program](
participle.Lexer(l),
participle.UseLookahead(lookahead),
participle.Elide("EOL", "Whitespace"),
)
if err != nil {
return nil, fmt.Errorf("failed to create a parser: %w", err)
}

b, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("failed to read all bytes from the given reader: %w", err)
}

ast, err := p.ParseBytes(name, b)
if err != nil {
return nil, fmt.Errorf("failed to parse from bytes: %w", err)
}

return ast, nil
}
Loading

0 comments on commit b858476

Please sign in to comment.