Skip to content

Commit

Permalink
Add support for declaring variables.
Browse files Browse the repository at this point in the history
  • Loading branch information
aleury committed Jan 19, 2024
1 parent 63cb858 commit 2f72938
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 52 deletions.
17 changes: 17 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ type ConstantDefinitionStatement struct {
func (cds *ConstantDefinitionStatement) statementNode() {}
func (cds *ConstantDefinitionStatement) TokenLiteral() string { return cds.Token.Literal }

type VariableDefinitionStatement struct {
Token token.Token // the token.VARIABLE_DEFINITION token
Name *Identifier
Value Expression
}

func (vds *VariableDefinitionStatement) statementNode() {}
func (vds *VariableDefinitionStatement) TokenLiteral() string { return vds.Token.Literal }

type LabelDefinitionStatement struct {
Token token.Token // the token.LABEL_DEFINITION token
}
Expand Down Expand Up @@ -81,3 +90,11 @@ type CharacterLiteral struct {

func (cl *CharacterLiteral) expressionNode() {}
func (cl *CharacterLiteral) TokenLiteral() string { return cl.Token.Literal }

type StringLiteral struct {
Token token.Token // the token.STRING token
Value string
}

func (sl *StringLiteral) expressionNode() {}
func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal }
12 changes: 4 additions & 8 deletions examples/print.g
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
@data msg "hello world"
VARB msg "hello world"

.run
SETX msg ; set X register to the address of msg
JUMP print

.done
HALT

.print
MOVX A ; move the value of address in X to A
JAEZ done ; jump to label 'done' if A = 0
MOVE *X -> A ; move the value of address in X to A
OUTA ; print A
INCX ; increment address stored in X
JUMP print ; jump back to the start of .print to print the next character
JANZ print ; jump to label 'done' if A = 0
HALT

6 changes: 6 additions & 0 deletions examples/print2.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
JUMP start

VARB msg "hello world"

.start

143 changes: 107 additions & 36 deletions gmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
"io"
"os"
"strings"

"golang.org/x/exp/slices"
)

