11 Developing with vise

11.1 Code repository structure

asm

Assembly parser and compiler.

cache

Holds and manages all loaded content.

engine

Outermost interface. Orchestrates execution of bytecode against input.

lang

Validation and specification of language context.

logging

Logging interface and build tags for loglevels.

persist

Interface and reference implementation of ‘state‘ and ‘cache‘ persistence across asynchronous vm executions.

render

Renders menu and templates, and enforces output size constraints.

resource

Retrieves data and bytecode from external symbols, and retrieves templates.

state

Holds the bytecode buffer, error states and navigation states.

vm

Defines instructions, and applies transformations according to the instructions.

11.2 Interacting with vise

Implementers of vise should interface with the system using the engine module.

The engine comes in two implementations, one volatile base implemetnation and a subclass that includes persistent state.

11.2.1 Modes of operation

The engine module provides three different modes of operation for the engine implementations.

11.2.1.1 Manual operation

Directly interaction with an engine.Engine instance.

The engine is manually initialized, and execution must be explicitly triggered with input every time the VM yields control.

Output flushing must also be operated manually.

The interface is the same for both persistent and volatile operation.

11.2.1.2 Synchronous loop

Receives input from a reader and writes into to a writer, and executes the underlying engine.Engine with given inputs until execution is terminated.

The loop may be either persistent or volatile.

This mode drives the interactive driver execution tool.

11.2.1.3 Asynchronous one-shot

Compatible with e.g. a network socket or HTTP frontend. The engine.RunPersisted method restores a persisted state and runs one single input until VM yield after which the new state is persisted.

This mode of operation can only be used with persistent state.

11.2.2 Configuration

The engine configuration defines the top-level parameters for the execution environment, including maximum output size, default language, execution entry point and more.

Please refer to engine.Config for details.

11.2.3 Sessions

The engine.Config.SessionId is used to disambiguate the end-user that is interacting with the engine.

For example, in a USSD context, the SessionId may be the phone number of the end-user.

11.2.4 Execution context

The engine stores the SessionId aswell as the current chosen lang.Language in the execution context. This is passed through to the VM operation, and is available for client code, specifically:

  • When resolving symbols with LOAD. (resource.EntryFunc).
  • When resolving menu symbols (resource.Resource.GetMenu).
  • When retrieving node templates (resource.Resource.GetTemplate).

11.3 Resolving resources

The core of implementation code is defined by implementing the resource.Resource interface. This is also described in the LOAD handler section.

In addition to resolving external code symbols, resource.Resource implementations also translate menu labels and templates based on the current language context, and retrieves bytecode for execution nodes.

11.3.1 Memory resource implementation

One of two reference implementations of resource.Resource is the resource.MemResource class. It enables the client to register all node and symbol resolutions at runtime, using its functions prefixed with Add....

The resource.MemResource implementation is primarily useful for use in tests.

11.3.2 Filesystem resource implementation

The Filesystem based resource implemementation is used by the dev/interactive tool, aswell as the executable examples in examples/ directory.

It is instantiated with a base directory location relative to which all resources are read.

11.3.2.1 Bytecode (resource.Resource.GetCode)

Read from basedir/<node>.bin.

11.3.2.2 Templates (resource.Resource.GetTemplate)

If language has been set, the template will be read from basedir/<node>_<lang>. For example, the norwegian template for the node root will be read from basedir/root_nor.

If reading the language specific template fails (or if no language has been set), template will be read from basedir/<node>.

A missing template file will result in load failure and program termination.

11.3.2.4 External symbols (resource.Resource.FuncFor)

The implementation allows setting resolver functions for symbols at runtime, using the resource.FsResource.AddLocalFunc method. This registers an resource.FsResource.EntryFunc with the lookup symbol as key. Note that the EntryFunc receives the language setting through the execution context.

If no function has been registered for the requested symbol, it will be looked up in the filesystem on basedir/<symbol>_<lang>.txt. For example, the norwegian entry for the symbol foo will be read from basedir/foo_nor.txt.

If reading the language specific entry fails (or if no language has been set), entry will be read from basedir/<symbol>.txt.

A missing entry will result in load failure and program termination.

The implementation contains no built-in handling of the SessionId supplied by the context.

11.4 Logging

Loglevels are set at compile-time using the following build tags:

Only use ONE of these tags.

The default tag is lognone which disables logging completely.

logging.Logger defines the logging interface. It is faintly inspired by the experimental slog.

11.5 Tools

Located in the dev/ directory of the source code repository.

11.5.1 Test data generation

go run ./dev/gendata/ <directory>

Outputs bytecodes and templates for test data scenarios used in ‘engine‘ unit tests.

11.5.2 Interactive runner

go run ./dev/interactive [-d <data_directory>] [--root <root_symbol>] [--session-id <session_id>] [--persist]

Creates a new interactive session using engine.DefaultEngine, starting execution at symbol root_symbol

data_directory points to a directory where templates and bytecode is to be found (in the same format as generated by dev/gendata).

If data_directory is not set, current directory will be used.

if root_symbol is not set, the symbol root will be used.

if session_id is set, mutable data will be stored and retrieved keyed by the given identifer (if implemented).

If persist is set, the execution state will be persisted across sessions.

11.5.3 Assembler

go run ./dev/asm <assembly_file>

Will output bytecode on STDOUT generated from a valid assembly file.

11.5.4 Disassembler

go run ./dev/disasm/ <binary_file>

Will list all the instructions on STDOUT from a valid binary file.

11.5.5 Interactive case examples

Found in examples/.

Be sure to make examples before running them.

Can be run with:

go run ./examples/<case> [...]

except helloworld which is run as

go run ./dev/interactive -d ./examples/helloworld [...]

The available options are the same as for the dev/interactive tool.

Contents of the case directory:

*.vis

assembly code.

*.bin

bytecode for each node symbol (only available after make).

*.txt.orig

default contents of a single data entry.

*.txt

current contents of a single data entry (only available after make).

11.6 Assembly examples

See testdata/*.vis

11.7 Bytecode example

Currently the following rules apply for encoding in version 0:

11.7.1 Example

(Minimal, WIP)

000a 03666f6f 05746f666f6f    # MOUT tofoo foo  - display a menu entry for choice "foo", described by "to foo"
0008 03666f6f 03626172        # INCMP bar foo   - move to node "bar" if input is "FOO"
0001 0461696565 01 01         # CATCH aiee 1 1  - move to node "aiee" (and immediately halt) if input match flag (1) is not set (1)
0003 04616263 020104          # LOAD abc 260    - execute code symbol "abc" with a result size limit of 260 (2 byte BE integer, 0x0104)
0003 04646566 00              # LOAD def 0      - execute code symbol "abc" with no size limit (sink)
0005 04616263                 # MAP abc         - make "abc" available for renderer
0007                          # HALT            - stop execution (require new input to continue)
0006 0461313233               # MOVE a123       - move to node "a123" (regardless of input)
0007                          # HALT            - stop execution