Collecting user input
Contents
- 1 PAL Tutorial - Collecting user input
- 1.1 Objectives
- 1.2 Step 1 - Create top-level containers
- 1.3 Step 2 - Collecting a number from the user
- 1.4 Step 3 - Stripping newline characters from input
- 1.5 Step 4 - Replacing dots with underscores
- 1.6 Step 5 - Collecting subjects
- 1.7 Step 6 - Grading the subjects
- 1.8 Step 7 - Collecting student names
- 1.9 Step 8 - Grading the students
- 1.10 Step 9 - Checking the objects
- 1.11 Step 10 - Correlating the grades
- 1.12 Resources from this tutorial
- 1.13 Further reading
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.
BROKEN: Actually this example program is broken in PROSE 0.9.0 due to a bug that has yet to be rectified. If you would like to see this program working, please use PROSE 0.8.1.
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 PUSH, P0, PUSH, P0 obj/commit [school], P0 reg/load P0, ![school] obj/commit P0, [subjects], PULL obj/commit P0, [students], PULL 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 PUSH, P0 local/jsr &[.stripdots] % % Create a container for this subject % obj/def P7 class/add P7, P12 attr/add P7, P13, PULL 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 PUSH, P0 local/jsr &[.stripdots] % % Create a container for this student % obj/def P7 class/add P7, P12 attr/add P7, P13, PULL 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.