Short tour of PAL

From PROSE Programming Language - Wiki
Revision as of 13:06, 4 August 2010 by Cambridge (Talk | contribs)

Jump to: navigation, search

Short tour of PAL in release 0.6.x

In release 0.6.x, only the PROSE Assembly Language (PAL) is available, and then only a subset of those instructions. So be aware, it's very low-level programming at this time. To learn more about the PROSE Programming Language, visit http://prose.sourceforge.net.

It is suggested you begin with the following articles before attempting this tour.

This article gives a brief tour of the following features in the PROSE Assembly Language.

Registers and indices

There are 93 unique instruction codes (opcodes) in PAL, 15 registers, 8 special register keywords and 5 data entry macros. Run prism -l and you'll see a list of them all:

    $ prism -l
    ------------------------------------------------------------
    INSTRUCTIONS

      0x80 noop
      0x81 stack/push
      0x82 stack/pull
      0x83 stack/peek
      0x84 stack/lock
      0x85 stack/unlock
      0x86 stack/flush
      0x89 obj/def
      0x8a obj/clone
      0x8b obj/edit
      0x8c obj/commit
      0x8d obj/del
      0x8e obj/addr
      0x8f obj/pa
      0x90 obj/child
      0x91 obj/dump
      0x92 class/add
      0x93 class/del
      0x94 class/load
      0x95 class/load()
      0x96 class/test
      0x97 attr/add
      0x98 attr/del
      0x99 attr/mod
      0x9a attr/mvadd
      0x9b attr/mvdel
      0x9c attr/mvmod
      0x9d attr/load
      0x9e attr/load()
      0x9f attr/test
      0xa0 attr/mvtest
      0xa1 attr/copy
      0xa2 attr/copy()
      0xa3 attr/direct
      0xa6 op/incr
      0xa7 op/decr
      0xa8 op/add
      0xa9 op/sub
      0xaa op/mult
      0xab op/div
      0xac op/mod
      0xad op/not
      0xae op/and
      0xaf op/or
      0xb0 op/xor
      0xb1 op/shl
      0xb2 op/shr
      0xb3 op/mask
      0xb4 op/swap
      0xbc error/def
      0xbd error/now
      0xbe error/jmp
      0xbf error/clr
      0xc0 func/def
      0xc1 func/call
      0xc2 func/bcall
      0xc3 func/rtn
      0xc4 local/jmp()
      0xc5 local/jsr()
      0xc6 local/jmp
      0xc7 local/jsr
      0xc8 local/rtn
      0xc9 reg/move
      0xca reg/load
      0xcb reg/jmpeq
      0xcc reg/jmpneq
      0xcd reg/jsreq
      0xce reg/jsrneq
      0xcf reg/dump
      0xd0 reg/load()
      0xd1 reg/clr
      0xd2 reg/index
      0xd3 reg/cmp
      0xd4 reg/save()
      0xd5 reg/copy
      0xd6 reg/copy()
      0xd7 reg/conv
      0xd8 reg/lcmp
      0xd9 reg/rcmp
      0xda reg/scan
      0xe8 opa/add
      0xe9 opa/sub
      0xea opa/mult
      0xeb opa/div
      0xec opa/mod
      0xed opa/not
      0xee opa/and
      0xef opa/or
      0xf0 opa/xor
      0xf1 opa/shl
      0xf2 opa/shr
      0xfb debug/source
      0xfc debug/level
    ------------------------------------------------------------
    INDICES (with Accumulator)

      0x60 to 0x63 A, LABEL
      0x64 to 0x67 A, DLABEL
      0x68 to 0x6b A, OBJREF
      0x6c to 0x6f A, TEXT
      0x70 to 0x73 A, RAW
      0x7c ------- A, PTRPOS
      0x7d ------- A, PTRNEG
    ------------------------------------------------------------
    INDICES (without Accumulator)

      0x40 to 0x43 LABEL
      0x44 to 0x47 DLABEL
      0x48 to 0x4b OBJREF
      0x4c to 0x4f TEXT
      0x50 to 0x53 RAW
      0x5c ------- PTRPOS
      0x5d ------- PTRNEG
    ------------------------------------------------------------
    REGISTERS (with Accumulator)

      0x20 A, P0
      0x21 A, P1
      0x22 A, P2
      0x23 A, P3
      0x24 A, P4
      0x25 A, P5
      0x26 A, P6
      0x27 A, P7
      0x28 A, P8
      0x29 A, P9
      0x2a A, P10
      0x2b A, P11
      0x2c A, P12
      0x2d A, P13
      0x37 A, A
      0x38 A, SFLG
      0x39 A, SCMP
      0x3a A, LOCK
      0x3b A, PCTX
      0x3c A, PEEK
      0x3d A, PULL
      0x3e A, PUSH
      0x3f A, NULL
    ------------------------------------------------------------
    REGISTERS (without Accumulator)

      0x00 P0
      0x01 P1
      0x02 P2
      0x03 P3
      0x04 P4
      0x05 P5
      0x06 P6
      0x07 P7
      0x08 P8
      0x09 P9
      0x0a P10
      0x0b P11
      0x0c P12
      0x0d P13
      0x17 A
      0x18 SFLG
      0x19 SCMP
      0x1a LOCK
      0x1b PCTX
      0x1c PEEK
      0x1d PULL
      0x1e PUSH
      0x1f NULL
    ------------------------------------------------------------
    MACROS

      0x20 EQUB
      0x40 EQUW
      0x60 EQUD
      0x80 EQUS
      0xa0 EQUP

