Difference between revisions of "Short tour of PAL"

From PROSE Programming Language - Wiki
Jump to: navigation, search
m
m
Line 490: Line 490:
 
== Data segments ==
 
== 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 <code>reg/load</code> 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.
+
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 <code>reg/load</code> 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, see <code>[http://prose.sourceforge.net/man/pal_macros.5.html pal_macros(5)]</code>.
  
 
Assemble the following example:
 
Assemble the following example:

Revision as of 18:44, 13 November 2015

Short tour of PAL in release 0.9.x

In release 0.9.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 170 unique instruction codes (opcodes) in PAL, 26 registers and 6 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
      0x84   stack/lock
      0x85   stack/unlock
      0x86   stack/flush
      0x88   obj/cmp
      0x89   obj/def
      0x8921 obj/clone
      0x8922 obj/edit
      0x8a   obj/commit
      0x8b   obj/del
      0x8c   obj/addr
      0x8c21 obj/pa
      0x8c22 obj/child
      0x8d   obj/dump
      0x92   class/add
      0x9221 class/del
      0x93   class/load
      0x9321 attr/load
      0x94   class/load()
      0x9421 attr/load()
      0x95   class/test
      0x97   attr/add
      0x9721 attr/del
      0x9722 attr/mod
      0x98   attr/mvadd
      0x9821 attr/mvdel
      0x9822 attr/mvmod
      0x99   attr/test
      0x9921 attr/mvtest
      0x9a   attr/copy
      0x9a21 attr/copy()
      0x9a22 attr/index
      0x9a23 attr/addr
      0x9a24 attr/xcopy
      0x9a25 attr/xcopy()
      0x9b   attr/direct
      0x9b21 attr/cmp
      0x9c   attr/def
      0xa6   op/incr
      0xa7   op/decr
      0xa8   op/add
      0xa821 opo/add
      0xa822 opx/add
      0xa9   op/sub
      0xa921 opo/sub
      0xa922 opx/sub
      0xaa   op/mult
      0xaa21 opo/mult
      0xaa22 opx/mult
      0xab   op/div
      0xab21 opo/div
      0xab22 opx/div
      0xac   op/mod
      0xac21 opo/mod
      0xac22 opx/mod
      0xad   op/not
      0xad21 opo/not
      0xad22 opx/not
      0xae   op/and
      0xae21 opo/and
      0xae22 opx/and
      0xaf   op/or
      0xaf21 opo/or
      0xaf22 opx/or
      0xb0   op/xor
      0xb021 opo/xor
      0xb022 opx/xor
      0xb1   op/shl
      0xb121 opo/shl
      0xb122 opx/shl
      0xb2   op/shr
      0xb221 opo/shr
      0xb222 opx/shr
      0xb3   op/mask
      0xb321 op/swap
      0xb5   list/def
      0xbb   error/def
      0xbc   error/now
      0xbd   error/jmp
      0xbe   error/clr
      0xbf   func/addr
      0xbf21 var/addr
      0xc0   func/def
      0xc1   func/call
      0xc121 func/bcall
      0xc2   func/rtn
      0xc4   local/jmp()
      0xc421 local/jsr()
      0xc5   local/jmp
      0xc521 local/jsr
      0xc6   local/rtn
      0xcc   reg/load
      0xcc21 reg/move
      0xcd   reg/jmpeq
      0xcd21 reg/jmpneq
      0xcd22 reg/jmplt
      0xcd23 reg/jmple
      0xcd24 reg/jmpgt
      0xcd25 reg/jmpge
      0xce   reg/jsreq
      0xce21 reg/jsrneq
      0xce22 reg/jsrlt
      0xce23 reg/jsrle
      0xce24 reg/jsrgt
      0xce25 reg/jsrge
      0xcf   reg/dump
      0xd0   reg/load()
      0xd1   reg/clr
      0xd2   reg/index
      0xd3   reg/cmp
      0xd4   reg/save()
      0xd5   reg/copy
      0xd521 reg/copy()
      0xd522 reg/conv
      0xd6   reg/lcmp
      0xd621 reg/rcmp
      0xd7   reg/scan
      0xd8   reg/type
      0xd9   reg/roll
      0xe0   var/def
      0xe021 mtx/def
      0xe022 itree/def
      0xe023 itree/bdef
      0xe024 xtree/def
      0xe025 xtree/bdef
      0xe1   var/local
      0xe121 mtx/local
      0xe122 itree/local
      0xe123 itree/blocal
      0xe124 xtree/local
      0xe125 xtree/blocal
      0xe2   var/static
      0xe221 mtx/static
      0xe222 itree/static
      0xe223 itree/bstatic
      0xe224 xtree/static
      0xe225 xtree/bstatic
      0xe3   var/global
      0xe321 mtx/global
      0xe322 itree/global
      0xe323 itree/bglobal
      0xe324 xtree/global
      0xe325 xtree/bglobal
      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
      0xf3   mtx/size
      0xf321 mtx/bsize
      0xf322 mtx/dim
      0xf4   mtx/resize
      0xf421 mtx/bresize
      0xf5   mtx/set
      0xf521 itree/addr
      0xf522 xtree/addr
      0xf523 itree/set
      0xf524 xtree/set
      0xf6   itree/xconv
      0xf621 xtree/iconv
      0xfb   debug/source
      0xfc   debug/level
    ------------------------------------------------------------
    INDICES

      0x40 to 0x43 IDESCTYPE_LABEL
      0x44 to 0x47 IDESCTYPE_DLABEL
      0x48 to 0x4b IDESCTYPE_OBJREF
      0x4c to 0x4f IDESCTYPE_TEXT
      0x50 to 0x53 IDESCTYPE_RAW
      0x5c ------- IFLAG_PTRPOS
      0x5d ------- IFLAG_PTRNEG
      0x60 to 0x63 LISTIDX_POS
      0x64 to 0x67 LISTIDX_NEG
      0x68 to 0x6b LISTIDX_MPOS
      0x6c to 0x6f LISTIDX_MNEG
      0x7c ------- LXFLAG_STATE
    ------------------------------------------------------------
    REGISTERS

      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
      0x0e P14
      0x0f P15
      0x16 PERR
      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
      0xc0 EQUI

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 12 types of index, although only 9 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 an _init section for now
    ._init

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

    % Load registers P10 and P11
    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 (0x07)
    0x2b82b788504b (len 0x000011)

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

    register: P10
    type: PSUNIT_TYPE_PALCODE (0x05)
    0x0000

    register: P11
    type: PSUNIT_TYPE_PALTEXT (0x07)
    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 the _init section 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: 109 bytes
      Compiled by: prism
      Compiler version code: 0.9.0
      Compile date: Sat Mar 16 23:17:10 2013
    ------------------------------------------------------------
    INSTRUCTION CODE

      000000 :  cc 00 4c 00 01 4c 01 cc   0a 5d 09 0b 4c 01 cf 00
      000010 :  01 0a 0b 8d 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 an _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 (0x07)
    0x2b8eb7de505c (len 0x000006)

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

    register: P10
    type: PSUNIT_TYPE_PALCODE (0x05)
    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 an _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. Registers can be tested 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 an _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 these registers:

    % This program will only have an _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, see pal_macros(5).

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: 453 bytes
      Compiled by: prism
      Compiler version code: 0.9.0
      Compile date: Sat Mar 16 23:20:37 2013
    ------------------------------------------------------------
    INSTRUCTION CODE

      000000 :  d0 00 44 04 d0 01 00 cd   5c 21 01 1f d0 02 01 d0
      000010 :  03 02 cd 5c 11 03 1f 8d   03 bd 5c 05 c5 21 03 be
      000020 :  bd c5 5d 13 d1 02 c5 5d   23 c6 8d 4c 09 c6 8d 4c
      000030 :  0a c6 8d 4c 0b c6 8d 4c   0c c6

      Size: 58 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 00002a
      idx 000001 ref 00002e
      idx 000002 ref 000032
      idx 000003 ref 000036
      idx 000004 ref 000000
      idx 000005 ref 000004
      idx 000006 ref 000029
      idx 000007 ref 00000f
      idx 000008 ref 000024
      idx 000009 ref 00001f

      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
      }
      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
      }

      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 registers will be zeroed, including SCMP, SFLG and PERR. 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   NULL, [func1]
    reg/dump     P0, P13, SCMP

    func/call    NULL, [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=[0x0027]
    .prose.code.default.func1:psModuleRoot=[.prose.code.default]
    register: P0
    type: PSUNIT_TYPE_PALTEXT (0x07)
    0x2b6b36e2107c (len 0x00000c)

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

    register: SCMP
    type: PSUNIT_TYPE_RAWIDX (0x08)
    0x1 (PSCMP_EQUAL)

    .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=[0x0027]
    .prose.code.default.func1:psModuleRoot=[.prose.code.default]
    register: P0
    type: PSUNIT_TYPE_NULL (0x00)

    register: P13
    type: PSUNIT_TYPE_NULL (0x00)

    register: SCMP
    type: PSUNIT_TYPE_RAWIDX (0x08)
    0x0 (PSCMP_NOTEQUAL)

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    NULL, [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=[0x0011]
    .prose.code.default.tools.tuning.key:psModuleRoot=[.prose.code.default]

It is possible to define variables and pass those variables to functions. It is also possible for functions to return variables. These are the subject of tutorials that can be located in PAL tutorials. In particular, see An arbitrary-precision calculator.

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    NULL, ![func1]
    func/call    NULL, ![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 (0x01)
    .prose.code.loft.test.main

    register: PCTX
    type: PSUNIT_TYPE_NODE (0x01)
    .prose.code.loft.util.func1

    register: PCTX
    type: PSUNIT_TYPE_NODE (0x01)
    .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     NULL, [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 0x0015]
    *     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: 0x00003d >>
    << Code addresses: 0x000051 >>
    << Text segment: 0x000058 >>
    << Data labels: 0x0000ed >>
    << Data addresses: 0x0000f8 >>
    << Data segment: 0x0000fd >>
    ~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     NULL, [func1]
    0x0014 :   func/rtn
    0x0015 : .f1
    0x0015 :   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    NULL, [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 (0x01)
    .prose.error.sys.OutOfRange

    register: PULL
    type: PSUNIT_TYPE_NODE (0x01)
    .prose.error.sys.HiddenData

    register: PULL
    type: PSUNIT_TYPE_NODE (0x01)
    .prose.error.sys.BadRegister

    register: PULL
    type: PSUNIT_TYPE_NODE (0x01)
    .prose.error.sys.AlreadyExists

    register: PULL
    type: PSUNIT_TYPE_NODE (0x01)
    .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 and variables in the nexus, basic input/output operations and passing variables between functions. 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 Reference Manual Pages online.