go-vise

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

asm.go (8119B)


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