Registers are pointers to units of data. They can point to text strings, nodes (objects in the nexus), program code, and a variety of other data types. The type of data is always associated with the register, so that bytecode instructions can validate, to a certain degree, that the arguments they have been given are correct.

Besides registers, other valid arguments that may be passed to PAL instructions are called indices. An index parameter is so called because when it is compiled into bytecode, it is saved to a table, and the argument itself converted to a numeric index. There are 7 types of index, although only 5 of these can be used explicitly within a PAL source file. Indices allow for data such as arbitrary text, code references and object references to be input.

The following code example demonstrates the use of registers and indices. It loads text and code pointers into a number of registers, and then dumps the contents to the screen.

    % This program will only have a _init section for now
    ._init

    % Load registers P0 and P1
    reg/load     P0, [A violin concerto], P1, [Brahms]
    reg/load     P10, &[._init], P11, [Brahms]

    % Report contents of registers
    reg/dump     P0, P1, P10, P11
    obj/dump     P0, P1, P10, P11

Compile and run the code:

    $ prism test
    $ prose test
    register: P0
    type: PSUNIT_TYPE_PALTEXT
    0x2b82b788504b (len 0x000011)

    register: P1
    type: PSUNIT_TYPE_PALTEXT
    0x2b82b788505e (len 0x000006)

    register: P10
    type: PSUNIT_TYPE_PALCODE
    0x0000

    register: P11
    type: PSUNIT_TYPE_PALTEXT
    0x2b82b788505e (len 0x000006)

    0x2b82b788504b (len 0x000011)
    A violin concerto
    0x2b82b788505e (len 0x000006)
    Brahms
    0x2b82b788505e (len 0x000006)
    Brahms
    prose: ERROR: no main() function found, nothing to do

The error 'no main() function found' means that the PROSE engine could only call _init sections and there was no main function from which to begin full program execution. For the purposes of this exercise the error can be ignored. For simplicity we have omitted defining a main function (as functions are described later on in this tour).

If you run prism -v on the binary, you'll see that the text 'A violin concerto' and 'Brahms' were given the index numbers 0 and 1 respectively. Note that even though the text 'Brahms' appeared twice in the source file, it only has one entry in the text table.

    $ prism -v test
    ------------------------------------------------------------
    PROSE HEADER
      File: test.pro
      Size: 112 bytes
      Compiled by: prism
      Compiler version code: 0.6.0
      Compile date: Wed Sep 30 11:51:25 2009
    ------------------------------------------------------------
    INSTRUCTION CODE

      000000 :  ca 00 4c 00 01 4c 01 ca   0a 5d 09 0b 4c 01 cf 00
      000010 :  01 0a 0b 91 00 01 0a 0b

      Size: 24 bytes
    ------------------------------------------------------------
    CODE LABELS

      idx 000000 len 000005 [_init]

      Size: 7 bytes
    ------------------------------------------------------------
    CODE ADDRESSES

      idx 000000 ref 000000

      Size: 1 bytes
    ------------------------------------------------------------
    TEXT DATA

      idx 000000 len 000011 [A violin concerto]
      idx 000001 len 000006 [Brahms]

      Size: 27 bytes
    ------------------------------------------------------------
    DATA LABELS


      Size: 0 bytes
    ------------------------------------------------------------
    DATA XREF TABLE


      Size: 0 bytes
    ------------------------------------------------------------
    DATA SEGMENTS


      Size: 0 bytes
    ------------------------------------------------------------
    END OF FILE

