Collecting user input

From PROSE Programming Language - Wiki
Revision as of 17:14, 19 March 2013 by Cambridge (Talk | contribs)

Jump to: navigation, search

PAL Tutorial - Collecting user input

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

A full list of available tutorials for the PROSE Assembly Language can be found on the tutorials index page.

This tutorial works through an example PAL program that collects from the user a list of classroom subjects and student names, correlating the two based on a 4-grade system. Due to a bug in the 0.8.x releases and 0.9.0, you will need to be running at least 0.9.1 for this tutorial to work.

Objectives

The objective is to write a PAL program that will collect a list of classroom subjects and students, grading each on a 1-4 scale and then correlating the subjects and students at the same level.

The program will start by asking for the number of subjects from the user and then each subject name, e.g.

    # of subjects [1-10]? 5

    Name of subject #1? Maths
    Name of subject #2? Physics
    Name of subject #3? History
    Name of subject #4? English Lit.
    Name of subject #5? Geography

Then the program will collect grades for each of the chosen subjects, e.g.

    Subject: Maths
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 3

    Subject: Physics
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 4

    Subject: History
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Subject: English Lit.
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Subject: Geography
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 1

The program will then ask for the number of students and prompt for each student name, e.g.

    # of students [1-20]? 20

    Name of student #1? D. Adams
    Name of student #2? J. Austen
    Name of student #3? F. Bacon
    Name of student #4? A. Bennett
    Name of student #5? W. Blake
    Name of student #6? E. Blyton
    Name of student #7? L. Carroll
    Name of student #8? A. Christie
    Name of student #9? N. Coward
    Name of student #10? C. Dickens
    Name of student #11? Sir A. C. Doyle
    Name of student #12? T. Hardy
    Name of student #13? A. Hitchcock
    Name of student #14? J. Keats
    Name of student #15? P. Larkin
    Name of student #16? C. Marlowe
    Name of student #17? J. Orton
    Name of student #18? H. Pinter
    Name of student #19? W. Shakespeare
    Name of student #20? J. R. R. Tolkien

We will then be asked to grade each student in a similar way, e.g.

    Student: D. Adams
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 4

    Student: J. Austen
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Student: F. Bacon
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 1

    Student: A. Bennett
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 3

    Student: W. Blake
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 3

    Student: E. Blyton
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 4

    Student: L. Carroll
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Student: A. Christie
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 4

    Student: N. Coward
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 1

    Student: C. Dickens
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 1

    Student: Sir A. C. Doyle
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Student: T. Hardy
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 4

    Student: A. Hitchcock
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 3

    Student: J. Keats
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 1

    Student: P. Larkin
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Student: C. Marlowe
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 3

    Student: J. Orton
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 2

    Student: H. Pinter
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 1

    Student: W. Shakespeare
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 4

    Student: J. R. R. Tolkien
    Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad [1-4]? 3

The program will then correlate the grades, producing a list of subjects and which students are assigned to those subjects based upon the grading, e.g.

    ------------------------------------------------------------------------------
    Subject: Maths
    Grade: Advanced
    Students: A. Bennett, W. Blake, A. Hitchcock, C. Marlowe, J. R. R. Tolkien

    Subject: Physics
    Grade: Postgrad
    Students: D. Adams, E. Blyton, A. Christie, T. Hardy, W. Shakespeare

    Subject: History
    Grade: Intermediate
    Students: J. Austen, L. Carroll, Sir A. C. Doyle, P. Larkin, J. Orton

    Subject: English Lit.
    Grade: Intermediate
    Students: J. Austen, L. Carroll, Sir A. C. Doyle, P. Larkin, J. Orton

    Subject: Geography
    Grade: Beginner
    Students: F. Bacon, N. Coward, C. Dickens, J. Keats, H. Pinter

Step 1 - Create top-level containers

To begin, we will create containers in the nexus for our subjects and students. We'll create a container called .prose.school and underneath that .prose.school.subjects and .prose.school.students:

    %
    % Simple test of data entry via stdin which creates a hierarchy using
    % classroom subjects and student names
    %
    ._init
    func/def     [main], &[.main]

    %
    % Create top-level containers for the data
    %
    reg/load     PCTX, ![.prose]
    obj/def      P0
    class/add    P0, [psContainer]
    obj/clone    P1, P0, P2, P0
    obj/commit   [school], P0
    reg/load     P0, ![school]
    obj/commit   P0, [subjects], P1
    obj/commit   P0, [students], P2
    local/rtn

    .main
    func/rtn