const MemSize = 1024
Expand All @@ -32,6 +30,7 @@ const (
OpADDA
OpMULA
OpMOVA
OpMOVE // temporary solution for destinguishing between moving between memory vs between registers
OpSETA
OpSETX
OpSETY
Expand Down Expand Up @@ -169,6 +168,9 @@ func (g *Machine) Run() {
case RegY:
g.Y = g.A
}
case OpMOVE:
offset := g.Next()
g.A = g.Memory[g.MemOffset+offset]
case OpSETA:
g.A = g.Next()
case OpSETX:
Expand Down Expand Up @@ -211,14 +213,16 @@ type ref struct {
}

type symbolTable struct {
labels map[string]Word
consts map[string]Word
labels map[string]Word
consts map[string]Word
variables map[string]Word
}

func newSymbolTable() *symbolTable {
return &symbolTable{
labels: make(map[string]Word),
consts: make(map[string]Word),
labels: make(map[string]Word),
consts: make(map[string]Word),
variables: make(map[string]Word),
}
}

Expand All @@ -230,13 +234,20 @@ func (t *symbolTable) defineConst(name string, value Word) {
t.consts[name] = value
}

func (t *symbolTable) defineVariable(name string, value Word) {
t.variables[name] = value
}

func (t *symbolTable) lookup(name string) (Word, bool) {
if value, ok := t.labels[name]; ok {
return value, ok
}
if value, ok := t.consts[name]; ok {
return value, ok
}
if value, ok := t.variables[name]; ok {
return value, ok
}
return Word(0), false
}

Expand All @@ -252,10 +263,10 @@ func Assemble(reader io.Reader) ([]Word, error) {
p := parser.New(l)
astProgram := p.ParseProgram()
if astProgram == nil {
return program, errors.New("failed to parse program")
return nil, errors.New("failed to parse program")
}
if len(p.Errors()) > 0 {
return program, p.Errors()[0]
return nil, p.Errors()[0]
}

// Assemble program
Expand All @@ -267,6 +278,20 @@ func Assemble(reader io.Reader) ([]Word, error) {
case *ast.LabelDefinitionStatement:
name := strings.TrimPrefix(stmt.TokenLiteral(), ".")
symbols.defineLabel(name, Word(len(program)))
case *ast.VariableDefinitionStatement:
symbols.defineVariable(stmt.Name.Value, Word(len(program)))
switch operand := stmt.Value.(type) {
case *ast.IntegerLiteral:
program = append(program, Word(operand.Value))
case *ast.StringLiteral:
strSlice := make([]Word, len(operand.Value))
for i, c := range operand.Value {
strSlice[i] = Word(c)
}
program = append(program, strSlice...)
default:
return nil, errors.New("invalid variable definition")
}
case *ast.OpcodeStatement:
program, refs, err = assembleOpcodeStatement(stmt, program, refs)
if err != nil {
Expand All @@ -290,49 +315,95 @@ func Assemble(reader io.Reader) ([]Word, error) {
}

func assembleOpcodeStatement(stmt *ast.OpcodeStatement, program []Word, refs []ref) ([]Word, []ref, error) {
opcode, ok := opcodes[stmt.TokenLiteral()]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrUndefinedInstruction, stmt.TokenLiteral(), stmt.Token.Line)
}
program = append(program, opcode)

if stmt.Operand == nil {
opcode, ok := opcodes[stmt.TokenLiteral()]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrUndefinedInstruction, stmt.TokenLiteral(), stmt.Token.Line)
}
program = append(program, opcode)
return program, refs, nil
}

switch operand := stmt.Operand.(type) {
case *ast.RegisterLiteral:
if !slices.Contains([]Word{OpADDA, OpMULA, OpMOVA}, opcode) {
opcodeStr := stmt.TokenLiteral()

switch opcodeStr {
case "MOVA":
switch operand := stmt.Operand.(type) {
case *ast.RegisterLiteral:
register, ok := registers[operand.TokenLiteral()]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidRegister, operand.TokenLiteral(), operand.Token.Line)
}
program = append(program, OpMOVA, register)
case *ast.Identifier:
program = append(program, OpMOVE)
r := ref{
Name: operand.TokenLiteral(),
Line: operand.Token.Line,
Address: Word(len(program)),
}
refs = append(refs, r)
program = append(program, Word(0))
default:
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidOperand, stmt.TokenLiteral(), stmt.Token.Line)
}
register, ok := registers[operand.TokenLiteral()]
case "MULA", "ADDA":
opcode, ok := opcodes[opcodeStr]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidRegister, operand.TokenLiteral(), operand.Token.Line)
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrUndefinedInstruction, stmt.TokenLiteral(), stmt.Token.Line)
}
program = append(program, register)
case *ast.Identifier:
if !slices.Contains([]Word{OpSETA, OpJUMP, OpJXNZ}, opcode) {
switch operand := stmt.Operand.(type) {
case *ast.RegisterLiteral:
register, ok := registers[operand.TokenLiteral()]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidRegister, operand.TokenLiteral(), operand.Token.Line)
}
program = append(program, opcode, register)
default:
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidOperand, stmt.TokenLiteral(), stmt.Token.Line)
}
r := ref{
Name: operand.TokenLiteral(),
Line: operand.Token.Line,
Address: Word(len(program)),
case "SETA", "SETX", "SETY":
opcode, ok := opcodes[opcodeStr]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrUndefinedInstruction, stmt.TokenLiteral(), stmt.Token.Line)
}
refs = append(refs, r)
program = append(program, Word(0))
case *ast.IntegerLiteral:
if !slices.Contains([]Word{OpSETA, OpSETX, OpSETY, OpJUMP}, opcode) {
switch operand := stmt.Operand.(type) {
case *ast.IntegerLiteral:
program = append(program, opcode, Word(operand.Value))
case *ast.CharacterLiteral:
program = append(program, opcode, Word(operand.Value))
case *ast.Identifier:
program = append(program, opcode)
r := ref{
Name: operand.TokenLiteral(),
Line: operand.Token.Line,
Address: Word(len(program)),
}
refs = append(refs, r)
program = append(program, Word(0))
default:
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidOperand, stmt.TokenLiteral(), stmt.Token.Line)
}
program = append(program, Word(operand.Value))
case *ast.CharacterLiteral:
if !slices.Contains([]Word{OpSETA, OpSETX, OpSETY}, opcode) {
case "JUMP", "JXNZ":
opcode, ok := opcodes[opcodeStr]
if !ok {
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrUndefinedInstruction, stmt.TokenLiteral(), stmt.Token.Line)
}
switch operand := stmt.Operand.(type) {
case *ast.IntegerLiteral:
program = append(program, opcode, Word(operand.Value))
case *ast.Identifier:
program = append(program, opcode)
r := ref{
Name: operand.TokenLiteral(),
Line: operand.Token.Line,
Address: Word(len(program)),
}
refs = append(refs, r)
program = append(program, Word(0))
default:
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidOperand, stmt.TokenLiteral(), stmt.Token.Line)
}
program = append(program, Word(operand.Value))
default:
return nil, nil, fmt.Errorf("%w: %s at line %d", ErrInvalidOperand, stmt.TokenLiteral(), stmt.Token.Line)
}