How to use the stack

The program stack is sized dymamically up and down as items are pushed onto it, and pulled off it. There are no theoretical limits to the number of items that can be pushed onto the stack, as long as there is available system memory.

Data is pushed onto the stack either using specific instructions that manipulate it, or by using special register keywords such as PUSH, PULL and PEEK.

The stack may also be locked. This doesn't prevent data from being pushed onto the stack, but it does protect the data already on the stack from being pulled off it. A lock may only be removed using the stack/unlock instruction, when the topmost item on the stack is the lock to be removed.

The following demonstrates stack operations:

    % This program will only have a _init section for now
    ._init

    % Load registers P0 and P1 and push onto stack
    reg/load     P0, [A violin concerto], P1, [Brahms]
    stack/push   P0, P1

    % Push code pointer onto stack
    reg/load     PUSH, &[._init]

    % Pull items off stack
    reg/load     P10, PULL
    reg/dump     PULL, PULL
    reg/dump     P10

When run, the above code yields the following output:

    register: PULL
    type: PSUNIT_TYPE_PALTEXT
    0x2b8eb7de505c (len 0x000006)

    register: PULL
    type: PSUNIT_TYPE_PALTEXT
    0x2b8eb7de5049 (len 0x000011)

    register: P10
    type: PSUNIT_TYPE_PALCODE
    0x0000

    prose: ERROR: no main() function found, nothing to do

Local branching

Without code sections or the ability to branch, the program pointer would only be able to move from one instruction to the next during bytecode execution, therefore the instructions would always be read in the order in which they appear in the source file.

PAL provides several mechanisms for modifying the program pointer. Branching is used to describe the ability to jump around to different sections of the local file. If you want to jump to a section of code in a different file, you need to use functions, which are described a little later on in this tour.

To branch to a code label, use either the local/jsr instruction which executes a subroutine that should be returned from using local/rtn, or use the local/jmp instruction which you cannot return from. These are demonstrated below:

    % This program will only have a _init section for now
    ._init
    local/jsr    &[.routine1]
    local/jmp    &[.routine2]

    % This code section will return to the caller
    .routine1
    obj/dump     [routine1]
    local/rtn

    % This code section will exit
    .routine2
    obj/dump     [routine2]
    local/rtn

Conditional branching allows sections of code within the local file to be called only if a certain condition is met. In this version of PROSE, the only type of test that can be performed is on registers, and it is achieved using the reg/jmpeq, reg/jsreq, reg/jmpneq and reg/jsrneq instructions. These instructions operate like their local/jmp and local/jsr cousins, but only if the two arguments are equal (eq) or not equal (neq).

    % This program will only have a _init section for now
    ._init
    reg/load     P0, [Mozart]
    reg/load     P1, [Beethoven]

    reg/jsreq    &[.equal], P0, [Mozart]
    reg/jmpneq   &[.notequal], P1, [Mozart]

    .equal
    obj/dump     [register comparison equal]
    local/rtn

    .notequal
    obj/dump     [register comparison not equal]
    local/rtn

Register comparison

Other types of comparison may be performed by using specific comparison instructions such as reg/cmp. These affect the value of the SCMP and SFLG registers.

The SCMP register holds the result of the last test, which can be one of:

  • 0 - Not equal
  • 1 - Equal
  • 2 - Less than
  • 4 - Greater than

The SFLG register holds the equality status of the last 32 tests in successive bits. It is a 32-bit integer where the lowest bit (bit 0) is set if the last test was equal, the next bit (bit 1) is set if the test before that was equal and so on.

The reg/clr instruction can be used for clearing the SFLG and SCMP registers.

