go-vise

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

dev.texi (12854B)


      1 @node dev
      2 @chapter Developing with vise
      3 
      4 
      5 @section Code repository structure
      6 
      7 @table @code
      8 @item asm
      9 Assembly parser and compiler.
     10 @item cache
     11 Holds and manages all loaded content.
     12 @item db
     13 Provides interface and implementations for data storage and retrieval backends.
     14 @item engine
     15 Outermost interface. Orchestrates execution of bytecode against input. 
     16 @item lang
     17 Validation and specification of language context.
     18 @item logging
     19 Logging interface and build tags for loglevels.
     20 @item persist
     21 Provides `state` and `cache` persistence across asynchronous vm executions.
     22 @item render
     23 Renders menu and templates, and enforces output size constraints.
     24 @item resource
     25 Resolves bytecode, translations, templates and menu symbols from external symbols.
     26 @item state
     27 Holds the bytecode buffer, error states and navigation states.
     28 @item vm
     29 Defines instructions, and applies transformations according to the instructions.
     30 @end table
     31 
     32 
     33 @section Interacting with @code{vise}
     34 
     35 Implementers of @code{vise} should interface with the system using the @code{engine} module.
     36 
     37 The engine comes in two implementations, one volatile base implemetnation and a subclass that includes persistent state.
     38 
     39 
     40 @subsection Modes of operation
     41 
     42 The @code{engine} module provides three different modes of operation for the engine implementations.
     43 
     44 
     45 @subsubsection Manual operation
     46 
     47 Directly interaction with an @code{engine.Engine} instance.
     48 
     49 The engine is manually initialized, and execution must be explicitly triggered with input every time the VM yields control.
     50 
     51 Output flushing must also be operated manually.
     52 
     53 The interface is the same for both persistent and volatile operation.
     54 
     55 
     56 @subsubsection Synchronous loop
     57 
     58 Receives input from a reader and writes into to a writer, and executes the underlying @code{engine.Engine} with given inputs until execution is terminated.
     59 
     60 The loop may be either persistent or volatile.
     61 
     62 This mode drives the interactive driver execution tool. 
     63 
     64 
     65 @subsubsection Asynchronous one-shot
     66 
     67 Compatible with e.g. a network socket or HTTP frontend. The @code{engine.RunPersisted} method restores a persisted state and runs one single input until VM yield after which the new state is persisted.
     68 
     69 This mode of operation can only be used with persistent state.
     70 
     71 
     72 @subsection Configuration
     73 
     74 The engine configuration defines the top-level parameters for the execution environment, including maximum output size, default language, execution entry point and more.
     75 
     76 Please refer to @code{engine.Config} for details.
     77 
     78 
     79 @anchor{sessions}
     80 @subsection Sessions
     81 
     82 The @code{engine.Config.SessionId} is used to disambiguate the end-user that is interacting with the engine.
     83 
     84 For example, in a @abbr{USSD} context, the @code{SessionId} may be the @emph{phone number} of the end-user.
     85 
     86 
     87 @anchor{execution_context}
     88 @subsection Execution context
     89 
     90 The engine stores the @code{SessionId} aswell as the current chosen @code{lang.Language} in the execution context. This is passed through to the VM operation, and is available for client code, specifically:
     91 
     92 @itemize
     93 @item When resolving symbols with @code{LOAD}. (@code{resource.EntryFunc}).
     94 @item When resolving menu symbols (@code{resource.Resource.GetMenu}).
     95 @item When retrieving node templates (@code{resource.Resource.GetTemplate}).
     96 @end itemize
     97 
     98 
     99 @subsection Blocking execution 
    100 
    101 Using the @code{engine.SetFirst()} method, a function may be defined that executes before the pending bytecode in the VM state.
    102 
    103 The function uses the same signature as the external functions executed by @code{resource} for @code{LOAD} instructions.
    104 
    105 This can be for example be used to prevent access to execution for a blocked user account, or as an override while doing maintenance.
    106 
    107 To prevent VM execution from the pre-VM check, the flag @code{TERMINATE} should be set in the @code{resource.Result.FlagSet} array.
    108 
    109 
    110 @section Resolving resources
    111 
    112 The core of implementation code is defined by implementing the @code{resource.Resource} interface. This is also described in the @ref{load_handler, LOAD handler} section.
    113 
    114 In addition to resolving external code symbols, @code{resource.Resource} implementations also translate @emph{menu labels} and @emph{templates} based on the current language context, and retrieves bytecode for execution nodes.
    115 
    116 @subsection Memory resource implementation
    117 
    118 One of two reference implementations of @code{resource.Resource} is the @code{resource.MemResource} class. It enables the client to register all node and symbol resolutions at runtime, using its functions prefixed with @code{Add...}. 
    119 
    120 The @code{resource.MemResource} implementation is primarily useful for use in tests.
    121 
    122 
    123 @subsection Filesystem resource implementation
    124 
    125 The Filesystem based resource implemementation is used by the @code{dev/interactive} tool, aswell as the executable examples in @file{examples/} directory.
    126 
    127 It is instantiated with a base directory location relative to which all resources are read.
    128 
    129 
    130 @subsubsection Bytecode (@code{resource.Resource.GetCode})
    131 
    132 Read from @file{basedir/<node>.bin}.
    133 
    134 
    135 @subsubsection Templates (@code{resource.Resource.GetTemplate})
    136 
    137 If language has been set, the template will be read from @file{basedir/<node>_<lang>}. For example, the @emph{norwegian} template for the node @code{root} will be read from @file{basedir/root_nor}.
    138 
    139 If reading the language specific template fails (or if no language has been set), template will be read from @file{basedir/<node>}.
    140 
    141 A missing template file will result in load failure and program termination.
    142 
    143 
    144 @subsubsection Menus (@code{resource.Resource.GetMenu})
    145 
    146 If language has been set, the template will be read from @file{basedir/<label>_<lang>_menu}. For example, the @emph{norwegian} template for the menu label @code{foo} will be read from @file{basedir/foo_nor_menu}.
    147 
    148 If reading the language specific menu label fails (or if no language has been set), label will be read from @file{basedir/<label>_menu}.
    149 
    150 If this also fails, the implementation returns the original label used for lookup.
    151 
    152 
    153 @subsubsection External symbols (@code{resource.Resource.FuncFor})
    154 
    155 The implementation allows setting resolver functions for symbols at runtime, using the @code{resource.FsResource.AddLocalFunc} method. This registers an @code{resource.FsResource.EntryFunc} with the lookup symbol as key. Note that the @code{EntryFunc} receives the language setting through the execution context.
    156 
    157 If no function has been registered for the requested symbol, it will be looked up in the filesystem on @file{basedir/<symbol>_<lang>.txt}. For example, the @emph{norwegian} entry for the symbol @code{foo} will be read from @file{basedir/foo_nor.txt}.
    158 
    159 If reading the language specific entry fails (or if no language has been set), entry will be read from @file{basedir/<symbol>.txt}.
    160 
    161 A missing entry will result in load failure and program termination.
    162 
    163 The implementation contains no built-in handling of the @code{SessionId} supplied by the context.
    164 
    165 
    166 @section Data provider
    167 
    168 The @code{db.Db} interface provides methods to get and set data to key-value stores.
    169 
    170 The storage keys are partitioned according to the @ref{sessions, session} context, aswell as what type of data is being stored or retrieved.
    171 
    172 The interface and the data types are defined in @code{db/db.go}.
    173 
    174 The included implementations are:
    175 
    176 @table @code
    177 @item MemDb
    178 An volatile, in-process store. Used in most tests.
    179 @item FsDb
    180 A filesystem-backed store using subdirectories to separate sessions.
    181 @item GdbmDb
    182 A @url{https://www.gnu.org/software/gdbm/gdbm,gdbm} backed store.
    183 @item PgDb
    184 A @url{https://www.postgresql.org/,Postgres} backed store, using a single table with two @code{BYTEA} columns and a connection pool.
    185 @end table
    186 
    187 
    188 @subsection Uses
    189 
    190 @code{db.Db} may fulfill all local data requirements in @code{vise}, including:
    191 
    192 @itemize
    193 @item Resource retrieval
    194 @item State and cache persistence
    195 @item Application data
    196 @end itemize
    197 
    198 
    199 @subsection Using data provider with resources
    200 
    201 The @code{resource.dbGetter} assists in using a @code{db.Db} implementation.
    202 
    203 Its functions may be assigned individually to a @code{resource.MenuResource}, allowing for co-existence of @code{db.Db} backed resources, aswell as from other sources.
    204 
    205 
    206 @subsection State persistence
    207 
    208 Any asynchronous or consecutive synchronous operation of the @code{engine.Engine} requires persistence of the associated @code{state.State} and @code{cache.Memory}. This is achieved using @code{persist.Persister}, instantiated with a @code{db.Db} implementation.
    209 
    210 The @code{db.Db} used for persistence does not need to be the same as e.g. used for retrieval of resources, or even for application data.
    211 
    212 
    213 @section Logging
    214 
    215 Loglevels are set at compile-time using the following build tags:
    216 
    217 @itemize
    218 @item @code{lognone}
    219 @item @code{logerror}
    220 @item @code{logwarn}
    221 @item @code{loginfo}
    222 @item @code{logdebug}
    223 @item @code{logtrace}
    224 @end itemize
    225 
    226 Only use @strong{ONE} of these tags.
    227 
    228 The default tag is @code{lognone} which disables logging completely.
    229 
    230 @code{logging.Logger} defines the logging interface. It is faintly inspired by the experimental @url{https://pkg.go.dev/golang.org/x/exp/slog) package, in that it differentiates explicit context logging, slog}.
    231 
    232 
    233 @section Tools
    234 
    235 Located in the @file{dev/} directory of the source code repository. 
    236 
    237 
    238 @subsection Test data generation
    239 
    240 @example
    241 go run ./dev/gendata/ <directory>
    242 @end example
    243 
    244 Outputs bytecodes and templates for test data scenarios used in `engine` unit tests.
    245 
    246 
    247 @subsection Interactive runner
    248 
    249 @example
    250 go run ./dev/interactive [-d <data_directory>] [--root <root_symbol>] [--session-id <session_id>] [--persist]
    251 @end example
    252 
    253 Creates a new interactive session using @code{engine.DefaultEngine}, starting execution at symbol @code{root_symbol}
    254 
    255 @code{data_directory} points to a directory where templates and bytecode is to be found (in the same format as generated by @file{dev/gendata}).
    256 
    257 If @code{data_directory} is not set, current directory will be used.
    258 
    259 if @code{root_symbol} is not set, the symbol @code{root} will be used.
    260 
    261 if @code{session_id} is set, mutable data will be stored and retrieved keyed by the given identifer (if implemented).
    262 
    263 If @code{persist} is set, the execution state will be persisted across sessions.
    264 
    265 
    266 @subsection Assembler
    267 
    268 @example
    269 go run ./dev/asm <assembly_file>
    270 @end example
    271 
    272 Will output bytecode on STDOUT generated from a valid assembly file.
    273 
    274 
    275 @subsection Disassembler
    276 
    277 @example
    278 go run ./dev/disasm/ <binary_file>
    279 @end example
    280 
    281 Will list all the instructions on STDOUT from a valid binary file.
    282 
    283 
    284 @subsection Interactive case examples
    285 
    286 Found in @file{examples/}.
    287 
    288 Be sure to @code{make examples} before running them.
    289 
    290 Can be run with:
    291 
    292 @example
    293 go run ./examples/<case> [...]
    294 @end example
    295 
    296 except helloworld which is run as
    297 
    298 @example
    299 go run ./dev/interactive -d ./examples/helloworld [...]
    300 @end example
    301 
    302 The available options are the same as for the @file{dev/interactive} tool.
    303 
    304 Contents of the case directory:
    305 
    306 @table @file
    307 @item *.vis
    308 assembly code.
    309 @item *.bin
    310 bytecode for each node symbol (only available after make).
    311 @item *.txt.orig
    312 default contents of a single data entry.
    313 @item *.txt
    314 current contents of a single data entry (only available after make).
    315 @end table
    316 
    317 
    318 @section Assembly examples
    319 
    320 See @file{testdata/*.vis}
    321 
    322 
    323 @section Bytecode example
    324 
    325 Currently the following rules apply for encoding in version @code{0}:
    326 
    327 @itemize
    328 @item A code instruction is a @emph{big-endian} 2-byte value. See @file{vm/opcodes.go} for valid opcode values.
    329 @item @code{symbol} value is encoded as @emph{one byte} of string length, after which the  byte-value of the string follows.
    330 @item @code{size} value is encoded as @emph{one byte} of numeric length, after which the @emph{big-endian} byte-value of the integer follows.
    331 @item @code{signal} value is encoded as @emph{one byte} of byte length, after which a byte-array representing the defined signal follows.
    332 @end itemize
    333 
    334 
    335 @subsection Example
    336 
    337 (Minimal, WIP)
    338 
    339 @verbatim
    340 000a 03666f6f 05746f666f6f    # MOUT tofoo foo  - display a menu entry for choice "foo", described by "to foo"
    341 0008 03666f6f 03626172        # INCMP bar foo   - move to node "bar" if input is "FOO"
    342 0001 0461696565 01 01         # CATCH aiee 1 1  - move to node "aiee" (and immediately halt) if input match flag (1) is set (1)
    343 0003 04616263 020104          # LOAD abc 260    - execute code symbol "abc" with a result size limit of 260 (2 byte BE integer, 0x0104)
    344 0003 04646566 00              # LOAD def 0      - execute code symbol "abc" with no size limit (sink)
    345 0005 04616263                 # MAP abc         - make "abc" available for renderer
    346 0007                          # HALT            - stop execution (require new input to continue)
    347 0006 0461313233               # MOVE a123       - move to node "a123" (regardless of input)
    348 0007                          # HALT            - stop execution
    349 @end verbatim