return program, refs, nil
Expand Down
54 changes: 51 additions & 3 deletions gmachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,11 +594,11 @@ func TestMOVAY(t *testing.T) {
}
}

func TestMOVA_FailsForInvalidRegister(t *testing.T) {
func TestMOVA_FailsForUnknownIdentifier(t *testing.T) {
t.Parallel()
g := gmachine.New(nil)
err := assembleAndRunFromString(g, "MOVA Z")
wantErr := gmachine.ErrInvalidOperand
wantErr := gmachine.ErrUnknownIdentifier
if err == nil {
t.Fatal("expected an error to be returned for invalid argument to MOVA")
}
Expand Down Expand Up @@ -836,6 +836,53 @@ OUTA
}
}

func TestVARB_DeclaresAIntegerVariableInMemory(t *testing.T) {
t.Parallel()
g := gmachine.New(nil)
err := assembleAndRunFromString(g, `VARB num 42`)
if err != nil {
t.Fatal("didn't expect an error:", err)
}
var want gmachine.Word = 42
got := g.Memory[g.MemOffset]
if want != got {
t.Errorf("want num %d, got %d", want, got)
}
}

func TestMOVA_MovesAVariableToAccumulatorRegister(t *testing.T) {
t.Parallel()
g := gmachine.New(nil)
err := assembleAndRunFromString(g, `
JUMP start
VARB num 42
.start
MOVA num
HALT
`)
if err != nil {
t.Fatal("didn't expect an error:", err)
}
var wantA gmachine.Word = 42
if wantA != g.A {
t.Errorf("want A %d, got %d", wantA, g.A)
}
}

func TestVARB_DeclaresAStringVariableInMemory(t *testing.T) {
t.Parallel()
g := gmachine.New(nil)
err := assembleAndRunFromString(g, `VARB msg "hello world"`)
if err != nil {
t.Fatal("didn't expect an error:", err)
}
want := []gmachine.Word{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}
got := g.Memory[int(g.MemOffset) : int(g.MemOffset)+len(want)]
if !cmp.Equal(want, got) {
t.Error(cmp.Diff(want, got))
}
}

func TestCompile(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -890,7 +937,8 @@ func TestCompile_FailsForWriteError(t *testing.T) {
}

func assembleFromString(input string) ([]gmachine.Word, error) {
return gmachine.Assemble(strings.NewReader(input))
program, err := gmachine.Assemble(strings.NewReader(input))
return program, err
}

func assembleAndRunFromString(g *gmachine.Machine, input string) error {
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.20
require (
github.com/google/go-cmp v0.5.9
github.com/rogpeppe/go-internal v1.11.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
)

require (
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
Expand Down
Loading

0 comments on commit 2f72938

Please sign in to comment.