The following code demonstrates using the SCMP and SFLG registers:

    % This program will only have a _init section for now
    ._init
    reg/load     P0, #59, P1, #72
    reg/cmp      P0, P1
    reg/jsreq    &[.lt], SCMP, #2
    reg/jsreq    &[.gt], SCMP, #4

    reg/clr
    reg/load     P2, P0, P4, P2, P3, P1, P5, P3
    reg/cmp      P0, P2, P1, P3, P0, P4, P1, P5
    reg/jmpeq    &[.equal], SFLG, #15
    obj/dump     [second test - not all comparisons were equal]
    local/rtn

    .lt
    obj/dump     [first test - result: less than]
    local/rtn

    .gt
    obj/dump     [first test - result: greater than]
    local/rtn

    .equal
    obj/dump     [second test - all comparisons equal]

Data segments

Arbitrary data can be entered into a PAL source file using data entry macros. This data is stored within sections of the bytecode file called data segments, which can be processed using the reg/load instruction in one of its indirect addressing modes. Indirect addressing is enabled if one or more of the instruction arguments are enclosed in parentheses. Different argument types enable different functionality with some of the PAL instructions when indirect addressing is used. It is a powerful feature that is described in length in the online manual pages.

Assemble the following example:

    ~strings
    EQUS {[first violins], [second violins]}
    EQUS {[violas], [cellos], [bass]}

    ~woodwind
    EQUS {[piccolos], [flutes], [clarinets], [oboes]}

    ~odd_numbers
    EQUB {1, 3, 5, 7, 9, 11, 13, 15, 17, 19}
    EQUB {21, 23, 25, 27, 29, 31, 33, 35, 37, 39}
    EQUD {0x292b2d2f, 0x31333537}

    ~pointers
    EQUP {&[.jump1], &[.jump2], &[.jump3], &[.jump4]}

    ~segments
    EQUP {&[~strings], &[~woodwind]}
    EQUP {&[~odd_numbers], &[~pointers]}

    ._init
    % Process each data segment listed in ~segments
    reg/load     P0, (&[~segments])

    .loop
    reg/load     P1, (P0)
    reg/jmpeq    &[.stop], P1, NULL

    % Now read each item from the selected data segment
    reg/load     P2, (P1)

    .loop2
    reg/load     P3, (P2)
    reg/jmpeq    &[.next], P3, NULL
    obj/dump     P3

    % This might be a code pointer, so try calling it
    % If it isn't a code pointer, we discard the error
    error/jmp    &[.trap]
    local/jsr    P3

    .trap
    error/clr
    error/jmp
    local/jmp    &[.loop2]

    .next
    reg/clr P2
    local/jmp    &[.loop]

    .stop
    local/rtn

    .jump1
    obj/dump     [jump1 called]
    local/rtn

    .jump2
    obj/dump     [jump2 called]
    local/rtn

    .jump3
    obj/dump     [jump3 called]
    local/rtn

    .jump4
    obj/dump     [jump4 called]
    local/rtn

When executed, the output should look like this:

    0x2b961ac240b0 (len 0x00000d)
    first violins
    0x2b961ac240bf (len 0x00000e)
    second violins
    0x2b961ac240cf (len 0x000006)
    violas
    0x2b961ac240d7 (len 0x000006)
    cellos
    0x2b961ac240df (len 0x000004)
    bass
    0x2b961ac240e5 (len 0x000008)
    piccolos
    0x2b961ac240ef (len 0x000006)
    flutes
    0x2b961ac240f7 (len 0x000009)
    clarinets
    0x2b961ac24102 (len 0x000005)
    oboes
    0x1
    0x3
    0x5
    0x7
    0x9
    0xb
    0xd
    0xf
    0x11
    0x13
    0x15
    0x17
    0x19
    0x1b
    0x1d
    0x1f
    0x21
    0x23
    0x25
    0x27
    0x292b2d2f
    0x31333537
    0x2b961ac24109 (len 0x00000c)
    jump1 called
    0x2b961ac24117 (len 0x00000c)
    jump2 called
    0x2b961ac24125 (len 0x00000c)
    jump3 called
    0x2b961ac24133 (len 0x00000c)
    jump4 called
    prose: ERROR: no main() function found, nothing to do