We have elected to create the containers in the _init section to keep the main routine clear of preparatory code.

We changed the current context to .prose with reg/load PCTX, ![.prose] at the start. This allows for us to use relative references when creating the underlying subjects and students containers. Because our containers are all identical except for their name, we also cloned the edit buffer twice using obj/clone to create the subcontainers. We pushed the cloned edit buffers to the stack using the PUSH argument and supplied those directly to obj/commit with a PULL.

Step 2 - Collecting a number from the user

Because we need to ask the user to enter the number of subjects as well as the number of students, it will be prudent to create a subroutine that can be used for obtaining a number from the user. We will call this routine using local/jsr, so it will return using local/rtn. The prompt to display to the user will be passed along in register P0, the lowest permitted number that can be entered will be in register P1 and the highest number in P2:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .getnumber
    %
    % Get number from user (prompt in P0, base P1, top P2)
    % Result is returned in P0
    %
    reg/load     PUSH, P3, P3, P0

    .gnget
    attr/mod     P9, P11, P3
    attr/copy    P0, P9, P10
    reg/conv     P0, P0

    reg/cmp      P0, P1
    reg/jmpeq    &[.gnget], SCMP, #2
    reg/cmp      P0, P2
    reg/jmpeq    &[.gnget], SCMP, #4

    attr/mod     P9, P11, [\n]
    stack/pull   P3
    local/rtn

The subroutine expects register P9 to be pointing to the node .prose.sys.io, and registers P10 and P11 pre-loaded with attribute definitions for psStreamIn and psStreamOut respectively.

Note that any registers we use in the subroutine that weren't explicitly required as input to the routine are pushed to the stack and retrieved again before returning (in this case, just register P3). This ensures that we can safely call this subroutine without it upsetting our caller's registers.

The number provided by the user is returned in register P0. The following code can be inserted after the main label to test out this code:

    reg/load     P9, ![.prose.sys.io]
    attr/load    P10, [psStreamIn], P11, [psStreamOut]
    reg/load     P0, [Enter a number between 1 and 10 ? ], P1, #1, P2, #10
    local/jsr    &[.getnumber]
    reg/dump     P0

Remove this test code again before continuing with the tutorial.

Step 3 - Stripping newline characters from input

If we're to use all the input we're given from psStreamIn, we have to handle newline characters which will terminate the byte string. For this, we'll write a subroutine that strips newline characters (ASCII 10) from the end of a string. It will edit the string passed in via register P0:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .stripnl
    %
    % Remove newline from end of text in P0
    %
    stack/push   A, P1, P2
    reg/load     P1, (P0)
    op/decr      P1
    reg/load     P2, (P0, P1)
    reg/jmpneq   &[.stripnl2], P2, #0x0a000000
    reg/save     P0, (P1)

    .stripnl2
    stack/pull   P2, P1, A
    local/rtn

This routine works by reading the last byte with reg/load in indirect addressing mode, and if it is ASCII 10 (0x0a) then modifies the string length so that it is one character shorter. As with the getnumber subroutine, because we'll be calling this using local/jsr, we need to save the registers we're going to use to the stack and restore them before returning, to avoid upsetting the caller's registers.

Step 4 - Replacing dots with underscores

