Developing with vise
********************

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.

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.

Modes of operation
------------------

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

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.

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.

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.

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.

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.

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).

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.

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.

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.

Bytecode (resource.Resource.GetCode)
....................................

Read from basedir/<node>.bin.

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.

Menus (resource.Resource.GetMenu)
.................................

If language has been set, the template will be read from basedir/<label>_<lang>_menu. For example, the norwegian template for the menu label foo will be read from basedir/foo_nor_menu.

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

If this also fails, the implementation returns the original label used for lookup.

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.

Logging
=======

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

  * lognone

  * logerror

  * logwarn

  * loginfo

  * logdebug

  * logtrace

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.

Tools
=====

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

Test data generation
--------------------

     go run ./dev/gendata/ <directory>

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

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.

Assembler
---------

     go run ./dev/asm <assembly_file>

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

Disassembler
------------

     go run ./dev/disasm/ <binary_file>

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

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).

Assembly examples
=================

See testdata/*.vis

Bytecode example
================

Currently the following rules apply for encoding in version 0:

  * A code instruction is a big-endian 2-byte value. See vm/opcodes.go for valid opcode values.

  * symbol value is encoded as one byte of string length, after which the  byte-value of the string follows.

  * size value is encoded as one byte of numeric length, after which the big-endian byte-value of the integer follows.

  * signal value is encoded as one byte of byte length, after which a byte-array representing the defined signal follows.

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