You can see how the data segments are stored in the bytecode when you run prism -v, as in the following example:

    $ prism -v test
    ------------------------------------------------------------
    PROSE HEADER
      File: test.pro
      Size: 455 bytes
      Compiled by: prism
      Compiler version code: 0.6.0
      Compile date: Fri Sep 11 17:45:39 2009
    ------------------------------------------------------------
    INSTRUCTION CODE

      000000 :  d0 00 44 04 d0 01 00 cb   5c 20 01 1f d0 02 01 d0
      000010 :  03 02 cb 5c 10 03 1f 91   03 be 5c 04 c7 03 bf be
      000020 :  c6 5d 12 d1 02 c6 5d 22   c8 91 4c 09 c8 91 4c 0a
      000030 :  c8 91 4c 0b c8 91 4c 0c   c8

      Size: 57 bytes
    ------------------------------------------------------------
    CODE LABELS

      idx 000000 len 000005 [jump1]
      idx 000001 len 000005 [jump2]
      idx 000002 len 000005 [jump3]
      idx 000003 len 000005 [jump4]
      idx 000004 len 000005 [_init]
      idx 000005 len 000004 [loop]
      idx 000006 len 000004 [stop]
      idx 000007 len 000005 [loop2]
      idx 000008 len 000004 [next]
      idx 000009 len 000004 [trap]

      Size: 66 bytes
    ------------------------------------------------------------
    CODE ADDRESSES

      idx 000000 ref 000029
      idx 000001 ref 00002d
      idx 000002 ref 000031
      idx 000003 ref 000035
      idx 000004 ref 000000
      idx 000005 ref 000004
      idx 000006 ref 000028
      idx 000007 ref 00000f
      idx 000008 ref 000023
      idx 000009 ref 00001e

      Size: 10 bytes
    ------------------------------------------------------------
    TEXT DATA

      idx 000000 len 00000d [first violins]
      idx 000001 len 00000e [second violins]
      idx 000002 len 000006 [violas]
      idx 000003 len 000006 [cellos]
      idx 000004 len 000004 [bass]
      idx 000005 len 000008 [piccolos]
      idx 000006 len 000006 [flutes]
      idx 000007 len 000009 [clarinets]
      idx 000008 len 000005 [oboes]
      idx 000009 len 00000c [jump1 called]
      idx 00000a len 00000c [jump2 called]
      idx 00000b len 00000c [jump3 called]
      idx 00000c len 00000c [jump4 called]

      Size: 145 bytes
    ------------------------------------------------------------
    DATA LABELS

      idx 000000 len 000007 [strings]
      idx 000001 len 000008 [woodwind]
      idx 000002 len 00000b [odd_numbers]
      idx 000003 len 000008 [pointers]
      idx 000004 len 000008 [segments]

      Size: 52 bytes
    ------------------------------------------------------------
    DATA XREF TABLE

      idx 000000 ref 000000
      idx 000001 ref 000001
      idx 000002 ref 000002
      idx 000003 ref 000003
      idx 000004 ref 000004

      Size: 5 bytes
    ------------------------------------------------------------
    DATA SEGMENTS

      idx 000000 len 000007 {
            81 80 81 82 82 83 84                              .......
      }
      idx 000001 len 000005 {
            83 85 86 87 88                                    .....
      }
      idx 000002 len 00001f {
            29 01 03 05 07 09 0b 0d 0f 11 13 29 15 17 19 1b   )..........)....
            1d 1f 21 23 25 27 61 29 2b 2d 2f 31 33 35 37      ..!#%'a)+-/1357
      }
      idx 000003 len 000009 {
            a3 40 00 40 01 40 02 40 03                        .@.@.@.@.
      }
      idx 000004 len 00000a {
            a1 44 00 44 01 a1 44 02 44 03                     .D.D..D.D.
      }

      Size: 67 bytes
    ------------------------------------------------------------
    END OF FILE

Note that a record is kept in the bytecode of which data entry macros were used to create the data segments. That way the data segments will return the same data items to reg/load(), and they will also appear the same as the source file when disassembled with prism -d.

Functions

It may seem unusual for a low-level assembly language to support functions, but because of their fundamental importance to the way the PROSE engine (and in particular the nexus) operates, you will find a number of features in PAL which ordinarily are only supported by higher-level languages. This makes PAL not only particularly unique, but also very efficient.

