go-vise

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

asm.go (8693B)


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