In version 0.5.x, the PROSE engine did not easily accept dots in object names, because a dot is the object delimiter in pathnames. This was rectified in version 0.6.x in two ways. Firstly dots in object references may be prefixed with a backslash character to avoid special interpretation. Secondly the obj/child command can be used for referencing a node by its name precluding any path component. However, even though this is no longer necessary, we shall replace any dots in the user input for underscores before using it as an object name. This will also require a subroutine, as follows:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .stripdots
    %
    % Remove dots from text in P0
    %
    stack/push   A, P1, P2, P3, P4

    %
    % Read length of text string and process 4 bytes at a time
    %
    reg/load     P1, (P0)
    reg/load     A, #0

    .strip_loop
    reg/load     P2, (P0, A)
    op/and       P3, P2, #0xff000000
    reg/jmpneq   &[.strip2], P3, #0x2e000000
    op/and       P2, P2, #0x00ffffff
    op/or        P2, P2, #0x5f000000
    reg/load     P4, #1

    .strip2
    op/and       P3, P2, #0x00ff0000
    reg/jmpneq   &[.strip3], P3, #0x002e0000
    op/and       P2, P2, #0xff00ffff
    op/or        P2, P2, #0x005f0000
    reg/load     P4, #1

    .strip3
    op/and       P3, P2, #0x0000ff00
    reg/jmpneq   &[.strip4], P3, #0x00002e00
    op/and       P2, P2, #0xffff00ff
    op/or        P2, P2, #0x00005f00
    reg/load     P4, #1

    .strip4
    op/and       P3, P2, #0x000000ff
    reg/jmpneq   &[.strip5], P3, #0x0000002e
    op/and       P2, P2, #0xffffff00
    op/or        P2, P2, #0x0000005f
    reg/load     P4, #1

    .strip5
    reg/jmpneq   &[.strip_next], P4, #1

    %
    % Save new 4-byte sequence back over original text (replacing . for _)
    %
    reg/save     P0, (P2, A)

    .strip_next
    opa/add      #4
    reg/cmp      A, P1
    reg/jmpeq    &[.strip_loop], SCMP, #2

    stack/pull   P4, P3, P2, P1, A
    local/rtn

Because reg/load in indirect addressing mode loads 32-bit sequences from a string, we need to test each octet for the underscore character. We save back any changed 32-bit sequences using reg/save. We know that we changed it, because we set register P4 to 1. We use the accumulator A as our loop counter, looping until we have reached the end of the text string. The length of the text string was pre-loaded into P1.

Step 5 - Collecting subjects

Now all of the supporting subroutines have been written, we can turn our attention to the functions. We're going to create one function to obtain the subjects, another to obtain the students, another to collect grades, and then one final function to collate and display the results.

The functions will need to be defined in the _init section and then called in succession using func/call in the main section.

The first function we'll write will be called getsubjects. The code label can be the same or different - in this case we'll call it .func_subjects to make it obvious this code is for a function called using func/call rather than a subroutine that we call with local/jsr.

Define the function in _init as follows:

    func/def     [getsubjects], &[.func_subjects]

Call the function in main like this:

    .main
    func/call     NULL, [getsubjects]

