ggvm - Go Graphics Virtual Machine
Intended to be a Stack based virtual machine, supporting push/pop, load/store, ALU instructions, and conditional/unconditional branching; but it will also have instructions which wrap the fogleman/gg library's functions. Essentially, a vm which takes input of an assembly file using a custom ISA, and is able to do everything which that ISA supports + output images.
Note
This project's progress is currently paused.
I started this project earlier this summer because I needed a tool to help me draw simple pictures and diagrams easily in a text editor. I had already been using the GG library to draw plots using Go in the past, and so wanted to incorporate that library into a little VM. A lot of the original idea has been implemented (specifically the core vm, and also parts of the GG functions, with bugs), but in doing so I found an existing alternative which does almost everything I need: Pikchr.
The syntax of Pikchr is very close to english, which actually made it hard to understand at first. But there is a syntax guide which made everything click (think like a parser, makes it easier 😄). Unlike GGVM, you cannot loop, recurse, import, or even call functions really (they are just macro expansions, not true jump points), but it has everything else that is most important to have when you consider that this is a drawing tool. Clearly alot of thought was put into its design.
For example, this simple Pikchr script generates a smiley (there is most likely an even simpler equivalent). Try it in PikchrShow.
circle rad 3cm fill yellow
circle rad 5mm fill white at first circle.c + (-1cm,1cm); dot rad 2mm at last circle.c
circle rad 5mm fill white at first circle.c + (1cm,1cm); dot rad 2mm at last circle.c
line from first circle.c + (0,5mm) down 1cm right 5mm then go left 5mm
arc ccw from first circle.c + (-2cm,0) to first circle.c + (0,-2cm)
arc ccw to first circle.c + (2cm,0)
Kudos to Mr. Richard Hipp for making it (same guy behind SQLite btw, insane rabbit hole highly recommend).
This doesn't mean and end for GGVM. Enhanced V2 coming soon!
Lately I've been using the gg library for an unrelated project, to generate custom plots. However, I found that to tweak a generated image, you must edit your Go code, and then recompile and run it, which gets tedious after a short while and is in my opinion, wasteful of compute resources.
My initial plan was to write a simple wrapper; a line-by-line interpreter. It would keep a gg Context within the program, and each line of the input file would execute one function of the gg library, adding to the image. Of course the issue with this is that you lose the ability to loop, have functions, and do numerical computations which in a lot of cases is necessary. The alternative (which I've now settled upon) is a minimal VM. The VM, just like the line-by-line interpreter, will also keep a gg Context and have instructions which run the gg library functions; but it'll also have the standard stack-based vm push/pop, load/store, branch, etc instructions which bring back the ability to loop, recurse, and perform math. I've also wanted to write a VM for a long time 😅.
Below is all the things which I want the vm to be capable of doing, on top of everything already mentioned:
- It should be capable of commenting out lines, or putting comments after code on a line. For instance
;this commentcould be its own line, or be after code. - It should be able to import code from other files. So for instance, if drawing a line is a primitive assembly instruction, and multiple lines can be used to draw an arrow, there could be an
arrow.ggvmfile which has a function that draws an arrow on the gg context, and consumes parameters for the arrow off the stack. Thearrow.ggvmfile could simply have a jump point calledarrow_func, and code in the main script could just push values onto the stack, and then runJUMP arrow.arrow_func. All such imported libraries will be local, or use the ~/ggvm directory for the library; I guess those could be called system-wide libraries. Importing should be relative to local path, or the ~/ggvm path. Something likeimport abcd/efg/library_nameto import~/ggvm/abcd/efg/library_name.ggvm, or./abcd/efg/library_name.ggvm. If there's a conflict, it should choose the local version. - I also want there to be a CLI version which interprets in real time. In this cli version, running
help <command>should immediately print the parameters and usage of the assembly instruction, and be otherwise omitted from the actual code. If possible, this should work for external functions as well, printing comments directly above the function's jump-point. SoHELP arrow.arrow_funcshould print the usage of the arrow function, after importing it. - Instructions and function names (jump points) should NOT be case sensitive. File/module names WILL be case sensitive.
- In the CLI version, only errors and help messages should be printed. When running a file, it should print everything, but not help messages (ignore them).
- Tokenization will be done in a state machine. yet to make that.
- Whenever there's an error, the stack and variables are NOT altered at all. And neither is the gg context.
- Finally, if there's time:
- maybe make a standard library? The gg library comes with a lot of built-in functions, so I think there shouldnt be a need for it.
- Basic VM instructions:
- IMPORT : Import the file specified by the filepath. It should work with/without the file extension at the end.
- PUSH : Pushes the next token onto the stack. This token could be a string, int64, or float64. A float will be if there's a decimal.
- POP: Pops the value off the stack and discards it.
- DUP: Duplicates the topmost value of the stack.
- SWAP: Swaps the two topmost values on the stack.
- STORE : Stores the topmost value of the stack into a variable.
- LOAD : Loads the value from the specified variable onto the top of the stack.
- ADD: Pops two off the stack and pushes the sum.
- SUB: Pops two off the stack and pushes the difference (top - bottom).
- MUL: Pops two off the stack and pushes the product.
- DIV: Pops two off the stack and pushes the quotient (top - bottom).
- SQRT: Pops one off the stack and pushes the square root.
- LT: Pops two off the stack, and pushes 1 if top < bottom, and 0 if otherwise.
- EQ: Pops two off the stack, and pushes 1 if top = bottom, and 0 if otherwise.
- JUMP : Jumps unconditionally to the target.
- JUMPZ : Pops the top off the stack, and jumps to the target if the value was 0.
- CASTINT: Pops the top off the stack and pushes that value cast to an int.
- CASTFLOAT: Pops the top off the stack and pushes that value cast to a float.
- HELP <instruction/function>: If the instruction/function exists, print the help for it. If not, throw an error.
Note
Going through all the gg instructions is taking too long. I'll update it later.
- gg instructions:
- ggNEWCONTEXT: Pops width and height off the stack, and pushes Context pointer. This can be stored to a variable.
- ggACTIVECONTEXT : This will set the active context to the variable specified.
- ggSAVE : Pops the gg context pointer from the stack and saves to filename. If this is split up by forward slashes, it will be saved to that directory.
- ggDEGREES: Converts stack top from radians to degrees.
- ggRADIANS: Converts stack top from degrees to radians.
- ggCLEAR
- ggCLIP
- ggRESETCLIP
- ggCLOSEPATH
- ggARC
- ggCIRCLE
- ggELLIPSE
- ggIMAGE
- ggLINE
- ggPOINT
- ggRECT
- ggSTRING
- ggMOVETO
- ggLINETO
- ggQUADTO
- ggCUBICTO
- ggPOP
- ggPUSH
- ggSCALEABT
- ggRGBA
- ggSHEAR
- ggSTRLEN
- ggFILL
- ggFILLPRES
- ggSETLINEWIDTH
- ggTRANSLATE
- ggINVERTMASK
- ggFONTSIZE
- ggFONTFILE