When a program is first loaded into the PROSE engine, it is represented by a single object that is attached to the nexus in a particular location (the module root). All functions that are provided by that program must be created using the func/def instruction, usually within the _init section. Each function that is created this way is then represented in the nexus by its own object, attached underneath the module root.

Once the _init section has completed, PROSE will search for a function called main, which should have been created underneath the module root. If it doesn't exist, the following error is displayed, which you will already be familiar with:

    prose: ERROR: no main() function found, nothing to do

The following example sets up the required main function, and then displays details about the main object once the function is executed. This will display the location of the main object within the nexus hierarchy, and the classes and attributes assigned to that object.

    ._init
    func/def     [main], &[.main]
    local/rtn

    .main
    obj/dump
    func/rtn

Here is the output when the above example is run:

    .prose.code.default.main:objectClass=psContainer
    .prose.code.default.main:objectClass=top
    .prose.code.default.main:objectClass=psFunction
    .prose.code.default.main:pn=[main]
    .prose.code.default.main:psObjectFileRd=[test.pro]
    .prose.code.default.main:psObjectCodeRef=[0x0006]
    .prose.code.default.main:psModuleRoot=[.prose.code.default]

Note that the correct way of returning from a function is by use of the func/rtn instruction, not local/rtn which is reserved for completing the _init section or returning from local branches.

A function is called using func/call or func/bcall. When a function is called, all 15 registers will be zeroed, as will SCMP and SFLG. When a function returns, if it was called with func/bcall then all non-zero registers will be restored to their original value, otherwise they will be zeroed again. It is known as func/bcall because this instruction bundles up the registers and pushes them onto the stack before executing the function, restoring the bundle from the stack upon return. This process is less efficient, so try to use func/call as your default choice, and only use func/bcall when you need the registers to be restored.

The difference between func/call and func/bcall is demonstrated below:

    ._init
    func/def     [main], &[.main]
    func/def     [func1], &[.my_function]
    local/rtn

    .main
    reg/load     P0, [A piano duet]
    reg/load     P13, P0
    reg/load     SCMP, #1

    func/bcall   [func1]
    reg/dump     P0, P13, SCMP

    func/call    [func1]
    reg/dump     P0, P13, SCMP
    func/rtn

    .my_function
    obj/dump
    func/rtn

This produces:

    .prose.code.default.func1:objectClass=psContainer
    .prose.code.default.func1:objectClass=top
    .prose.code.default.func1:objectClass=psFunction
    .prose.code.default.func1:pn=[func1]
    .prose.code.default.func1:psObjectFileRd=[test.pro]
    .prose.code.default.func1:psObjectCodeRef=[0x0025]
    .prose.code.default.func1:psModuleRoot=[.prose.code.default]
    register: P0
    type: PSUNIT_TYPE_PALTEXT
    0x2b6b36e2107c (len 0x00000c)

    register: P13
    type: PSUNIT_TYPE_PALTEXT
    0x2b6b36e2107c (len 0x00000c)

    register: SCMP
    type: PSUNIT_TYPE_RAWIDX
    0x1

    .prose.code.default.func1:objectClass=psContainer
    .prose.code.default.func1:objectClass=top
    .prose.code.default.func1:objectClass=psFunction
    .prose.code.default.func1:pn=[func1]
    .prose.code.default.func1:psObjectFileRd=[test.pro]
    .prose.code.default.func1:psObjectCodeRef=[0x0025]
    .prose.code.default.func1:psModuleRoot=[.prose.code.default]
    register: P0
    type: PSUNIT_TYPE_NULL

    register: P13
    type: PSUNIT_TYPE_NULL

    register: SCMP
    type: PSUNIT_TYPE_RAWIDX
    0x0

Functions may also be created in subcontainers that are not directly underneath the module root. For example:

    ._init
    func/def     [main], &[.main]
    func/def     [tools.tuning.key], &[.tuning_key]
    local/rtn

    .main
    obj/dump
    func/call    [tools.tuning.key]
    func/rtn

    .tuning_key
    obj/dump
    func/rtn