The function itself can now be written. First we need to obtain the number of subjects from the user, using our getnumber subroutine written earlier:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .func_subjects
    %
    % Preparation
    %
    reg/load     PCTX, ![.prose.school.subjects], P9, ![.prose.sys.io]
    attr/load    P10, [psStreamIn], P11, [psStreamOut], P13, [description]
    class/load   P12, [psContainer]

    %
    % Obtain number of subjects from user
    %
    reg/load     P0, [# of subjects \[1-10\]? ], P1, #1, P2, #10
    local/jsr    &[.getnumber]

Then we need to prompt for each subject name, stripping newlines and replacing underscores for dots using our stripnl and stripdots subroutines:

    reg/load     A, #1, P1, P0
    op/incr      P1

    .sbloop
    %
    % Get each subject name
    %
    reg/copy     P0, [Name of subject #], A, [? ]
    attr/mod     P9, P11, P0
    attr/copy    P0, P9, P10

    %
    % Get rid of newline character at end of input
    %
    local/jsr    &[.stripnl]

    %
    % Make sure there are no dots in the name (keep copy of original)
    %
    reg/copy     P3, P0
    local/jsr    &[.stripdots]

    %
    % Create a container for this subject
    %
    obj/def      P7
    class/add    P7, P12
    attr/add     P7, P13, P3
    obj/commit   P0, P7
    reg/clr      P0

    op/incr
    reg/jmpneq   &[.sbloop], A, P1

We also create the subject containers in the above code, having previously set PCTX to .prose.school.subjects. Notice that we copy the original P0 register using reg/copy and push to the stack before replacing dots for underscores with stripdots. This way we can use the original subject name in the description attribute used by attr/add, and the modified subject name in the obj/commit.

Then we need to prepare to grade the subjects. This requires a new function called getgrades which we'll define in a moment. Before then, we need to consider how this function will know whether to grade subjects or students. We could do this by passing an argument to the function, but that requires variables and we haven't reached that tutorial yet. So we will flag our intention by setting a description attribute on the .prose.school object we created earlier. We'll set the description to 0 to indicate that we wish to grade the subjects, or 1 to indicate that we wish to grade students:

    %
    % Set flag for getgrades() function to indicate that we are obtaining
    % grades for the classroom subjects
    %
    attr/add     ![.prose.school], [description], [0]
    func/call    NULL, [getgrades]
    func/rtn

Step 6 - Grading the subjects

Because both subjects and students will need grading in the same way, we'll write a function that processes the objects in one or other branch of the nexus, either .prose.school.subjects or .prose.school.students depending on whether the description attribute on the object .prose.school is set to 0 or 1.

So let's define the getgrades function to point to a code section we'll call func_grades. This definition must go in the _init section:

    func/def     [getgrades], &[.func_grades]

The first step inside the function is to set the context root (PCTX) to point to the correct branch of the nexus. We'll also set register P8 to describe the type of data:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .func_grades
    %
    % Preparation
    %
    reg/load     PCTX, ![.prose.school], P9, ![.prose.sys.io]
    attr/load    P10, [psStreamIn], P11, [psStreamOut], P12, [description]

    %
    % Read flag to determine which branch we're grading
    %
    attr/test    PCTX, [description], [0]
    reg/jmpeq    &[.grade1], SCMP, #1

    %
    % We're processing the students
    %
    reg/load     PCTX, ![students]
    reg/load     P8, [Student: ]
    local/jmp    &[.grade2]

    %
    % We're processing the subjects
    %
    .grade1
    reg/load     PCTX, ![subjects]
    reg/load     P8, [Subject: ]

    .grade2

Next we need to process each node on the selected branch, and obtain a grade for it:

    %
    % Iterate each node on the branch
    %
    reg/load     P0, (PCTX)
    attr/mod     P9, P11, [\n]

    .gdloop
    reg/load     P1, (P0)
    reg/jmpeq    &[.gdexit], P1, NULL
    attr/copy    P2, P1, P12
    reg/copy     P2, P8, P2, [\n]
    attr/mod     P9, P11, P2

    %
    % Obtain a grade for this subject or student
    %
    stack/push   P0, P1
    reg/load     P1, #1, P2, #4, P0,
                   [Grade 1) Beginner, 2) Intermediate, 3) Advanced, 4) Postgrad \[1-4\]? ]
    local/jsr    &[.getnumber]

