go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

asm.go (8080B)


      1 package asm
      2 
      3 import (
      4 	"bytes"
      5 	"encoding/binary"
      6 	"fmt"
      7 	"io"
      8 	"log"
      9 	"math"
     10 	"strconv"
     11 	"strings"
     12 
     13 	"github.com/alecthomas/participle/v2"
     14 	"github.com/alecthomas/participle/v2/lexer"
     15 
     16 	"git.defalsify.org/vise.git/vm"
     17 )
     18 
     19 
     20 // Asm assembles bytecode from the vise assembly mini-language.
     21 //
     22 // TODO: Conceal from outside use
     23 type Asm struct {
     24 	Instructions []*Instruction `@@*`
     25 }
     26 
     27 // Arg holds all parsed argument elements of a single line of assembly code.
     28 //
     29 // TODO: Conceal from outside use
     30 type Arg struct {
     31 	Sym *string `(@Sym Whitespace?)?`
     32 	Size *uint32 `(@Size Whitespace?)?`
     33 	Flag *uint8 `(@Size Whitespace?)?`
     34 	Selector *string `(@Sym Whitespace?)?`
     35 	Desc *string `(@Sym Whitespace?)?`
     36 	//Desc *string `(Quote ((@Sym | @Size) @Whitespace?)+ Quote Whitespace?)?`
     37 }
     38 
     39 // writes the parsed instruction bytes to output.
     40 func flush(b *bytes.Buffer, w io.Writer) (int, error) {
     41 	if w != nil {
     42 		return w.Write(b.Bytes())
     43 	}
     44 	return 0, nil
     45 }
     46 
     47 func parseTwoSym(b *bytes.Buffer, arg Arg) (int, error) {
     48 	var rn int
     49 
     50 	var selector string
     51 	var sym string
     52 	if arg.Size != nil {
     53 		selector = strconv.FormatUint(uint64(*arg.Size), 10)
     54 		//sym = *arg.Selector
     55 		sym = *arg.Sym
     56 	} else if arg.Selector != nil {
     57 		if *arg.Sym == "*" {
     58 			sym = *arg.Selector
     59 			selector = *arg.Sym
     60 		} else {
     61 			sym = *arg.Sym
     62 			selector = *arg.Selector
     63 		}
     64 	}
     65 
     66 
     67 	n, err := writeSym(b, sym)
     68 	rn += n
     69 	if err != nil {
     70 		return rn, err
     71 	}
     72 
     73 	n, err = writeSym(b, selector)
     74 	rn += n
     75 	if err != nil {
     76 		return rn, err
     77 	}
     78 	return rn, nil
     79 }
     80 
     81 func parseTwoSymReverse(b *bytes.Buffer, arg Arg) (int, error) {
     82 	var rn int
     83 
     84 	sym := *arg.Selector
     85 	selector := *arg.Sym 
     86 	n, err := writeSym(b, selector)
     87 	rn += n
     88 	if err != nil {
     89 		return rn, err
     90 	}
     91 
     92 	n, err = writeSym(b, sym)
     93 	rn += n
     94 	if err != nil {
     95 		return rn, err
     96 	}
     97 
     98 	return rn, nil
     99 }
    100 
    101 func parseSig(b *bytes.Buffer, arg Arg) (int, error) {
    102 	var rn int
    103 
    104 	n, err := writeSym(b, *arg.Sym)
    105 	rn += n
    106 	if err != nil {
    107 		return rn, err
    108 	}
    109 
    110 	n, err = writeSize(b, *arg.Size)
    111 	rn += n
    112 	if err != nil {
    113 		return rn, err
    114 	}
    115 
    116 	n, err = b.Write([]byte{uint8(*arg.Flag)})
    117 	rn += n
    118 	if err != nil {
    119 		return rn, err
    120 	}
    121 
    122 	return rn, nil
    123 }
    124 
    125 func parseSized(b *bytes.Buffer, arg Arg) (int, error) {
    126 	var rn int
    127 
    128 	n, err := writeSym(b, *arg.Sym)
    129 	rn += n
    130 	if err != nil {
    131 		return rn, err
    132 	}
    133 
    134 	n, err = writeSize(b, *arg.Size)
    135 	rn += n
    136 	if err != nil {
    137 		return rn, err
    138 	}
    139 
    140 	return rn, nil
    141 }
    142 
    143 func parseFlagged(b *bytes.Buffer, arg Arg) (int, error) {
    144 	var rn int
    145 
    146 	n, err := writeSize(b, *arg.Size)
    147 	rn += n
    148 	if err != nil {
    149 		return rn, err
    150 	}
    151 
    152 	n, err = b.Write([]byte{uint8(*arg.Flag)})
    153 	rn += n
    154 	if err != nil {
    155 		return rn, err
    156 	}
    157 
    158 	return rn, nil
    159 
    160 }
    161 
    162 func parseOne(op vm.Opcode, instruction *Instruction, w io.Writer) (int, error) {
    163 	a := instruction.OpArg
    164 	var n_buf int
    165 	var n_out int
    166 	
    167 	b := bytes.NewBuffer(nil)
    168 
    169 	n, err := writeOpcode(b, op)
    170 	n_buf += n
    171 	if  err != nil {
    172 		return n_out, err
    173 	}
    174 
    175 	// Catch
    176 	if a.Selector != nil {
    177 		log.Printf("have selector %v", instruction)
    178 		var n int
    179 		var err error
    180 		if op == vm.MOUT {
    181 			n, err = parseTwoSymReverse(b, a)
    182 		} else {
    183 			n, err = parseTwoSym(b, a)
    184 		}
    185 		n_buf += n
    186 		if err != nil {
    187 			return n_out, err
    188 		}
    189 		return flush(b, w)
    190 	}
    191 
    192 	// Catch CATCH, LOAD and twosyms with integer-as-string
    193 	if a.Size != nil {
    194 		log.Printf("have size %v", instruction)
    195 		if a.Sym == nil {
    196 			n, err := parseFlagged(b, a)
    197 			n_buf += n
    198 			if err != nil {
    199 				return n_out, err
    200 			}
    201 		} else {
    202 			if a.Flag != nil {
    203 				n, err := parseSig(b, a)
    204 				n_buf += n
    205 				if err != nil {
    206 					return n_out, err
    207 				}
    208 			} else if op == vm.LOAD {
    209 				n, err := parseSized(b, a)
    210 				n_buf += n
    211 				if err != nil {
    212 					return n_out, err
    213 				}
    214 			} else {
    215 				n, err := parseTwoSym(b, a)
    216 				n_buf += n
    217 				if err != nil {
    218 					return n_out, err
    219 				}
    220 
    221 			}
    222 		}
    223 		return flush(b, w)
    224 	}
    225 
    226 	// Catch HALT
    227 	if a.Sym == nil {
    228 		return flush(b, w)
    229 	}
    230 
    231 	n, err = writeSym(b, *a.Sym)
    232 	n_buf += n
    233 	return flush(b, w)
    234 }
    235 
    236 // String implements the String interface.
    237 func (a Arg) String() string {
    238 	s := "[Arg]"
    239 	if a.Sym != nil {
    240 		s += " Sym: " + *a.Sym
    241 	}
    242 	if a.Size != nil {
    243 		s += fmt.Sprintf(" Size: %v", *a.Size)
    244 	}
    245 	if a.Flag != nil {
    246 		s += fmt.Sprintf(" Flag: %v", *a.Flag)
    247 	}
    248 	if a.Selector != nil {
    249 		s += " Selector: " + *a.Selector
    250 	}
    251 	if a.Desc != nil {
    252 		s += " Description: " + *a.Desc
    253 	}
    254 
    255 	return fmt.Sprintf(s)
    256 }
    257 
    258 // Instruction represents one full line of assembly code.
    259 //
    260 // TODO: Conceal from outside use
    261 type Instruction struct {
    262 	OpCode string `@Ident`
    263 	OpArg Arg `(Whitespace @@)?`
    264 	Comment string `Comment? EOL`
    265 }
    266 
    267 // String implements the String interface.
    268 func (i Instruction) String() string {
    269 	return fmt.Sprintf("%s %s", i.OpCode, i.OpArg)
    270 }
    271 
    272 var (
    273 	asmLexer = lexer.MustSimple([]lexer.SimpleRule{
    274 		{"Comment", `(?:#)[^\n]*`},
    275 		{"Ident", `^[A-Z]+`},
    276 		{"Size", `[0-9]+`},
    277 		{"Sym", `[a-zA-Z_\*\.\^\<\>][a-zA-Z0-9_]*`},
    278 		{"Whitespace", `[ \t]+`},
    279 		{"EOL", `[\n\r]+`},
    280 		{"Quote", `["']`},
    281 	})
    282 	asmParser = participle.MustBuild[Asm](
    283 		participle.Lexer(asmLexer),
    284 		participle.Elide("Comment", "Whitespace"),
    285 	)
    286 )
    287 
    288 func numSize(n uint32) int {
    289 	v := math.Log2(float64(n))
    290 	return int((v  / 8) + 1)
    291 }
    292 
    293 func writeOpcode(w *bytes.Buffer, op vm.Opcode) (int, error) {
    294 	bn := [2]byte{}
    295 	binary.BigEndian.PutUint16(bn[:], uint16(op))
    296 	n, err := w.Write(bn[:])
    297 	return n, err
    298 }
    299 
    300 func writeSym(w *bytes.Buffer, s string) (int, error) {
    301 	sz := len(s)
    302 	if sz > 255 {
    303 		return 0, fmt.Errorf("string size %v too big", sz)
    304 	}
    305 	w.Write([]byte{byte(sz)})
    306 	return w.WriteString(s)
    307 }
    308 
    309 func writeSize(w *bytes.Buffer, n uint32) (int, error) {
    310 	if n == 0 {
    311 		return w.Write([]byte{0x01, 0x00})
    312 	}
    313 	bn := [4]byte{}
    314 	sz := numSize(n)
    315 	if sz > 4 {
    316 		return 0, fmt.Errorf("number size %v too big", sz)
    317 	}
    318 	w.Write([]byte{byte(sz)})
    319 	binary.BigEndian.PutUint32(bn[:], n)
    320 	c := 4-sz
    321 	return w.Write(bn[c:])
    322 }
    323 
    324 // Batcher handles assembly commands that generates multiple instructions, such as menu navigation commands.
    325 type Batcher struct {
    326 	menuProcessor MenuProcessor
    327 	inMenu bool
    328 }
    329 
    330 // NewBatcher creates a new Batcher objcet.
    331 func NewBatcher(mp MenuProcessor) Batcher {
    332 	return Batcher{
    333 		menuProcessor: NewMenuProcessor(),
    334 	}
    335 }
    336 
    337 // MenuExit generates the instructions for the batch and writes them to the given io.Writer.
    338 func(bt *Batcher) MenuExit(w io.Writer) (int, error) {
    339 	if !bt.inMenu {
    340 		return 0, nil
    341 	}
    342 	bt.inMenu = false
    343 	b := bt.menuProcessor.ToLines()
    344 	return w.Write(b)
    345 }
    346 
    347 // MenuAdd adds a new menu instruction to the batcher.
    348 func(bt *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) {
    349 	bt.inMenu = true
    350 	var selector string
    351 	var sym string
    352 	var display string
    353 	if arg.Desc != nil {
    354 		sym = *arg.Sym
    355 		display = *arg.Desc
    356 		selector = *arg.Selector
    357 	} else if arg.Size != nil {
    358 		if arg.Sym != nil {
    359 			sym = *arg.Sym
    360 		}
    361 		selector = strconv.FormatUint(uint64(*arg.Size), 10)
    362 		display = *arg.Selector
    363 	} else {
    364 		selector = *arg.Sym
    365 		display = *arg.Selector
    366 	}
    367 	log.Printf("menu processor add %v '%v' '%v' '%v'", code, selector, display, sym)
    368 	err := bt.menuProcessor.Add(code, selector, display, sym)
    369 	return 0, err
    370 }
    371 
    372 // Exit is a synonym for MenuExit
    373 func(bt *Batcher) Exit(w io.Writer) (int, error) {
    374 	return bt.MenuExit(w)
    375 }
    376 
    377 // Parse one or more lines of assembly code, and write assembled bytecode to the provided writer.
    378 func Parse(s string, w io.Writer) (int, error) {
    379 	rd := strings.NewReader(s)
    380 	ast, err := asmParser.Parse("file", rd)
    381 	if err != nil {
    382 		return 0, err
    383 	}
    384 
    385 	batch := Batcher{
    386 		
    387 	}
    388 
    389 	var rn int
    390 	for _, v := range ast.Instructions {
    391 		log.Printf("parsing line %v: %v", v.OpCode, v.OpArg)
    392 		op, ok := vm.OpcodeIndex[v.OpCode]
    393 		if !ok {
    394 			n, err := batch.MenuAdd(w, v.OpCode, v.OpArg)
    395 			rn += n
    396 			if err != nil {
    397 				return rn, err
    398 			}
    399 		} else {
    400 			n, err := batch.MenuExit(w)
    401 			if err != nil {
    402 				return rn, err
    403 			}
    404 			rn += n
    405 			n, err = parseOne(op, v, w)
    406 			rn += n
    407 			if err != nil {
    408 				return rn, err
    409 			}
    410 			log.Printf("wrote %v bytes for %v", n, v.OpArg)
    411 		}
    412 	}
    413 	n, err := batch.Exit(w)
    414 	rn += n
    415 	if err != nil {
    416 		return rn, err
    417 	}
    418 	rn += n
    419 
    420 	return rn, err
    421 }