The example above yields the following output:

    .prose.code.default.main:objectClass=psContainer
    .prose.code.default.main:objectClass=top
    .prose.code.default.main:objectClass=psFunction
    .prose.code.default.main:pn=[main]
    .prose.code.default.main:psObjectFileRd=[test.pro]
    .prose.code.default.main:psObjectCodeRef=[0x000b]
    .prose.code.default.main:psModuleRoot=[.prose.code.default]
    .prose.code.default.tools.tuning.key:objectClass=psContainer
    .prose.code.default.tools.tuning.key:objectClass=top
    .prose.code.default.tools.tuning.key:objectClass=psFunction
    .prose.code.default.tools.tuning.key:pn=[key]
    .prose.code.default.tools.tuning.key:psObjectFileRd=[test.pro]
    .prose.code.default.tools.tuning.key:psObjectCodeRef=[0x0010]
    .prose.code.default.tools.tuning.key:psModuleRoot=[.prose.code.default]

Loading multiple programs

It is possible to give PROSE a list of several files to load at start-up time. The _init section in each file will be run, which will set-up the necessary functions. Then the main function will be executed.

Each file may create functions in the same location in the nexus, i.e. the same module root, which would mean they cannot create functions with identical names. Alternatively they can deploy in different areas of the nexus, with their own namespace.

If the location of the module root is not specified in the PAL source file, the code will be loaded in the location .prose.code.default. Alternatively, a different location underneath .prose.code may be specified using a data segment with the special name Module.

The following example consists of three separate source files. Save this one as one.pal:

    ~Module
    EQUS {[loft.util]}

    ._init
    func/def     [func1], &[.f1]
    local/rtn

    .f1
    reg/dump     PCTX
    func/rtn

Here is two.pal:

    ~Module
    EQUS {[loft.util]}

    ._init
    func/def     [func2], &[.f2]
    local/rtn

    .f2
    reg/dump     PCTX
    func/rtn

And here is three.pal:

    ~Module
    EQUS {[loft.test]}

    ._init
    func/def     [main], &[.main]
    local/rtn

    .main
    reg/dump     PCTX
    reg/load     PCTX, ![.prose.code.loft.util]
    func/call    ![func1]
    func/call    ![func2]
    func/rtn

Now assemble and execute these programs:

    $ prism one
    $ prism two
    $ prism three
    $ prose one two three
    register: PCTX
    type: PSUNIT_TYPE_NODE
    .prose.code.loft.test.main

    register: PCTX
    type: PSUNIT_TYPE_NODE
    .prose.code.loft.util.func1

    register: PCTX
    type: PSUNIT_TYPE_NODE
    .prose.code.loft.util.func2

Generating and handling errors

When an error occurs, unless it is trapped by the program, PROSE will exit and display the details of the error with a stack trace. The stack trace tells you exactly where the error occurred, and the list of functions that had been called up to that point.

The following example defines a new error type, and generates an error. Save this as error.pal, assemble it and run it. Note that while this example opts for defining a new error type, it would also have been possible to use one of a number of pre-defined error types. Run man ps_errortypes for a full list.

    ~Module
    EQUS {[loft.test]}

    ._init
    func/def     [main], &[.main]
    func/def     [func1], &[.f1]
    local/rtn

    .main
    error/def     [TestError], [Test error message]
    func/call     [func1]
    func/rtn

    .f1
    error/now     ![.prose.error.loft.test.TestError],
                    [This is a manually triggered error for testing purposes]

In the above example, the following stack trace will be displayed:

    * prose.error.loft.test.TestError: Test error message
    * This is a manually triggered error for testing purposes
    *     at loft.test.func1()                        [error.pro, addr 0x0014]
    *     at loft.test.main()                         [error.pro, addr 0x0010]
    *     in prose.code._tid.0

What this tells you is that the loft.test.main() function had called loft.test.func1() when the error occurred. Both functions are defined in the binary file error.pro, and the address reached within each function is displayed.