Now we're going to create a jump table. Code labels in bytecode are given consecutive index numbers by the assembler. Whenever a code label is created or first referenced it is added to a table by prism and given the next index number. This code address table can be queried using reg/load in one of its indirect addressing modes, which allows us to jump to a section of code based upon a numerical index. We will set-up a different address for each grade, which will push the appropriate text to the stack:

    %
    % Force .gdset label to be given an index number now
    %
    noop         &[.gdset]

    %
    % Jump to correct code section based upon selection
    %
    reg/index    A, &[.gdjmp1]
    op/decr      P0
    opa/add      P0
    stack/pull   P1, P0
    reg/load     P2, (#0, A)
    local/jmp    P2

    .gdjmp1
    stack/push   [Beginner]
    local/jmp    &[.gdset]

    .gdjmp2
    stack/push   [Intermediate]
    local/jmp    &[.gdset]

    .gdjmp3
    stack/push   [Advanced]
    local/jmp    &[.gdset]

    .gdjmp4
    stack/push   [Postgrad]

    .gdset

We are relying on indices for gdjmp1, gdjmp2, gdjmp3 and gdjmp4 being adjacent. We can determine what index number they have been assigned by running prism -v on the binary file. However, we know that they will have consecutive index numbers as long as they are all defined next to each other. But we are referencing the gdset label for the first time too, so we must force an early reference to give it an index number first. We do this using a no operation instruction noop &[.gdset]. Although this instruction literally does nothing except discard its arguments, the assembler will create an index for gdset when it is first referenced.

The reg/index instruction is used to obtain the index number for a particular code label. We then add on the grade number we collected from the user via getnumber, and read that offset from the code address table using reg/load P2, (#0, A), where P2 is the register to save the code pointer, #0 identifies that we are looking up an index in the code address table (rather than the data segment table), and A identifies the accumulator as the register that holds the index to lookup. The result, saved to register P2 will be an address we can jump to with local/jmp.

Here is the relevant section of the output from prism -v on the bytecode file, displaying the index numbers generated for these code labels:

    $ prism -v students
    ...
    ------------------------------------------------------------
    CODE LABELS
    ...
      idx 00000f len 000005 [gdset]
      idx 000010 len 000006 [gdjmp1]
      idx 000011 len 000006 [gdjmp2]
      idx 000012 len 000006 [gdjmp3]
      idx 000013 len 000006 [gdjmp4]

Without the noop, the gdjmp1 label would have index 0x0f while the gdset label would have index 0x10.

All that remains in this function now is to add a second description attribute to the object we were grading, making it a multi-value attribute:

    %
    % Add an additional description attribute
    %
    reg/copy     P2, [Grade: ], PULL
    attr/mvadd   P1, P12, P2
    local/jmp    &[.gdloop]

    .gdexit
    func/rtn

Step 7 - Collecting student names

We are collecting student names using a function called getstudents. As discussed earlier, the code label will be a slightly different format, we'll call it func_students. We need to define this function in the _init section like this:

    func/def     [getstudents], &[.func_students]

We will also need to call this function immediately after the call to getsubjects in the main section:

    func/call    NULL, [getstudents]

The code to collect the list of student names is very similar to the code we wrote earlier for subjects:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .func_students
    %
    % Preparation
    %
    reg/load     PCTX, ![.prose.school.students], P9, ![.prose.sys.io]
    attr/load    P10, [psStreamIn], P11, [psStreamOut], P13, [description]
    class/load   P12, [psContainer]

    %
    % Obtain number of students
    %
    reg/load     P0, [# of students \[1-20\]? ], P1, #1, P2, #20
    local/jsr    &[.getnumber]

    reg/load     A, #1, P1, P0
    op/incr      P1

    .stloop
    %
    % Get each student name
    %
    reg/copy     P0, [Name of student #], A, [? ]
    attr/mod     P9, P11, P0
    attr/copy    P0, P9, P10

    %
    % Get rid of newline character at end of input
    %
    local/jsr    &[.stripnl]

    %
    % Make sure there are no dots in the name (keep copy of original)
    %
    reg/copy     P3, P0
    local/jsr    &[.stripdots]

    %
    % Create a container for this student
    %
    obj/def      P7
    class/add    P7, P12
    attr/add     P7, P13, P3
    obj/commit   P0, P7
    reg/clr      P0

    op/incr
    reg/jmpneq   &[.stloop], A, P1

Step 8 - Grading the students

Grading the students requires a call to getgrades:

    %
    % Set flag for getgrades() function to indicate that we are obtaining
    % grades for the classroom subjects
    %
    attr/mod     ![.prose.school], [description], [1]
    func/call    NULL, [getgrades]
    func/rtn

Step 9 - Checking the objects

Now would be a good time to check that the code we've written so far is creating objects as expected. The main section should currently look like this:

    .main
    func/call    NULL, [getsubjects]
    func/call    NULL, [getstudents]
    func/rtn

After the call to getstudents, add a call to the nxdump program:

    func/call    NULL, [tools.nxdump]

Assemble and execute the bytecode as follows, ensuring nxdump.pro is provided to prose as a binary file to load. Using the example from the objectives, this is the relevant section from the output of nxdump:

    $ prose students nxdump
    ...
    .prose.school:objectClass=top
    .prose.school:objectClass=psContainer
    .prose.school:pn=[school]
    .prose.school:description=[1]
    .prose.school.subjects:objectClass=top
    .prose.school.subjects:objectClass=psContainer
    .prose.school.subjects:pn=[subjects]
    .prose.school.subjects.Maths:objectClass=top
    .prose.school.subjects.Maths:objectClass=psContainer
    .prose.school.subjects.Maths:pn=[Maths]
    .prose.school.subjects.Maths:description=[Maths]
    .prose.school.subjects.Maths:description=[Grade: Advanced]
    .prose.school.subjects.Physics:objectClass=top
    .prose.school.subjects.Physics:objectClass=psContainer
    .prose.school.subjects.Physics:pn=[Physics]
    .prose.school.subjects.Physics:description=[Physics]
    .prose.school.subjects.Physics:description=[Grade: Postgrad]
    .prose.school.subjects.History:objectClass=top
    .prose.school.subjects.History:objectClass=psContainer
    .prose.school.subjects.History:pn=[History]
    .prose.school.subjects.History:description=[History]
    .prose.school.subjects.History:description=[Grade: Intermediate]
    .prose.school.subjects.English Lit_:objectClass=top
    .prose.school.subjects.English Lit_:objectClass=psContainer
    .prose.school.subjects.English Lit_:pn=[English Lit_]
    .prose.school.subjects.English Lit_:description=[English Lit.]
    .prose.school.subjects.English Lit_:description=[Grade: Intermediate]
    .prose.school.subjects.Geography:objectClass=top
    .prose.school.subjects.Geography:objectClass=psContainer
    .prose.school.subjects.Geography:pn=[Geography]
    .prose.school.subjects.Geography:description=[Geography]
    .prose.school.subjects.Geography:description=[Grade: Beginner]
    .prose.school.students:objectClass=top
    .prose.school.students:objectClass=psContainer
    .prose.school.students:pn=[students]
    .prose.school.students.D_ Adams:objectClass=top
    .prose.school.students.D_ Adams:objectClass=psContainer
    .prose.school.students.D_ Adams:pn=[D_ Adams]
    .prose.school.students.D_ Adams:description=[D. Adams]
    .prose.school.students.D_ Adams:description=[Grade: Postgrad]
    .prose.school.students.J_ Austen:objectClass=top
    .prose.school.students.J_ Austen:objectClass=psContainer
    .prose.school.students.J_ Austen:pn=[J_ Austen]
    .prose.school.students.J_ Austen:description=[J. Austen]
    .prose.school.students.J_ Austen:description=[Grade: Intermediate]
    .prose.school.students.F_ Bacon:objectClass=top
    .prose.school.students.F_ Bacon:objectClass=psContainer
    .prose.school.students.F_ Bacon:pn=[F_ Bacon]
    .prose.school.students.F_ Bacon:description=[F. Bacon]
    .prose.school.students.F_ Bacon:description=[Grade: Beginner]
    .prose.school.students.A_ Bennett:objectClass=top
    .prose.school.students.A_ Bennett:objectClass=psContainer
    .prose.school.students.A_ Bennett:pn=[A_ Bennett]
    .prose.school.students.A_ Bennett:description=[A. Bennett]
    .prose.school.students.A_ Bennett:description=[Grade: Advanced]
    .prose.school.students.W_ Blake:objectClass=top
    .prose.school.students.W_ Blake:objectClass=psContainer
    .prose.school.students.W_ Blake:pn=[W_ Blake]
    .prose.school.students.W_ Blake:description=[W. Blake]
    .prose.school.students.W_ Blake:description=[Grade: Advanced]
    .prose.school.students.E_ Blyton:objectClass=top
    .prose.school.students.E_ Blyton:objectClass=psContainer
    .prose.school.students.E_ Blyton:pn=[E_ Blyton]
    .prose.school.students.E_ Blyton:description=[E. Blyton]
    .prose.school.students.E_ Blyton:description=[Grade: Postgrad]
    .prose.school.students.L_ Carroll:objectClass=top
    .prose.school.students.L_ Carroll:objectClass=psContainer
    .prose.school.students.L_ Carroll:pn=[L_ Carroll]
    .prose.school.students.L_ Carroll:description=[L. Carroll]
    .prose.school.students.L_ Carroll:description=[Grade: Intermediate]
    .prose.school.students.A_ Christie:objectClass=top
    .prose.school.students.A_ Christie:objectClass=psContainer
    .prose.school.students.A_ Christie:pn=[A_ Christie]
    .prose.school.students.A_ Christie:description=[A. Christie]
    .prose.school.students.A_ Christie:description=[Grade: Postgrad]
    .prose.school.students.N_ Coward:objectClass=top
    .prose.school.students.N_ Coward:objectClass=psContainer
    .prose.school.students.N_ Coward:pn=[N_ Coward]
    .prose.school.students.N_ Coward:description=[N. Coward]
    .prose.school.students.N_ Coward:description=[Grade: Beginner]
    .prose.school.students.C_ Dickens:objectClass=top
    .prose.school.students.C_ Dickens:objectClass=psContainer
    .prose.school.students.C_ Dickens:pn=[C_ Dickens]
    .prose.school.students.C_ Dickens:description=[C. Dickens]
    .prose.school.students.C_ Dickens:description=[Grade: Beginner]
    .prose.school.students.Sir A_ C_ Doyle:objectClass=top
    .prose.school.students.Sir A_ C_ Doyle:objectClass=psContainer
    .prose.school.students.Sir A_ C_ Doyle:pn=[Sir A_ C_ Doyle]
    .prose.school.students.Sir A_ C_ Doyle:description=[Sir A. C. Doyle]
    .prose.school.students.Sir A_ C_ Doyle:description=[Grade: Intermediate]
    .prose.school.students.T_ Hardy:objectClass=top
    .prose.school.students.T_ Hardy:objectClass=psContainer
    .prose.school.students.T_ Hardy:pn=[T_ Hardy]
    .prose.school.students.T_ Hardy:description=[T. Hardy]
    .prose.school.students.T_ Hardy:description=[Grade: Postgrad]
    .prose.school.students.A_ Hitchcock:objectClass=top
    .prose.school.students.A_ Hitchcock:objectClass=psContainer
    .prose.school.students.A_ Hitchcock:pn=[A_ Hitchcock]
    .prose.school.students.A_ Hitchcock:description=[A. Hitchcock]
    .prose.school.students.A_ Hitchcock:description=[Grade: Advanced]
    .prose.school.students.J_ Keats:objectClass=top
    .prose.school.students.J_ Keats:objectClass=psContainer
    .prose.school.students.J_ Keats:pn=[J_ Keats]
    .prose.school.students.J_ Keats:description=[J. Keats]
    .prose.school.students.J_ Keats:description=[Grade: Beginner]
    .prose.school.students.P_ Larkin:objectClass=top
    .prose.school.students.P_ Larkin:objectClass=psContainer
    .prose.school.students.P_ Larkin:pn=[P_ Larkin]
    .prose.school.students.P_ Larkin:description=[P. Larkin]
    .prose.school.students.P_ Larkin:description=[Grade: Intermediate]
    .prose.school.students.C_ Marlowe:objectClass=top
    .prose.school.students.C_ Marlowe:objectClass=psContainer
    .prose.school.students.C_ Marlowe:pn=[C_ Marlowe]
    .prose.school.students.C_ Marlowe:description=[C. Marlowe]
    .prose.school.students.C_ Marlowe:description=[Grade: Advanced]
    .prose.school.students.J_ Orton:objectClass=top
    .prose.school.students.J_ Orton:objectClass=psContainer
    .prose.school.students.J_ Orton:pn=[J_ Orton]
    .prose.school.students.J_ Orton:description=[J. Orton]
    .prose.school.students.J_ Orton:description=[Grade: Intermediate]
    .prose.school.students.H_ Pinter:objectClass=top
    .prose.school.students.H_ Pinter:objectClass=psContainer
    .prose.school.students.H_ Pinter:pn=[H_ Pinter]
    .prose.school.students.H_ Pinter:description=[H. Pinter]
    .prose.school.students.H_ Pinter:description=[Grade: Beginner]
    .prose.school.students.W_ Shakespeare:objectClass=top
    .prose.school.students.W_ Shakespeare:objectClass=psContainer
    .prose.school.students.W_ Shakespeare:pn=[W_ Shakespeare]
    .prose.school.students.W_ Shakespeare:description=[W. Shakespeare]
    .prose.school.students.W_ Shakespeare:description=[Grade: Postgrad]
    .prose.school.students.J_ R_ R_ Tolkien:objectClass=top
    .prose.school.students.J_ R_ R_ Tolkien:objectClass=psContainer
    .prose.school.students.J_ R_ R_ Tolkien:pn=[J_ R_ R_ Tolkien]
    .prose.school.students.J_ R_ R_ Tolkien:description=[J. R. R. Tolkien]
    .prose.school.students.J_ R_ R_ Tolkien:description=[Grade: Advanced]

From this output you can see that the objects are being created correctly. You can now remove the code that calls tools.nxdump and continue writing the program.

Step 10 - Correlating the grades

To associate students with subjects based upon the grading of each requires two loops. An outer loop that processes each subject and an inner loop that searches for students with similar grades.

Before we begin writing the display routine, let's define the function in _init. Here are all the function definitions that should appear in _init:

    ._init
    func/def     [main], &[.main]
    func/def     [getsubjects], &[.func_subjects]
    func/def     [getstudents], &[.func_students]
    func/def     [getgrades], &[.func_grades]
    func/def     [display], &[.func_display]

We will call the display function after getstudents in the main section. Here is the complete main section:

    .main
    func/call    NULL, [getsubjects]
    func/call    NULL, [getstudents]
    func/call    NULL, [display]
    func/rtn

We're going to write a support subroutine that reads two values from the description attribute assigned to an object. We know that we have added two description values to each subject and student. The first value is the display name (before dots were converted to underscore characters). The second value is the grade. However, as we can't guarantee the order that we will read these attributes, we will identify the grade by virtue of it beginning with the text "Grade: ". (A better version of this program would actually use a different attribute for the grade, rather than using multi-value attributes).

The support routine will be called getdesc. We will assume that the object to be processed is passed in via register P1. The display name will be returned in register P3 and the grade in P4. It is expected that register P12 will contain a pointer to the description attribute definition. For the purpose of this subroutine we will assume that we can modify registers P2 through to P5 safely without affecting the caller. Here is the code:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .getdesc
    %
    % Read both values from description (one is display name, one is the grade)
    % Will return display name in P3 and description in P4
    %
    attr/copy    P2, (P1, P12)
    attr/copy    P3, (P2)
    attr/copy    P4, (P2)
    reg/clr      P2

    reg/scan     P2, P3, [Grade: ]
    reg/jmpneq   &[.gdret], P2, #0

    %
    % Swap P3 and P4
    %
    reg/move     P5, P3, P3, P4, P4, P5

    .gdret
    local/rtn

Now we come to writing the display function itself:

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    .func_display
    %
    % Preparation
    %
    reg/load     PCTX, ![.prose.school], P9, ![.prose.sys.io]
    attr/load    P11, [psStreamOut], P12, [description]

    %
    % Process each subject, and then list the students at the same grade
    %
    attr/mod     P9, P11, [------------------------------------------------------------------------------\n]
    reg/load     P0, (![subjects])

    .dloop
    reg/load     P1, (P0)
    reg/jmpeq    &[.dexit], P1, NULL

    %
    % Read both values from description (one is display name, one is the grade)
    %
    local/jsr    &[.getdesc]
    reg/copy     P3, [Subject: ], P3, [\n]
    attr/mod     P9, P11, P3
    reg/copy     P5, P4, [\n]
    attr/mod     P9, P11, P5

    %
    % Display all students at this grade
    %
    reg/load     P6, (![students])
    reg/load     A, #0
    reg/move     P8, P4

    .dloop2
    reg/load     P1, (P6)
    reg/jmpeq    &[.dcont], P1, NULL

    %
    % Read both values from description (one is display name, one is the grade)
    %
    local/jsr    &[.getdesc]
    reg/jmpneq   &[.dnext], P4, P8

    %
    % Display list of students on one line (comma-separated)
    %
    reg/jmpneq   &[.dcomma], A, #0
    reg/copy     P3, [Students: ], P3
    local/jmp    &[.dstu]

    .dcomma
    reg/copy     P3, [, ], P3

    .dstu
    attr/mod     P9, P11, P3
    op/incr

    .dnext
    reg/clr      P3, P4
    local/jmp    &[.dloop2]

    .dcont
    reg/clr      P3, P4, P6, P8
    attr/mod     P9, P11, [\n\n]
    local/jmp    &[.dloop]

    .dexit
    func/rtn

Notice how we rely on the fact that reg/copy can safely write a result over the top of an existing byte string, as it releases the memory from the underlying string first. We use reg/move to move a byte string pointer from one register to another (you cannot copy a string pointer using reg/load as this would create a duplicate pointer). You will also see how we are careful to clean up byte string registers with reg/clr at the end of both the inner and outer loops to avoid memory leaks. If you are unsure which registers now contain byte strings, running reg/dump with no arguments will list the data types stored in all registers. Any register containing the type PSUNIT_TYPE_STRING that you no longer need should be cleaned with reg/clr.

Resources from this tutorial

Further reading

See the other tutorials available for the PROSE Assembly Language on the tutorials index page.

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.