Commits

Anonymous committed e0af8a5

added j1bas toy compiler

Comments (0)

Files changed (3)

+package main
+
+import (
+	"flag"
+	"os"
+	"strconv"
+)
+
+var t *Tokenizer
+
+func term() {
+	// as we have no MUL/DIV - parse terms tokens here
+	switch t.Token() {
+	case TOK_VARIABLE:
+		emitFetch(varAddr(t.Value()))
+		t.Next() // skip variable name
+	case TOK_AT:
+		t.Next() // skip '@'
+		if t.Token() != TOK_NUMBER {
+			panic("Invalid syntax for memory address")
+		}
+		n, _ := strconv.Atoi(t.Value())
+		emitFetch(uint16(n))
+		t.Next()
+	case TOK_NUMBER:
+		emitLit(t.Value())
+		t.Next() // skip number 
+	case TOK_LPAR:
+		t.Next()
+		expr()
+		if t.Token() != TOK_RPAR {
+			panic("')' expected");
+		}
+		t.Next() // expect RPAR
+	default:
+		panic("Expected either variable, or number, or parenthesis")
+	}
+}
+
+func expr() {
+	term()
+	for {
+		tok := t.Token()
+		if tok != TOK_PLUS && tok != TOK_MINUS && tok != TOK_AND && tok != TOK_OR {
+			break
+		}
+		t.Next()
+		term()
+		switch tok {
+		case TOK_PLUS: // T+N
+			emitAlu(2, 0x0002)
+		case TOK_MINUS: // TODO: explain code
+			emitAlu(6, 0)
+			emitLit("1")
+			emitAlu(2, 0x0002)
+			emitAlu(2, 0x0002)
+		case TOK_AND: // TODO
+		case TOK_OR: // TODO
+		}
+	}
+}
+
+func let() {
+	if t.Token() == TOK_LET {
+		t.Next() // skip keyword LET
+	}
+	var addr uint16
+	if t.Token() == TOK_AT {
+		t.Next()
+		if t.Token() != TOK_NUMBER {
+			panic("Memory address expected")
+		}
+		n, _ := strconv.Atoi(t.Value())
+		addr = uint16(n)
+	} else if t.Token() == TOK_VARIABLE {
+		addr = varAddr(t.Value())
+	} else {
+		panic("LET: variable or address expected")
+	}
+
+	println(addr)
+	t.Next() // skip variable name
+
+	if t.Token() != TOK_EQ {
+		panic("LET: '=' expected")
+	}
+
+	t.Next() // skip "="
+	expr()
+
+	emitStore(addr)
+}
+
+func cond() {
+	expr()
+	tok := t.Token()
+	t.Next()
+	expr()
+	switch tok {
+	case TOK_EQ:
+		emitAlu(7, 0x0002)
+	case TOK_LT:
+		emitAlu(8, 0x0002)
+	case TOK_GT:
+		emitAlu(8, 0x0002)
+		emitLit("0")
+		emitAlu(7, 0x0002)
+	default:
+		panic("Unexpected condition")
+	}
+}
+
+func statement() {
+	tok := t.Token()
+	switch tok {
+	case TOK_EOL: // skip empty line, do nothing
+	case TOK_REM:
+		t.SkipEol() // skip whole line
+	case TOK_LET: fallthrough
+	case TOK_VARIABLE:
+		let()
+	case TOK_IF:
+		t.Next() // skip keyword IF
+		cond()
+		if t.Token() != TOK_THEN {
+			panic("'THEN' expected")
+		}
+		t.Next() // skip keyword THEN
+		condAddr := emitCurrentOffset()
+		emit(0) // emit dummy word
+		statement()
+		// patch dummy word with conditional jump
+		emitPatch(condAddr, 0x2000|emitCurrentOffset())
+	case TOK_GOTO:
+		t.Next() // skip goto
+		if t.Token() != TOK_NUMBER {
+			panic("Number expected in GOTO")
+		}
+		line := t.Value()
+		emitGoto(line)
+		t.Next() // skip line number
+	case TOK_GOSUB:
+		t.Next() // skip gosub
+		if t.Token() != TOK_NUMBER {
+			panic("Number expected in GOTO")
+		}
+		line := t.Value()
+		// TODO: emit CALL (not jump)
+		emitGoto(line)
+		t.Next() // skip line number
+	case TOK_RETURN:
+		emitAlu(0, 0x1008) // return (";")
+	default:
+		panic("Unexpected statement token: " + t.Value())
+	}
+	if t.Token() != TOK_EOL && t.Token() != TOK_EOF {
+		panic("EOL expected")
+	}
+	t.Next()
+}
+
+func lineStatement() {
+	// line may start with a number
+	if t.Token() == TOK_NUMBER {
+		addLine(t.Value())
+		t.Next()
+	}
+
+	statement()
+}
+
+func usage() {
+	println("USAGE:")
+	println("\tj1bas <file.bas> <rom.bin>")
+}
+
+func main() {
+	defer func() {
+		if e := recover(); e != nil {
+			println("FATAL:")
+			println(e)
+		}
+	}()
+
+	flag.Usage = usage;
+
+	flag.Parse();
+
+	if len(flag.Args()) != 2 {
+		usage()
+		os.Exit(0)
+	}
+
+	from, err := os.Open(flag.Arg(0))
+	if err != nil {
+		panic(err)
+	}
+	to, err := os.OpenFile(flag.Arg(1), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+	if err != nil {
+		panic(err)
+	}
+
+	t = NewTokenizer(from)
+
+	for t.Token() != TOK_EOF {
+		lineStatement()
+	}
+
+	writeRom(to)
+	to.Close()
+}
+package main
+
+import "strconv"
+import "io"
+
+var rom [8192]uint16
+var pc uint16 = 0
+
+var lines map[string]uint16 = make(map[string]uint16)
+var linesUnresolved map[uint16]string = make(map[uint16]string)
+
+func emit(op uint16) {
+	rom[pc] = op
+	pc++
+}
+
+func emitLit(lit string) {
+	n, _:= strconv.Atoi(lit)
+	emit(0x8000 | uint16(n))
+}
+
+func addLine(line string) {
+	lines[line] = pc
+	for k, v := range linesUnresolved {
+		if v == line {
+			rom[k] = 0x0000 | pc
+		}
+	}
+}
+
+func emitGoto(line string) {
+	if addr, ok := lines[line]; ok {
+		emit(0x0000 | addr)
+	} else {
+		println("unknown line:"+line)
+		linesUnresolved[pc] = line
+		emit(0)
+	}
+}
+
+func varAddr(v string) uint16 {
+	return uint16(v[0] - 'a') * 2
+}
+
+func emitAlu(op uint16, flags uint16) {
+	emit(0x6000 | (op << 8) | flags)
+}
+
+func emitFetch(addr uint16) {
+	emit(0x8000 | addr)
+	emitAlu(12, 0)
+}
+
+func emitStore(addr uint16) {
+	emit(0x8000 | addr)
+	emitAlu(0, 0x0022)
+	emitAlu(1, 0x0002)
+}
+
+func emitPatch(addr uint16, val uint16) {
+	rom[addr] = val
+}
+
+// ret: 0x7008
+
+func writeRom(w io.Writer) {
+	for i := uint16(0); i < pc; i++ {
+		var word []byte = make([]byte, 2)
+		word[0] = byte(rom[i] & 0xff)
+		word[1] = byte(rom[i] >> 8)
+		w.Write(word)
+	}
+}
+
+func emitCurrentOffset() uint16 {
+	return pc
+}
+package main
+
+import (
+	"bufio"
+	"io"
+	"strings"
+	"unicode"
+)
+
+const (
+	TOK_EOF = iota
+
+	TOK_NUMBER
+	TOK_VARIABLE
+
+	// comment
+	TOK_REM
+
+	// keywords
+	TOK_LET
+	TOK_PRINT
+	TOK_IF
+	TOK_THEN
+	TOK_GOTO
+	TOK_GOSUB
+	TOK_RETURN
+
+	TOK_PLUS
+	TOK_MINUS
+	TOK_AND
+	TOK_OR
+	TOK_XOR
+	TOK_LPAR
+	TOK_RPAR
+	TOK_LT
+	TOK_GT
+	TOK_EQ
+	TOK_AT
+
+	// end of line
+	TOK_EOL = -1
+	// unknown token
+	TOK_OTHER = -2
+	// other errors
+	TOK_ERROR = -3
+)
+
+var keywords map[string]int = map[string]int{"rem": TOK_REM,
+	"let":  TOK_LET,
+	"if": TOK_IF,
+	"then": TOK_THEN,
+	"goto": TOK_GOTO,
+	"gosub": TOK_GOSUB,
+	"return": TOK_RETURN,
+}
+
+type Tokenizer struct {
+	r         *bufio.Reader
+	token     int
+	tokenSize int
+}
+
+func NewTokenizer(r io.Reader) *Tokenizer {
+	t := new(Tokenizer)
+	t.r = bufio.NewReader(r)
+	t.token = TOK_ERROR
+	t.tokenSize = 0
+	return t
+}
+
+func (t *Tokenizer) char() int {
+	var c int
+	if s, err := t.r.Peek(1); err == nil && len(s) == 1 {
+		c = int(s[0])
+	} else {
+		return TOK_ERROR
+	}
+
+	switch c {
+	case '\n':
+		return TOK_EOL
+	case '=':
+		return TOK_EQ
+	case '+':
+		return TOK_PLUS
+	case '-':
+		return TOK_MINUS
+	case '(':
+		return TOK_LPAR
+	case ')':
+		return TOK_RPAR
+	case '&':
+		return TOK_AND
+	case '|':
+		return TOK_OR
+	case '^':
+		return TOK_XOR
+	case '<':
+		return TOK_LT
+	case '>':
+		return TOK_GT
+	case '@':
+		return TOK_AT
+	}
+
+	return TOK_OTHER
+}
+
+func (t *Tokenizer) Token() int {
+	const MAX_NUMLEN = 5
+	var c rune
+
+	t.tokenSize = 0
+
+	// check EOF
+	if s, err := t.r.Peek(1); err == io.EOF {
+		return TOK_EOF
+	} else {
+		if err != nil {
+			return TOK_ERROR
+		} else {
+			c = rune(s[0])
+		}
+	}
+
+	// check numeric literal
+	if unicode.IsNumber(c) {
+		s, _ := t.r.Peek(MAX_NUMLEN)
+		for i := 0; i < len(s); i++ {
+			if !unicode.IsNumber(rune(s[i])) {
+				if i > 0 {
+					t.tokenSize = i
+					return TOK_NUMBER
+				} else {
+					return TOK_ERROR /* too short */
+				}
+			}
+		}
+		return TOK_ERROR /* too long */
+	}
+
+	// check keywords
+	for k, v := range keywords {
+		s, _ := t.r.Peek(len(k))
+		if strings.ToLower(string(s)) == k {
+			t.tokenSize = len(k)
+			return v
+		}
+	}
+
+	// check variables
+	if c >= 'a' && c <= 'z' {
+		t.tokenSize = 1
+		return TOK_VARIABLE
+	}
+
+	// check mathematical operators
+	tok := t.char()
+	if tok == TOK_ERROR {
+		return TOK_ERROR
+	}
+	t.tokenSize = 1
+	return tok
+}
+
+func (t *Tokenizer) Value() string {
+	if t.tokenSize > 0 {
+		buf, _ := t.r.Peek(t.tokenSize)
+		return string(buf)
+	}
+	return ""
+}
+
+func (t *Tokenizer) Next() {
+	if t.Token() == TOK_EOF {
+		return
+	}
+	buf := make([]byte, t.tokenSize)
+	t.r.Read(buf)
+
+	for {
+		s, err := t.r.Peek(1)
+		if err != nil || s[0] != ' ' {
+			break
+		}
+		t.r.Read(s)
+	}
+}
+
+func (t *Tokenizer) SkipEol() {
+	for {
+		t.Next()
+		tok := t.Token()
+		if tok == TOK_ERROR || tok == TOK_EOL {
+			break
+		}
+	}
+}