To understand the bytecode address, one needs to disassemble the bytecode with a special debug option as follows:

    $ prism -D30 -d error
    << Instruction code: 0x00001f >>
    << Code labels: 0x00003c >>
    << Code addresses: 0x000050 >>
    << Text segment: 0x000057 >>
    << Data labels: 0x0000ec >>
    << Data addresses: 0x0000f7 >>
    << Data segment: 0x0000fc >>
    ~Module
      EQUS { [loft.test] }

    0x0000 : ._init
    0x0000 :   func/def [main], &[.main]
    0x0005 :   func/def [func1], &[.f1]
    0x000a :   local/rtn
    0x000b : .main
    0x000b :   error/def [TestError], [Test error message]
    0x0010 :   func/call [func1]
    0x0013 :   func/rtn
    0x0014 : .f1
    0x0014 :   error/now ![.prose.error.loft.test.TestError], [This is a manually triggered error for testing purposes]

In most real-world scenarios, once the PROSE scripting language is supported, the programmer will not be writing in assembly language, so making reference to addresses within bytecode files will make errors very difficult to trace. To circumvent this problem, the debug/source instruction can be used at intervals throughput PAL source files to instruct PROSE of the filename and line number of the originating source. Note that the source information is reset when a function is called, so at a minimum there must be one debug/source instruction per function, although usually you'll have many more than this.

The use of debug/source is demonstrated below, where a hypothetical script called hypothetical.ps has been compiled into these assembly instructions:

    ~Module
    EQUS {[loft.test]}

    ._init
    func/def     [main], &[.main]
    func/def     [func1], &[.f1]
    local/rtn

    .main
    debug/source [hypothetical.ps], #59
    error/def    [TestError], [Test error message]
    debug/source #60
    func/call    [func1]
    func/rtn

    .f1
    debug/source [hypothetical.ps], #88
    error/now    ![.prose.error.loft.test.TestError],
                    [This is a manually triggered error for testing purposes]

This yields the following stack trace:

    * prose.error.loft.test.TestError: Test error message
    * This is a manually triggered error for testing purposes
    *     at loft.test.func1()                        [hypothetical.ps, line 88]
    *     at loft.test.main()                         [hypothetical.ps, line 60]
    *     in prose.code._tid.0

Navigating the nexus

When navigating a filesystem, you having a working directory, you have commands that allow you to move around from one directory to another, and commands for reading the contents of a directory. Many of the same features can be found in PAL for navigating around the nexus. The special register PCTX contains a pointer to the current context (akin to the working directory). Object reference indices may be used for accessing nodes that are relative to the current context or relative to the root of the nexus e.g. ![nodename], and walkers may be used for iterating nodes attached to a branch.

The following example demonstrates some basic navigation techniques:

    ~Module
    EQUS {[loft.test]}

    ._init
    func/def     [main], &[.main]
    local/rtn

    .main
    reg/load     PCTX, ![.prose.error]

    % Push first 5 system error definitions onto stack
    .load
    reg/load     P0, (![sys])
    reg/load     PUSH, (P0)
    reg/load     PUSH, (P0)
    reg/load     PUSH, (P0)
    reg/load     PUSH, (P0)
    reg/load     PUSH, (P0)
    reg/clr      P0

    % Then pull them off the stack, dumping their addresses
    .unload
    reg/dump     PULL, PULL, PULL, PULL, PULL
    func/rtn

The output of the above example is:

    register: PULL
    type: PSUNIT_TYPE_NODE
    .prose.error.sys.OutOfRange

    register: PULL
    type: PSUNIT_TYPE_NODE
    .prose.error.sys.HiddenData

    register: PULL
    type: PSUNIT_TYPE_NODE
    .prose.error.sys.BadRegister

    register: PULL
    type: PSUNIT_TYPE_NODE
    .prose.error.sys.AlreadyExists

    register: PULL
    type: PSUNIT_TYPE_NODE
    .prose.error.sys.BadDevice

Combining walkers with the stack features of PAL, it is possible to write a short program that iterates every node in the nexus, dumping out its contents. This was demonstrated in the article Running your first program.

Further reading

This short tour of PAL briefly touched upon some of the features available to the low-level programmer. There are several important features that have not been included in this article including creating, deleting and modifying objects in the nexus and basic input/output operations. These are the subject of dedicated tutorials that can be located in PAL tutorials.

PROSE is released with detailed manual pages that describe how PAL operates, and how each instruction is used. These manual pages can be read using the man command, for example man pal_intro or man pal_commands, or from the project links on the main page of this wiki.