Virtue is a mathematical processor shaped after ideas from APL, mathematics and other language systems. Its final aim is to implement the "knowledge" of mathematics and other related areas in a simple and consistent framework.
It is a vector/array processor. All presently implemented operations are scalar. This means that no matrix multiplication/division or similar is implemented (the so called inner products). However, as there are complex numbers, quaternions and octonions (not implemented fully yet, so they can presently not be generated or inputed), all matrix operations are possible using those multidimensional numbers instead of vectors/arrays of singledimensional numbers. The inner product (matrix product) operations will be implemented in next development stages.
Scalar operations work on two scalars, scalar and vector/array or two vectors/arrays of the same rank.
Virtue is a Reversed Polish Notation (RPN)
processor with a stack. Operations are in principle niladic (no
arguments), monadic (one argument, the Top Of Stack - TOS) or dyadic
(two arguments, TOS-1 op TOS), although defined functions can be
n-adic (defined by the function definition). However n-adic functions
can not be used as scalar operators, even with "EACH
".
The 'workspace' is the whole environment of the
Virtue. It contains all the data on the stack, as well as general
variables like QuadCT, QuadIO (see further in text - presently they
are not changeable), the "ASSIGN
"ed
or "SET
"
variables as well as FUNCTION
s.
The sentence inputed at the Virtue prompt is not regarded as part of
the workspace, and is active until it's execution is finished. After
that moment it is discarded.
Presently there are no 'workspace save' or 'workspace load' operators implemented, so pleas keep all your valuable texts (programmes) for Virtue in a separate windowed text file, and just copy-paste them into Virtue.
If the sentence inputed requires very long
execution, and you do not like to wait until it finishes, it is
always breakable by using the control-c (^c) break character, as
common on UNIX. The state of the top of stack is indeterminate. It
could contain, for example, an array with half finished results. The
data on stack below the top of stack is in most cases preserved, but
it is certain only that the data below that point on stack is
unchanged. When FUNCTION
s
are executed the top elements of the stack shall be examined
thoroughly before assuming their correctness.
There are basically three data types:
ASCII
characters.
Characters are not compatible with any other type (LOGIC or NUMERIC).
However, in LOGIC operations a character will take a value of 0 if it
is a ' ' (space) or a control character, and 1 if it is printable. A
space (' ') is taken as a LOGIC 0, as it is actually an empty
character. Example of using ASCII, LOGIC and NUMERIC: The number of
words in a sentence is (usually, if no double spaces) 'one more than
the number of spaces', in Virtue: "'Hello,
how are you today?' ' ' EQUAL ADD REDUCE 1 ADD.
"
giving 5.
LOGIC
LOGIC values are
fuzzy logic
values between FALSE and TRUE, represented as numbers in the range
[0, 1]. The results of LOGIC operations are normal numbers, i.e. they
can be intermixed with any NUMERIC type. The basic truth values can
be input as "#FALSE
",
being scalar 0, and "#TRUE
",
being scalar 1. Logic operations (AND, OR, NOT etc.) are calculated
using £ukasiewicz
logic t-norm.
It is important to note that to be compatible with t-norm values
(i.e. inside the range [0..1]), numerical values are evaluated by
assuming probabilities, i.e. the LOGIC value of a negative number is
0 (certainly not),
the LOGIC value of a positive number > 1 is 1 (certainly
yes), and values between [0,1] are
left as is (probably).
These conversion operations are done only during execution of logic
operators (i.e. AND, OR, NOT etc.). Special attention is given to
COMPLEX, QUATERNION and OCTONION numbers, where the 'csgn' function
(see: Signum)
is used to define COMPLEX, QUATERNION and OCTONION numbers < 0.
Generically 'sgn' is defined as z/|z| for any number (real, complex, quaternion, octonion etc.), 'csgn' is defined as z/sqrt(z2).
NUMERIC
numeric
values come internally in several forms, but they
are all compatible and follow normal mathematical rules. Therefore,
i.e. "-1 0.5 POWER
DUP MULTIPLY
"
[sqr(sqrt(-1))] is -1. Numeric values are internally represented by:
INTEGER (range 'long int' less one bit: -1073741823..1073741823). Bigger values become REAL automatically, REAL integers may become INTEGERS if in the given range, and are always printed that way.
REAL (range 'double' underflow 2.2e-308,
overflow 1.8e308). The precision is 53 bit mantissa (if using a
computer with IEEE 754 mathematics), giving the ulp (the difference
between consecutive positive REALs) between 1.1e-16 and 2.2e-16. It
is important to note that many operations (like sin() or cos()) give
results of the precision of maximum 1 ulp or less! This is a
consequence of using a digital computer with IEEE 754
representation. When necessary (as for example in "-1
0.5 POWER DUP MULTIPLY
"),
the results of internal |sin()| or |cos()| smaller than 1.0e-15 will
be forced to 0. Consequently it can be taken that the precision of
Virtue using 'double real' is actually 1.0e-15 only (unfortunately).
COMPLEX A two-dimensional number, represented internally by two REALs.
QUATERNION A three- or four-dimensional number, represented internally by four REALs.
OCTONION A five-, six-, seven- or eight-dimensional number, represented internally by eight REALS.
There are two
special numbers which may be inputed and generated: 'inf' and 'nan'.
'inf' is infinity, and may be signed (inf, -inf). For example "1
0 DIVIDE
" gives 'inf',
"-1 0 DIVIDE
"
gives '-inf', "1 1
0 DIVIDE DIVIDE
" gives
0. 'nan' is Not a Number, i.e. when there is no mathematical
solution, as for example in "0
0 DIVIDE
", which gives
'nan'. It is produced on all operation singularity points. It is
important to note that neither '±inf' nor 'nan' will disallow
further processing, i.e. there is no 'domain error' in Virtue. This
is mathematically correct, as those numbers can occur only on
singularities. Remark: as anything taken 0 times, i.e. multiplied by
0 is 0, be the anything a number or a thing, both '±inf' and
'nan' multiplied by 0 give the result 0. This way booleans can clean
out 'nan's. "0 0
DIVIDE 0 MULTIPLY
" is 0.
Furthermore 'nan' and 'inf' can be normally inputed at any point as a
number, generating a 'real' number, e.g: "nan
"
(or: "NaN
"),
"inf
"
(or: "Inf
"),
"-inf
"
(or: "-Inf
"),
"naninan
"(or:
"NaNiNaN
"
etc.), "infi-inf
"(or:
"Infi-Inf
"
etc.), "naninanjnanknan
"
or even "-infi2.5jnank2
".
To be able to process 'nan's and 'inf's, the comparison operations
EQUAL and NOTEQUAL will compare 'nan's [i.e. "naninan
naninan EQUAL
" gives 1
(true)] and 'inf's, so that e.g. "1inanj-inf
1inanj-inf EQUAL
" is 1
(true). Beware that, naturally, '"Inf
"
is not equal to "-Inf
".
There are three additional stack data types:
ADDRESS
The ADDRESS is used
by the "ASSIGN
"
and "SET
"
operators, and can be used also by the "SHAPE
".
FUNCTION
A FUNCTION is a sentence starting with "FUNCTION" and ending with "FUNCTIONEND", shorthanded as ";". It can be "ASSIGN"ed or "SET" into a variable, and on TOS it can be "EXECUTE"d or "EACH"ed.
LABEL
A LABEL is an internal address inside one sentence, which can be used by the "JUMP" operator, and is manipulated in a similar way as a number. The label represents the Programme Counter (PC) position inside a sentence. The sentences are tokenized, and each token (number, address, operator, function etc.) takes one programme place. Therefore LABELs are actually general integers with a special tag.
There are two types of vectors/arrays - simple
and COMPLEXED. A simple vector/array consists of the same type of
data elements, i.e. "'Hello'
",
or "(1 2 3)
",
or "(1i2 2i3 3i4)
".
A COMPLEXED vector/array consists of two or more data types, for
example "(1 2.3
2)
", or "'The
value is' 2 CATENATE
".
Beware that "(1 2
3)
" and "(1.0
2.0 3.0)
" are simple
vectors, the first one consisting of INTEGERs, the second of REALs.
However "(1 2.0 3)
"
is COMPLEXED, the first and third elements are INTEGERs, the second
REAL. Virtue processes COMPLEXED vectors/arrays exactly the same as
simple, as long as the types are compatible for the operation [i.e.
"(1 2.2 3i4j5k7.2)
1 ADD
" gives (2 3.2
4i4j5k7.2), however "(1
2.2) 'a' CATENATE 1 ADD
"
gives the error '17 - dyadic function arguments are not
conformable.']
The only actual difference with operation compatible COMPLEXED vectors/arrays, as opposed to simple ones, is that the processing of simple vectors/arrays is slightly faster than that of the COMPLEXED ones.
Vectors/arrays can also be nested, i.e.
elements of a vector can contain other vectors/arrays of any shape,
which can contain vectors/arrays of any shape etc. ad infinitum
(actually till the memory is exhausted). There are several operators
which produce nested vectors, the principal one is "ENCLOSE
".
"Virtue
-h
" gives a short
message about available options and exits. "Virtue
-v
" prints the version
information and exits.
Virtue can be used in interactive and
non-interactive mode. Interactive mode outputs several messages and a
prompt after each executed sentence (actually after printing out the
Top Of Stack value). Non-interactive mode is provided for using
Virtue as a pipeline (see Appendix 2 for more details). The
non-interactive mode is invoked by the startup flag "-q"
(for quiet), i.e. "Virtue
-q
".
Several startup flags deal with performance measurements.
The timing startup flag is "-t"
("Virtue -t
"),
and will give execution times for each Virtue sentence. These
execution times are real time execution times, i.e. on a busy
processor they will give longer times [they are clock times, not
Central Processing Unit (CPU) times].
To measure absolute and relative processor performance for each given Virtue sentence, there are the "-p" (performance) and "-pp" (processor performance) flags.
For performance measurement two different type
of Virtue instructions are counted: the 'operators' and the
'operations'. 'Operators' are all Virtue words. 'Operations' are the
individual operations done by the operators on individual
vector/array members. For example, "1
1 ADD.
" executes 3
operators (the scalar 1, the scalar 1, the scalar operator ADD), and
only 1 operation (the addition of 1 and 1). "(1
2 3) 1 ADD.
" executes
also 3 operators [the vector (1 2 3), the scalar 1, the scalar
operator ADD
],
but it executes 3 operations (1+1, 2+1, 3+1).
"Virtue
-p
" will print the
number of operators and operations executed, which may be used for
algorithm tuning, and the Virtue performance on a given computer in
operators/sec and operations/sec. Beware that the same constraints as
for the "-t" option are valid for all performance
measurements.
The "-pp" startup flag must be
followed by a space and the computer processor speed in MHz. For
example, on Grgur, which is a 20MHz Sun-3, Virtue would be started
with "Virtue -pp
20
". On a 2.8GHz
processor, you would write "Virtue
-pp 2800
". This flag
enables the relative processor (actually computer) performance
measurement, printing the amount of Virtue instructions (operators
plus operations) per MHz of processor clock. When testing on several
different computer architectures the same Virtue text it can be seen
that certain Virtue texts are relatively faster or slower executed on
different processor/hardware platforms.
When finished with the work with Virtue, the
operator which stops the Virtue system is "OFF
",
but it has to be executed as any other operator. This means that it
is also possible to put the OFF
operator into a function, or in a middle of a
sentence. In the moment the interpreter executes the operator OFF
,
it will immediately exit to the enveloping operating system. It is
also possible to introduce this operator into a function, with
conditional execution, for example. Do not forget to finish the
sentence with the OFF operator, i.e to exit type: "OFF.
"
All sentences finish with "END
",
which can be written as ".
".
After receiving the end of sentence, Virtue executes the given
sentence and prints out the Top Of Stack. Further sentences operate
in the same way as if they were not separate, i.e. the stack is
unchanged between sentences, so the following expressions are
equivalent:
"1
.
" !! Warning: the
lexical analyser will interpret "1.
"
as a real number 1.0, so the "." must be separated with a
space after a number. However "1..
"
is a proper expression for a sentence containing
only the real number 1.0!
"PITIMES.
"
"@_pi.
"
"ASSIGN.
"
== (is equivalent to) "1
PITIMES @_pi ASSIGN.
"
The first row of four sentences will print out intermediate results. The second row of one sentence will print out only the final result, i.e the Top Of Stack.
Comments are written in line between the
double-quotes (") and can span from empty comment (""
)
to any number of lines. They can be put between any words (e.g.: 1
2 "with the next operator we divide 1 by 2" DIVIDE
),
but, naturally, not inside a word (so, e.g.: 1
2 D"ivi"IVIDE
is
not recognized as DIVIDE
,
and the word will be ignored. It is important to note that the
FUNCTION
word has two forms, and no comments may be
inserted between the sub-words of these forms. They are "FUNCTION
",
"ARGS FUNCTION
".
See FUNCTION
operator further in the text.
The only sentences which can not be divided
between lines in the abovementioned fashion are sentences which
contain LABELs and "JUMP
"s.
The LABEL and the appropriate "JUMP
"s
have to be all in the same sentence. The easiest way to use "JUMP
"s
is using FUNCTION
s,
which are a-priori one sentence texts.
The sentence results (TOS print at the end of
sentence execution) are given in the smallest possible format for a
number, i.e. a real number 1.0 will be printed as '1', the same as
the complex number 1i0 or the quaternion 1i0j0k0. The quaternion
1i1j1k0 will be printed (and can be inputed) as 1i1j1. As all numbers
are intercompatible and are automatically converted to the proper
result (i.e. "10i10j10
MAGNITUDE
" is a real
number 17.3205, and the square root of the integer e.g. -1 will give
a complex number: "-1
0.5 POWER
" gives 0i1).
The input for complex, quaternion and octonion
numbers is free format from left to right, i.e. "0k5
"
is 0i0j0k5 and "1j3
"
is 1i0j3, or "1j4l3o7
"
is the octonion 1i0j4k0l3m0n0o7. Complex numbers have the format aib
for a+ib (as opposed to APL, where it is ajb), quaternions aibjckd,
and octonions aibjckdlemfngoh, where a, b, c, d, e, f, g, h are
numbers in any acceptable numeric format, i.e. integers, fixed point
or exponential notation (xey). Therefore 0i-1.2e-24
is a properly formated complex number.
Vectors can be inputed as (a b c d ...), and
strings as 'abcd...'. Multidimensional arrays can not be inputed
directly, but must be reshaped (see "RESHAPE
").
Structured vectors/arrays can not be inputed
directly, but must be built up with ENCLOSE
and CATENATE
.
The vector/array index origin (QuadIO) is always 1 in the present implementation. There will be a possibility to choose the index origin as 0 or 1 in the future.
Certain operations, like LOGIC operations, comparison operations, use a comparison tolerance, i.e. they will claim that e.g. two numbers are equal if they do not differe more than QuadCT, which is presently set to 1e-13. In a later version QuadCT will be assignable by the human.
LOGIC FALSE is equivalent to the number 0, and is printed so. LOGIC TRUE is equivalent to the number 1, and is printed so. The LOGIC operations operate on all numbers, and take that any number less than or equal to 0 (for explanation on LOGIC conversions of complex numbers, etc. see above under DATA TYPES) is FALSE, any number biger than of equal to 1 is TRUE, and any number inside the open interval (0.0,1.0) (i.e inside one ulp more than 0.0 or less than 1.0) is regarded as the probability, that is the value is PROBABLE. The results of LOGIC operations are always either INTEGERs 0 (FALSE) and 1 (TRUE), or REALs from the open interval (0.0+1ulp, 1.0-1ulp). The conversions to LOGIC data is done automatically during logic operations, but there is no specific LOGIC data type, the values are always kept as numbers.
A NIL is an empty vector. It can be produced
by, for example, "0
INTERVAL
", and is the
shape of a scalar (e.g. "4
SHAPE
" gives NIL). The
NIL on the TOS is printed as a single dot ('.'). Any monadic or
dyadic scalar operation operating on a NIL (any argument or both)
gives NIL. The shape of NIL is a one-element vector with the only
element being 0 [i.e. (0)]. So:
"4
SHAPE.
" gives . [NIL]
"SHAPE.
"
of the above gives 0
"SHAPE.
"
of the above gives 1.
NIL can explicitly be inputed as "#NIL
",
producing a vector of length 0. NIL can also be regarded as
representing an empty set. It will catenate with anything, giving as
the result only what was catenated with it.
NIL is used by the JUMP
operator, and is also produced by the IF
operator. The IF
will produce NIL on TOS if the TOS-1 is #FALSE,
otherwise leaves the TOS unchanged. JUMP
will jump on any address, except 0 and NIL. On NIL
JUMP
will not not do anything, i.e. "#NIL
JUMP 1
" gives the result
1.
Virtue has four main name spaces inbuilt, and they are distinguished by the lexical preprocessor by special beginning characters. There are no reserved words in Virtue. All Virtue operators have one or more synonymous names and a special sequence of characters from the APL character set.
All operator names are all capitals. They are fixed by the implementation.
To allow a full character name space for variables and labels, they start with the following special character:
"_abcd
"
a variable name provided for data
".abcd
"
a variable name provided for functions
"%abcd
"
a label name
There are two name spaces for user variables, the human is supposed to use _ID for variables containing data and .ID for variables containing functions, but there is no restriction imposed.
Functional, variable and label data are stored
under their name. To ASSIGN
or SET
the value to a name an address is necessary. The
address in a sentence shows the interpreter where to save the data,
i.e. which name in the symbol table will hold the value (data,
function, label). The name itself in a sentence shows the interpreter
where to get the data (from which name to take it), and then put on
the TOS.
In the previous paragraphs we described the variable names. Putting the variable name in a sentence will in the moment of execution take the value from the named variable and put it on the stack.
To specify the internal position where a value can be assigned to, based on the name, and address must be specified, begining with the address character "@".
Example: "_a.
"
[if _a was never before assigned in the workspace] gives the error
'23 - value
has been passed an empty variable', i.e. can not use un-initialized
variable.
Now, if we want to put a value through the
address into a variable we shall write:
"3
@_a SET.
"
(This will
leave the stack unchanged.)
Now the first sentence "_a.
"
will give the number 3 as TOS (and print it out, as it is a
selfstanding sentence - ends with ".").
The number space has the normal numerical characters, and constants are inputed in a fashion commonly used in computers. So there is 1, -1, -1.0, 1.0, 1e10, 1e-10, up to the complexity of -1.234e-12.458 ...
Complex numbers are a sequence of two numbers represented above, with the imaginary part separated by the letter "i", as common in mathematics (and not the letter "j", as it is in APL!).
Quaternions are a sequence of 3 or 4 numbers
separated by "i", "j" and "k". They can
be inputed with in such a way that all the zeros are left out, except
the first one, i.e. "0k1
"
(the number 0i0j0k1) or "0j1
"
(the number 0i0j1k0), or "0j1k1
"
(the number 0i0j1k1).
Octonions are a sequence of 5, 6, 7 or 8
numbers separated by "i", "j", "k",
"l", "m", "n", "o", and
follow the same shorthand conventions as the quaternions described
above. "0o1
"
is the number 0i0j0k0l0m0n0o1.
As the mathematics of quaternions and octonions
necessitates a lot of processing per number dimension, the Virtue
interpreter processes differently dimensioned quaternions and
octonions with with shortened mathematical procedures. This means
that "1i2j3 2i3j4
MULTIPLY
" is executed
much faster than, for example, "1i2j3k1e-300
2i3j4e1-300
". The
interpreter will take only a pure 0.0 in the last dimension as
signifying a 3-dimensional number. The same applies specially to the
octonions, whose mathematics is extremely compute intensive.
"FUNCTION
"
A FUNCTION
is a sentence starting with "FUNCTION
"
and ending with "FUNCTIONEND
".
It can be "ASSIGN
"ed
or "SET
"
into a variable, and on TOS it can be "EXECUTE
"d,
or "EACH
"ed.
The whole function definition has to be in one (1) sentence, i.e.
between the FUNCTION
word and the ";
"
word there can be no ".
"
("END
").
This is rather obvious, as the ".
"
initiates the execution of the sentence, and a half finished FUNCTION
is nothing you can execute. A lexical analyser
error will be given if this condition is not satisfied. A FUNCTION
header has two forms. Just "FUNCTION
"
gives a pletoradic function (function with no defined number of
elements, i.e. zero or more). A pletoradic function can be used as a
pure Lambda function, it is unnamed, and has any number of arguments.
"ARGS <n>
FUNCTION
" gives an
n-adic function. The number of arguments of the function can be any,
but the result generally has to be just one (on TOS), although it is
allowed to leave any number of results on the stack, as the result of
a FUNCTION
.
However, in that case, the caller has to know it and deal with it.
FUNCTION
s
which are "EACH
"ed
must be monadic ("ARGS
1 FUNCTION
" ... ";
")
or dyadic ("ARGS 2
FUNCTION
" ... ";
"),
and have to take scalars as input and leave only one result on stack.
Although Virtue will make every effort to catch possible calling
convention irregularities for the "EACH
"
operator, generically the behaviour of Virtue if an "EACH
"ed
function does not follow this scalar convention is not defined.
Beware that a substructure in this sense is actually behaving as a
scalar. Any non-scalar result of the "EACH
"ed
function will be interpreted as a substructure, i.e. the result of
the "EACH
"
will be a (sub-)structured vector/array. Functions can be recursive.
For examples, please see Appendix 1.
"NILADIC
"
This is actually an
alias for "ARGS 0
FUNCTION
", for the
convenience in defining user functions.
"MONADIC
"
This is actually an
alias for "ARGS 1
FUNCTION
", for the
convenience in defining user functions, as well as in formating
sentences with EACH. For example "MONADIC
SHAPE; EACH
".
"DYADIC
"
This is actually an
alias for "ARGS 2
FUNCTION
", for
convenience in defining user functions, as well as in formating
sentences with short DYADIC
inlined functions.
"FUNCTIONEND
"
The semicolon (";
")
marks the end of a FUNCTION
definition, and ";
"
also returns from that function, leaving the TOS unchanged.
FUNCTIONEND
(";
")
can not be used inside a FUNCTION
,
as it is a syntactial element in Virtue, and always terminates the
function definition. Please see FUNCTION
for more. Beware: the whole FUNCTION
definition ending with FUNCTIONEND
(";
")
has to be in one (1) sentence!
"RETURN
"
The RETURN
operator behaves exactly the same as "0
JUMP
", it actually
finishes the processing of a sentence; so it is also the return from
a function. Example: "1
RETURN 2 ADD
" gives 1.
"IF_YES
",
"?Y
"
The IF_YES
is an operator (including addresses, scalars and
variables) modifier. An operator or a function will be executed if
the previous CHECK
yielded TRUE, based on its (CHECK
's)
argument. From the last CHECK
operator till the end of the sentence (or till the
next CHECK
operator) each operator modified by the IF_YES
modifier will be executed. Contrary, if the
previous CHECK
yielded FALSE, all IF_YES
modified operators will be ignored (skipped). This
is the same as the "y:" modifier in the Pilot language.
Example: "1 CHECK 2
3 ADD IF_YES SUBTRACT IF_NO
"
gives the result 5. "0
CHECK 2 3 ADD IF_YES SUBTRACT IF_NO
"
gives the result -1. For a slightly more elaborate example see IF_NO
.
Data, variables and addresses can be modified by the IF_YES
operator modifier, as well as FUNCTION
s.
A FUNCTION
will be automatically executed (as with the
EXECUTE
operator) if the CHECK
condition was TRUE, and skipped (and popped from
the stack) if FALSE. An IF_YES
operator at the benining of a sentence behaves as
a NOOP
.
Both IF_YES
and IF_NO
work in a non-fuzzy way, i.e. there is only true
or false truth. Interesting ways of using the IF_YES
and IF_NO
modifiers on variables and addresses is presenter
in the following examples:
_var1 1 LESS CHECK 1 IF_YES _var1 IF_NO.
"The result is _var1 if it is not less than one, one otherwise"
_OTHER CHECK @_this IF_NO @_that IF_YES 1234 SET.
"The number 1234 will be saved either in variable _this or in variable _that, depending on the boolean value of the variable (flag) _OTHER. -- Beware: there is, naturally, no check possible for the compatibility of IF_YES and IF_NO results, i.e. do not try something like: '_OTHER CHECK @_this IF_NO 53 IF_YES 1234 SET.', as it will work for IF_NO, but not for IF_YES!"
"IF_NO
",
"?N
"
The IF_NO
is an operator modifier. An operator, a function
or a data, address or variable load on stack will be executed if the
previous CHECK
yielded FALSE, based on its (CHECK
's)
argument. From the CHECK
operator till the end of the sentence (or till the
next CHECK
operator) each operator modified by the IF_NO
modifier will be executed. Contrary, if the
previous CHECK
yielded TRUE, all IF_NO
modified operators will be ignored (skipped). This
is the same as the "n:" modifier in the Pilot language. For
example, we are writing a programme which will, depending on the
condition that the user is a 'gambler' or a normal user print
different explanations (the value of 'gambler' shall be set, for this
example, in the variable _gambler):
_gambler CHECK
'Rolling your 6 dice I get:' WRITE IF_YES DISCARD
'A vector of 6 random numbers:' WRITE IF_NO DISCARD
6 6 RESHAPE ROLL WRITE
+REDUCE 'Your score is:' DISCARD IF_NO
'The sum of these numbers is:' DISCARD IF_YES
SWAP CATENATE WRITE LEFT IF_NO LEFT IF_NO
'Thank you for gambling.' DISCARD IF_NO WRITE IF_YES
LEFT LEFT LEFT.
Naturally, this is a very silly example. Data,
variables and addresses can not yet be modified by the IF_NO
operator modifier, but FUNCTION
s
can. A FUNCTION
will be automatically executed (as with the
EXECUTE
operator) if the CHECK
condition was FALSE, and skipped (and popped from
the stack) if TRUE. An IF_NO
operator at the benining of a sentence behaves as
a NOOP
.
Both IF_NO
and IF_YES
work in a non-fuzzy way, i.e. there is only true
or false truth.
"NOOP
"
No Operation. Nothing done, nothing changed, just a little bit of time wasted.
"OFF
",
"QUIT
",
"ENDPROCESS
"
Quits the Virtue
interpreter. Everything being saved in the
memory and not on paper or copy-pasted to a text editor is lost once
and forewer. What a pitty. But you have to have a way of stopping to
work once in a while. "OFF
"
is a regular operator, which will work inside any function or
sentence. The last TOS before the OFF operator is printed out before
quitting. For example: "FUNCTION
1 2 + 3 + OFF 4 5 + ; @_a SET _a EXECUTE .
"
will print out:
--- Good bye. (999) ---
6
end exit into the operating system.
All operations operate on the Top Of Stack (TOS) and leave the result on TOS.
"WRITE
",
"TOS
"
Print the Top Of Stack. The stack pointer stays unchanged.
"PRINT
",
"QUADOUT
"
Print out the Top Of Stack. This operation DISCARDS the TOS data.
"CEILING
"
The result is TOS
rounded upwards towards the nearest 'integer' ("-1.2
CEILING
" is -1, "1.2
CEILING
" is 2, "3.4i2.5
CEILING
" is 3i3).
"CONJUGATE
"
Mathematical
conjugation. For singledimensional numbers
[i.e. reals (including integers)] it is the same as the argument.
"1.1 CONJUGATE
"
is 1.1 Complex: "1i1
CONJUGATE
" is 1i-1
Quaternion: "1i1j1k1
CONJUGATE
" is 1i-1j-1k-1
"DIRECTION
"
Gives the unit
vector of a number Real: "-7
DIRECTION
" gives -1
Complex: "-7i-7
DIRECTION
" is
-0.707107i-0.707107 Quaternion: "-7i-7j-7k-7
DIRECTION
" is
-0.5i-0.5j-0.5k-0.5
"EXPONENTIAL
"
Is the
exponentiation with base e "1
EXPONENTIAL
" is e
(2.71828...) with as high precission as possible on a given
computer/os/compiler
"FACTORIAL
"
Pure factorial for
positive integers ("5
FACTORIAL
" is 120),
gamma function for all other numbers ("5i5
FACTORIAL
" is
-0.974395i2.00669)
"FLOOR
"
The result is TOS
rounded downwards to the nearest 'integer' ("-1.2
FLOOR
" is -2, "1.2
FLOOR
" is 1, "3.4i2.5
FLOOR
" is 3i2.
"MAGNITUDE
"
The magnitude
(hypotenuse) of the number. The result is
for all numbers always a real number. Real: "-17
MAGNITUDE
" is 17
Complex: "-17i17
MAGNITUDE
" is 24.0416
Quaternion: "-17i-17j-17k-17
MAGNITUDE
" is 34
"NATURALLOG
"
Logarithm with base
e. x "NATURALLOG
EXPONENTIAL
" is x. For
example "1i1
NATURALLOG EXPONENTIAL
"
is 1i1. Please take into the account the possible imprecissions
produced by the computer.
"NEGATIVE
"
The result is 0 - the number (0-x).
"NOT
"
LOGIC operation.
Any non-LOGIC values will be converted to LOGIC
values before performing the operation. The result of any NOT is 1 if
the argument is 0 (inside the comparison tolerance QuadCT), 0
otherwise. "(1 0)
NOT
" is (0 1). Uses
Lukasiewicz logic: ("_x
NOT
" is "1
_X SUBTRACT
").
"PITIMES
"
pi times
the argument. "1
PITIMES
" is 3.14159...
"RECIPROCAL
"
1 divided by the
argument. "2
RECIPROCAL
" is 0.5 "1i1
RECIPROCAL
" is 0.5i-0.5
"0.5i0.5j0.5k0.5
RECIPROCAL
" is
0.5i-0.5j-0.5k-0.5
"ROLL
"
The result is a
random number between the Index Origin (QuadIO), presently 1 and the
number given as argument. If the given number is integer, the result
will be integer, if it is a non-integer the result will be
non-integer. Multidimensional numbers will "ROLL"
independently in each dimension: "100i100j100k100
ROLL
" gives
85.0188i40.4383j79.3099k80.844 "100i100j100k100
ROLL
" gives
92.1647i20.7551j34.5223k77.823 "100i100j100k100
ROLL
" gives
28.7775i56.397j48.7397k63.8871 etc. ASCII characters will roll
between ' ' (space) and the given character. So for example 'z'
10 RESHAPE ROLL
could give '
>elCghr1zz', or anything else.
Scalar monadics work on substructures, by
applying to each element, as if there were no substructures. For
example "(1 2 3)
ENCLOSE (4 5 6) ENCLOSE CATENATE NEGATIVE
"
results in ((-1 -2 -3) (-4 -5 -6)).
Table 1: Implemented monadic operators working on data Presently implemented (Yes), not implemented (-) and not supported (X) datatypes
|
Ascii |
Unicode |
Integer |
Real |
Complex |
Quaternion |
Octonion |
CEILING |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
CONJUGATE |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
DIRECTION |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
EXPONENTIAL |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
FACTORIAL |
X |
X |
Yes |
Yes |
Yes |
X |
X |
FLOOR |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
MAGNITUDE |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
NATURALLOG |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
NEGATIVE |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
NOT |
Yes |
X |
Yes |
Yes |
Yes |
- |
- |
PITIMES |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
RECIPROCAL |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
ROLL |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
Yes |
INTERVAL |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
"CHECK
"
CHECK
is used in conjunction with the IF_YES
and IF_NO
operator modifiers (for more explanation see
IF_YES
and IF_NO
).
CHECK
expects a LOGIC
scalar or one element vector, giving the truth
value of the check. This value is saved internally, and is available
for all consequent IF_YES
and IF_NO
operator modifiers until the end of the sentence,
or until the next CHECK
operator. CHECK
discards the data on TOS.
"INTERVAL
"
The result of the
"INTERVAL
"
is a vector from index origin (QuadIO - presently 1) to the integer
number. The vector has 1 dimension and the number of elements is
equal to the integer number. With the multidimensional numbers,
"INTERVAL
"
gives a plane (complex numbers), a 3 or 4 dimensional 'space'
(quaternions) or a 5, 6, 7 or 8 dimensional 'space' (octonions), with
the multidimensional 'vector' addresses in each array location.
Examples: Integer: "10
INTERVAL
" is the vector
(1 2 3 4 5 6 7 8 9 10) of the shape 10. Complex integer: "5i5
INTERVAL
" is the
2-dimensional array 1i1 2i1 3i1 4i1 5i1 1i2 2i2 3i2 4i2 5i2 1i3 2i3
3i3 4i3 5i3 1i4 2i4 3i4 4i4 5i4 1i5 2i5 3i5 4i5 5i5 of the shape (5
5). Quaternion integer: "3i3j3
INTERVAL
" is a
3-dimensional array: 1i1j1 2i1j1 3i1j1 1i2j1 2i2j1 3i2j1 1i3j1 2i3j1
3i3j1 1i1j2 2i1j2 3i1j2 1i2j2 2i2j2 3i2j2 1i3j2 2i3j2 3i3j2 1i1j3
2i1j3 3i1j3 1i2j3 2i2j3 3i2j3 1i3j3 2i3j3 3i3j3 of the shape (3 3 3)
Quaternions give 3-dimensional arrays of indexes if k=0,
4-dimensional otherwise. Octonions give 5- (m=n=o=0), 6- (n=o=0), 7-
(o=0), or 8-dimensional arrays. On ASCII, i.e. on characters,
"INTERVAL
"
will make a character vector starting with ' ' and ending with the
given character. For example: "'C'
INTERVAL
" will result in
' !"#$%&'()*+,-./0123456789:;<=>?@ABC'. "'
' INTERVAL
" will give '
'.
"DECAPSULATE
"
Converts a COMPLEX,
QUATERNION or OCTONION scalar into a vector of the appropriate
length. INTEGER and REAL numbers will make
no change, COMPLEX number will make a 2 element vector, QUATERNION a
4 element, and OCTONION a 8 element vector.
Implementation
restriction: For now only scalars can be DECAPSULATE
d.
"SHAPE
"
Gives the shape of
a vector/array. The shape is a
1-dimensional vector with as many elements as the array has
dimensions, and with the particular dimensions as the vector
elements. Examples: "1i2
SHAPE
" is NIL [a scalar
is a zerodimensional array] "7
INTERVAL SHAPE
" is 7
"2i7 INTERVAL
SHAPE
" is (7 2) "3i4j2k7
INTERVAL SHAPE
" is (7 2
4 3). This corresponds to the row major order of the arrays. If the
argument to "SHAPE
"
is a variable address, the result is an ASCII vector of the variable
name. For example: "@_distance
SHAPE
" is '_distance'.
The variable does not have to be initialized for this operation. If
the argument to "SHAPE
"
is a LABEL, the result is an ASCII vector of the label name.
"RAVEL
"
The result is a
one-dimensional vector of all the elements of an anydimensional
array. Example: "24
INTERVAL (2 3 4) RESHAPE
"
gives
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
|
|||
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
of the
shape (2 3 4). "RAVEL
"
of the above array is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 of the shape 24. RAVEL
of a scalar gives a vector of length 1.
"FIRST
"
Selects the first
item of the argument in a row major order. If
the argument is empty, the result will be the 'prototype' of the
argument. If the empty element is a character (type ASCII), it will
result in an scalar with the character value of space (' '), and if
it is a numeric empty element, the result will be 0. For example "''
FIRST
" results in ' ',
"0 INTERVAL FIRST
"
results in 0. "FIRST
"
takes of the first (leftmost) dimension from the argument, giving a
result with rank (dimensionality) 1 less than the argument. Example:
For the "24
INTERVAL (2 3 4) RESHAPE
"
result (see in RAVEL
),
the result of "FIRST
"
is
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Applying "FIRST
"
again gives: 1 2 3 4 and "FIRST
"
again: 1 Any further application of FIRST
will give the scalar itself. FIRST
and REST
are principally the same as CAR (FIRST
)
and CDR (REST
)
in Lisp.
"REST
"
Selects all but the
first item of the argument in a row major order. The
REST
of a scalar is #NIL (there is nothing in a scalar
if you take out the first and only element - the result is an empty
set). However, the type of the #NIL is ASCII if it resulted from
"REST
"
of a scalar character. Therefore: "53
REST FIRST
" is 0, "'a'
REST FIRST
" is a space
(' '). REST
will never change the rank (dimensionality) of the
argument. For example, applied to the "24
INTERVAL (2 3 4) RESHAPE
"
(see RAVEL
),
"REST
"
will produce:
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
The "SHAPE
"
of this result is (1 3 4), i.e. it is still rank 3 (a 3-dimensional
array), but the first (leftmost) dimension has only 1 element of a
2-dimensional array "SHAPE
"d
(3 4). Therefore applying "REST
"
to the result above will give #NIL [there is no non-first member of
the array of shape (1 3 4)!]. To get to a lower dimension first use
"FIRST
"
on the above [the (1 3 4) shaped array]. So: "24
INTERVAL (2 3 4) RESHAPE REST FIRST REST
"
produces:
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
[The "FIRST
"
took the first (leftmost) dimension of the 3-dimensional array
resulting from the first "REST
".]
FIRST
and REST
are principally the same as CAR (FIRST
)
and CDR (REST
)
in Lisp.
"ENCLOSE
"
Creates a scalar
vector/array whose only element is the argument given. A scalar
vector/array is a structure which has only one element, of the SHAPE
of a scalar, i.e. whose shape is NIL. For example:
"(1 2 3) (2 4)
RESHAPE ENCLOSE
" results
in
1 |
2 |
3 |
1 |
2 |
3 |
1 |
2 |
After this scalar
vector is on TOS, "SHAPE" will result in '.', i.e. #NIL.
However "MONADIC
SHAPE; EACH
" will
produce the SHAPE of the enclosed element (or more of them, if
CATENATE
d).
For the above example it will produce:
2 |
4 |
"DISCLOSE
"
DISCLOSE
is the left inverse of ENCLOSE
.
DISCLOSE
works only on scalar or one element structured
vectors. The result of disclosing a vector/array which was not
enclosed is the vector/array itself, i.e. no operation is done.
Disclosing an enclosed structure reduces its depth by 1, and the
result is a non-structured vector/array. "(1
2 3) (2 4) RESHAPE DUPLICATE ENCLOSE DISCLOSE EQUAL
"
is always true for any data.
NB.: For disclosing substructures of
an vector/array use the "FIRST
"
- "REST
"
combination.
"DEPTH
"
The result is the
depth of a structure, i.e. the number of substructure levels, the
nesting levels. If there are no sub-levels, sub-structures, the depth
is either 0 for scalars, or 1 for vectors/arrays: "1
DEPTH
" gives 0, "(1)
DEPTH
" gives 1. "1
ENCLOSE ENCLOSE ENCLOSE DEPTH
"
is 3.
"REDUCE
"
REDUCE
is a special operator whose syntax includes itself
and the operator word in from of it. This means that, e.g., "ADD
DIVIDE
" are two
consecutive words, and are two consecutive dyadic operators, whereas
"ADD REDUCE DIVIDE
"
are also TWO consecutive words ("ADD
REDUCE
" and "DIVIDE
")
and are a monadic operator ("ADD
REDUCE
") followed by the
dyadic operator "DIVIDE
".
Table 2 gives the list of implemented REDUCE
operations. The REDUCE
has the effect of placing the operator verb (e.g.
"ADD
"
in the previous text) between adjacent pairs of imens along the LAST
axis of the TOS, and evaluating the resulting expression for each
pair, from right to left throughout the vector/array. The right to
left execution means that non-commutable operatons give alternating
results, i.e. "(1 2
3 4) SUBTRACT REDUCE
"
gives -2 [(1-(2-(3-4)))]. Example: "12
INTERVAL (3 4) RESHAPE ADD REDUCE
"
gives (10 26 42). REDUCE
does not support COMPLEXED vectors/arrays yet. A
REDUCE
operator at the begining of a sentence behaves as
a NOOP
.
"REDUCEFIRSTAXIS
"
This operator has
the same syntactic effect as the REDUCE
(see above). The only difference with REDUCE
is that it reduces the array allong the FIRST
axis, as opposed to REDUCE
(which reduces along the LAST axis). Example: "12
INTERVAL (3 4) RESHAPE +REDUCEFIRSTAXIS
"
gives (6 15 24 33). REDUCEFIRSTAXIS
does not support COMPLEXED vectors/arrays yet.
REDUCEFIRSTAXIS
presently does not support non-commutable
operators, i.e. SUBTRACT
and DIVIDE
.
A REDUCEFIRSTAXIS
operator at the begining of a sentence behaves as
a NOOP
.
Table
2: Implemented reduction operators (REDUCE
and REDUCEFIRSTAXIS
)
Presently implemented (Yes), not implemented (-) and not supported
(X) datatypes
|
Ascii |
Unicode |
Integer |
Real |
Complex |
Quaternion |
Octonion |
ADD |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
AND |
Binary |
- |
Yes boolean |
Yes boolean |
- |
- |
- |
DIVIDE |
X |
X |
Yes |
Yes |
Yes |
Yes |
- |
MULTIPLY |
X |
X |
Yes |
Yes |
Yes |
Yes |
- |
OR |
Binary |
X |
Yes boolean |
Yes boolean |
- |
- |
- |
SUBTRACT |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
"EXECUTE
"
Execute starts the
execution of a function, which should be on TOS. To get the function
on TOS, just use the variable name into which the FUNCTION
was SET
,
or ASSIGN
(actually after ASSIGN
the FUNCTION
stays on TOS and can be EXECUTE
d).
To allow recursivity, execute will make a new stack frame. The
FUNCTION
s
can have any number of arguments, but must leave only one result on
the stack. See FUNCTION
for further description.
"JUMP
"
Jump is commonly
used inside functions, although it can be used inside sentences.
However, it can not jump between consecutive sentences (functions are
single sentences!), as only the current sentence is kept as a
programme. JUMP
has one argument, which is actually always an
integer. "0 JUMP
"
has the same effect as RETURN
has, and is commonly used to stop the function
execution anywhere. As seen in the example below, "0
JUMP
" can also be used
inside a sentence, and it will just skip the rest of it, as also in
functions. "#NIL
JUMP
" will just
continue, and forget the JUMP
.
Any other number on TOS will force the interpreter to continue the
execution from that word in the sentence. Commonly the positions of
the words in a sentence are represented by LABEL names, although the
value of a LABEL is just an integer. For example: "(1
2 3) 5 JUMP (5 6 7) 1 ADD.
"
gives (2 3 4), the (5 6 7) is skipped. From this example it is
obvious that each operator or structure [e.g. (5 6 7)] is one word in
a Virtue sentence. However, the above sentence would be much more
readable (and allow for modification) by usage of labels. So: "(1
2 3) @%one JUMP (5 6 7) %one 1 ADD.
"
gives (2 3 4). "(1
2 3) #NIL JUMP (5 6 7) 1 ADD ADD.
"
gives (7 9 11). "(1
2 3) 0 JUMP (5 6 7) 1 ADD.
"
gives (1 2 3).
All operations operate on the TOS-1 and TOS,
discarding TOS (i.e. popping the stack) and leaving the result on the
new TOS. For non-commutative operations the TOS-1 is the left
argument, and the TOS the right. For example: "10
5 DIVIDE
" gives 2, "5
10 DIVIDE
" gives 0.5.
The non-commutative operations on quaternions and octonions are also
performed in traditional mathematical sense 'left op right' (e.g.
"2i2j2k2 10i9j8k7
MULTIPLY
" gives
-28i36j40k32, "10i9j8k7
2i2j2k2 MULTIPLY
" gives
-28i40j32k36).
The scalar dyadics work on scalars and scalars, scalars and vectors/arrays, and vectors/arrays and vectors/arrays of the same shape and rank (dimensionality).
"ADD
",
"+
"
Addition. Always commutative.
"AND
",
"^
"
LOGIC and. Any
non-LOGIC values will be converted to LOGIC values before performing
the operation. "(1
1 0 0) (1 0 1 0) AND
" is
(1 0 0 0). Uses Lukasiewicz logic: ("_x
_y AND
" is "_x
_y MINIMUM
").
"BINOMIAL
",
"!
"
Binomial function.
For nonnegative integer arguments yields the
number of distinct combinations of TOS things taken TOS-1 at a time.
Example: "3 10
BINOMIAL
" is 45. The
binomial function extends to all numbers. The coefficients of the
binomial expansion (x+1)^_r can be determined by BINOMIAL using the
expression "0 _r
INTERVAL CATENATE _r BINOMIAL
",
e.g if _r is 3 then "(0
1 2 3) 3 BINOMIAL
" is (1
3 3 1).
"CIRCULAR
"
This is a special operator which is dyadic, although it actually performs monadic work. The left argument (TOS-1) to it is the number (or vector/array) on which the operation(s) specified in the right argument (TOS) scalar/vector/array work. The operations can be specified symbolically, or by an integer in the range of -7 to +7. (This range will be later expanded to -12 to +12 according to the APL specification of those operations.) The following operations are specified:
" |
"-7" |
hyperbolic arcus tangens. |
" |
"-6" |
hyperbolic arcus cosinus. |
" |
"-5" |
hyperbolic arcus sinus. |
" |
"-4" |
sqrt(-1+sqr(TOS-1)). |
" |
"-3" |
arcus tangens. |
" |
"-2" |
arcus cosinus. |
" |
"-1" |
arcus sinus. |
" |
"0" |
sqrt(1-sqr(TOS-1)). |
" |
"1" |
sinus. |
" |
"2" |
cosinus. |
" |
"3" |
tangens. |
" |
"4" |
sqrt(1+sqr(TOS-1)). |
" |
"5" |
hyperbolic sinus. |
" |
"6" |
hyperbolic cosinus. |
" |
"7" |
hyperbolic tangens. |
Examples:
"0
#COS CIRCULAR
" gives 1
"0
(#SIN #COS #TAN) CIRCULAR
"
gives (0 1 0)
"0
(1 2 3) CIRCULAR
" gives
(0 1 0)
"(0
1 2) (#ACOS #ASIN #ATAN) CIRCULAR
"
gives (1.5708 1.5708 1.10715)
"DIVIDE
"
Division.
"EQUAL
",
"=
"
Compares for equality of two ASCIIs or NUMBERs. Commutative. The result is LOGIC TRUE (1) if equal else FALSE (0). The NUMBERs are compared based on comparison tolerance (QuadCT). EQUAL compares 'nan's, i.e. two numbers are EQUAL if both of them are nan (in all number dimensions, i.e. nan = nan, naninan = naninan, naninanjnanknan = naninanjnanknan etc.)
"GREATER
",
">
"
The result is LOGIC
TRUE if TOS-1 > TOS. Multidimensional numbers are compared by
their MAGNITUDE
,
i.e. "3i3 3i2
GREATER
" is equivalent
to "3i3 MAGNITUDE
3i2 MAGNITUDE GREATER
".
"LESS
",
"<
"
The result is LOGIC
TRUE if TOS-1 < TOS. Multidimensional numbers are compared by
their MAGNITUDE
.
"LOGARITHM
"
Logarithm base
TOS-1 of TOS. Example: "2
16 LOGARITHM
" is 4.
"MAXIMUM
"
Bigger of the two
numbers. Multidimensional numbers are
compared by their MAGNITUDE
.
Example: "3i3 3i2
MAXIMUM
" is 3i3.
"MINIMUM
"
Smaller of the two
numbers. Multidimensional numbers are
compared by their MAGNITUDE
.
Example: "3i3 3i2
MINIMUM
" is 3i2.
"MULTIPLY
"
Multiplication.
Commutative for INTEGER, REAL and COMPLEX.
Non-commutative for QUATERNION and OCTONION. As non-commutative TOS-1
is multiplied by TOS, e.g.: "2i2j2k2
10i9j8k7 MULTIPLY
" gives
-28i36j40k32, "10i9j8k7
2i2j2k2 MULTIPLY
" gives
-28i40j32k36.
"NAND
"
LOGIC not and. Any
non-LOGIC values will be converted to LOGIC values before performing
the operation. "(1
1 0 0) (1 0 1 0) NAND
"
is (0 1 1 1). Uses Lukasiewicz logic: ("_x
_y NAND
" is "1
_x _y MINIMUM SUBTRACT
").
"NOR
"
LOGIC not or. Any
non-LOGIC values will be converted to LOGIC values before performing
the operation. "(1
1 0 0) (1 0 1 0) NOR
" is
(0 0 0 1). Uses Lukasiewicz logic: ("_x
_y OR
" is "1
_x _y MAXIMUM SUBTRACT
").
"NOTEQUAL
"
Not equal. LOGIC result is TRUE (1) if TOS-1 ≠ TOS. Commutative. The NUMBERs are compared based on comparison tolerance (QuadCT). NOTEQUAL compares 'nan's, i.e. two numbers are NOT EQUAL if any of them is nan (in any number dimension)
"NOTGREATER
"
The result is LOGIC
TRUE if TOS-1 ≤ TOS. Multidimensional numbers are compared by
their MAGNITUDE
.
"NOTLESS
"
The result is LOGIC
TRUE if TOS-1 ≥ TOS. Multidimensional numbers are compared by
their MAGNITUDE
.
"OR
"
LOGIC or. Any
non-LOGIC values will be converted to LOGIC values before performing
the operation. "(1
1 0 0) (1 0 1 0) OR
" is
(1 1 1 0). Uses Lukasiewicz logic: ("_x
_y OR
" is "_x
_y MAXIMUM
").
"POWER
",
"*
"
TOS-1 raised on the
power of TOS. Even roots of negative numbers produce complex numbers,
i.e. "-10 0.25
POWER
" is
1.25743i1.25743.
"RESIDUE
",
"|
"
For real positive
numbers, the remainder of division of TOS-1 by TOS. Generally
residue is TOS-(TOS-1)*floor(TOS / (TOS-1)). Example: "(-10
7i10 0.3) (17 5 10) RESIDUE.
"
is (-3 -5i7 0.1).
"STRONGAND
"
Lukasiewicz LOGIC
strong and. Any non-LOGIC values will be converted to LOGIC values
before performing the operation. For BOOLEAN LOGIC values behaves the
same as AND. Lukasiewicz logic operation: ("_x
_y STRONGAND
" is "0
_x _y ADD 1 SUBTRACT MAXIMUM
").
"STRONGNAND
"
Lukasiewicz LOGIC
strong not and. Any non-LOGIC values will be converted to LOGIC
values before performing the operation For BOOLEAN LOGIC values
behaves the same as NAND. Lukasiewicz logic operation: ("_x
_y STRONGNAND
" is "1
0 _x _y ADD 1 SUBTRACT MAXIMUM SUBTRACT
").
"STRONGNOR
"
Lukasiewicz LOGIC
strong not or. Any non-LOGIC values will be converted to LOGIC values
before performing the operation. For BOOLEAN LOGIC values behaves the
same as NOR. Lukasiewicz logic operation: ("_x
_y STRONGNOR
" is "1
1 _x _y ADD MINIMUM SUBTRACT
").
"STRONGOR
"
Lukasiewicz LOGIC
strong or. Any non-LOGIC values will be converted to LOGIC values
before performing the operation. For BOOLEAN LOGIC values behaves the
same as OR. Lukasiewicz logic operation: ("_x
_y STRONGOR
" is "1
_x _y ADD MINIMUM
").
"SUBTRACT
",
"-
"
TOS subtracted from TOS-1. Non-commutative.
Table 3: Implemented scalar dyadic operators working on data Presently implemented (Yes), not implemented (-) and not supported (X) datatypes
|
Ascii |
Unicode |
Integer |
Real |
Complex |
Quaternion |
Octonion |
ADD |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
AND |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
BINOMIAL |
X |
X |
Yes |
Yes |
Yes |
- |
- |
CIRCULAR |
X |
X |
Yes |
Yes |
- |
- |
- |
DIVIDE |
X |
X |
Yes |
Yes |
Yes |
Yes |
- |
EQUAL |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
GREATER |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
LESS |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
LOGARITHM |
X |
X |
Yes |
Yes |
Yes |
- |
- |
MAXIMUM |
Yes |
- |
Yes |
Yes |
Yes |
- |
- |
MINIMUM |
Yes |
- |
Yes |
Yes |
Yes |
- |
- |
MULTIPLY |
X |
X |
Yes |
Yes |
Yes |
Yes |
- |
NAND |
Yes |
X |
Yes |
Yes |
Yes |
Yes |
- |
NOR |
Yes |
X |
Yes |
Yes |
Yes |
Yes |
- |
NOTEQUAL |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
NOTGREATER |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
NOTLESS |
Yes |
- |
Yes |
Yes |
Yes |
Yes |
- |
OR |
Yes |
X |
Yes |
Yes |
Yes |
Yes |
- |
POWER |
X |
X |
Yes |
Yes |
Yes |
- |
- |
RESIDUE |
X |
X |
Yes |
Yes |
Yes |
- |
- |
STRONGAND |
X |
X |
Yes |
Yes |
Yes |
- |
- |
STRONGNAND |
X |
X |
Yes |
Yes |
Yes |
- |
- |
STRONGNOR |
X |
X |
Yes |
Yes |
Yes |
- |
- |
STRONGOR |
X |
X |
Yes |
Yes |
Yes |
- |
- |
SUBTRACT |
X |
X |
Yes |
Yes |
Yes |
Yes |
Yes |
Non-scalar dyadics work on structures, and do not produce a mathematical, but a structural result.
"CATENATE
",
",
"
Joins the TOS-1
scalar or vector with the TOS scalar or vector. Note that only
scalars and vectors may be joined, not arrays. Example: "(3i1
4i2 5i3) 1i2j3k4 CATENATE ' ab' (1 2 3) , CATENATE
"
gives (3i1 4i2 5i3 1i2j3k4 ab 1 2 3).
"DEAL
",
"?
"
"<a>
<b> DEAL
" selects
unique integers randomly from the population of "<a>
INTERVAL
", without
repetition. The TOS argument must, naturally, be less then or equal
to the TOS-1 (cannot deal 12 out of 10 cards!). If TOS and TOS-1 are
equal DEAL gives a random permutation of the set of integers 1..TOS.
Example: "10 10
DEAL
" gives (9 4 8 10 2
3 6 5 7 1) "10 10
DEAL
" gives (3 2 9 5 10
6 7 8 4 1) ... "100
0 DEAL
" gives NIL.
"EACH
"
EACH
applies the function, which is on TOS, to each
element of the space (vector/array) on TOS-1 in case of a monadic
FUNCTION
(i.e. a dyadic EACH
),
or on TOS-2 and TOS-1 in case of a dyadic FUNCTION
(i.e. a triadic EACH
).
A monadic FUNCTION
is defined as "ARGS
1 FUNCTION
", a dyadic
one as "ARGS 2
FUNCTION
". For any other
function (i.e. niladic or pletoradic) EACH
will return an error. To get the function on TOS,
just use the variable name into which the FUNCTION
was SET
,
or ASSIGN
ed
- the same way as for EXECUTE
.
EACH
ed
FUNCTION
can have any specification necessary (i.e. can be
recursive, call other functions etc., but shall work on scalars!
Working on scalars means that the function has to receive its input
as a scalar (although the scalar may be a scalar array, but as a
substructure), and shall leave its only result on TOS. Functions
which return more than one result on stack will have unpredictable
behaviour with "EACH
".
If the result of the FUNCTION
is not a scalar but a vector/array, it will be
included in the result of "EACH
"
as a sub-structure (the same as if normally "ENCLOSE
"
would have been called on this vector/array). Therefore "(1
2 3) (10 20 30) ADD
"
[giving (11 22 33)] and "(1
2 3) ARGS 1 FUNCTION (10 20 30) ADD; EACH.
"
are not the same, as the second one gives as a result ((11 21 31) (12
22 32) (13 23 33))! An example for simple "EACH
":
To calculate 26 first Fibonnaci numbers using a recursive algorithm
(the FUNCTION
'.fib' can be found in the Appendix 1, and shall
be defined before the example works) write: "26
INTERVAL .fib EACH
"
giving the result: (1 4 6 10 16 26 42 68 110 178 288 466 754 1220
1974 3194 5168 8362 13530 21892 35422 57314 92736 150050 242786
392836) Restriction: Presently only monadic EACH
is implemented.
"RESHAPE
"
RESHAPE
structures the items of TOS-1 into an array of the
shape TOS. This means that the scalar,
vector or n-dimensional space on TOS-1 will become of the shape
specified in a scalar or a (one-dimensional) vector. The number of
dimensions of the resulting space (array) is the number of elements
of the vector on TOS, and the sizes of each of the dimensions are
given by the (integer) numbers in the TOS vector. RESHAPE and SHAPE
are related. Examples: "1
(2 3 4) RESHAPE
" gives
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
|
|||
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
that is,
a 2 rows by 3 rows by 4 columns array of 1's. "10
INTERVAL (2 18) RESHAPE
"
gives
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
that is
2 rows of 18 columns. Note that RESHAPE
will either expand the vector to the array by
repeating it from the begin as many times as necessary, or will
compress it by deleting the superflous elements. If we take the
result of the previous operation and apply e.g. "(2
4 13) RESHAPE
" we will
get
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
|
||||||||||||
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
1 |
2 |
and finally,
by applying "10
RESHAPE
" we get the
original vector (the result of "10
INTERVAL
"): (1 2 3 4 5 6
7 8 9 10).
Restriction: Presently structured data can not be
reshaped.
"ASSIGN
"
ASSIGN
is a dyadic operator which puts the value on TOS-1
into the address at TOS, and leaves the stack with the assigned
(saved) value on the TOS for further processing. Example: "3
@_a ASSIGN
"
1 ADD
"
gives 4, and after that "_a
"
puts 3 on TOS. So if we continue, "ADD
"
would result in 7. ASSIGN
works on data and functions so, e.g. the following
would give the same result as above: "FUNCTION
3; @_a ASSIGN EXECUTE 1 ADD _a EXECUTE ADD
"
It is though wise to distinguish functions from variables by using
"_" for variables and "." for functions, although
there is no restriction imposed. So, actually the above FUNCTION
assignment would be wiser to have been written:
"FUNCTION 3; @.a
ASSIGN EXECUTE 1 ADD .a EXECUTE ADD
"
"SET
"
SET
is also a dyadic operator which puts the value on
TOS-1 into the address at TOS, but opposed to "ASSIGN
"
it pops both the address and the value from the stack. So, e.g. the
previous example would be written with SET
as: "FUNCTION
3; @.a SET .a EXECUTE 1 ADD .a EXECUTE ADD
".
"IF
"
IF
is a dyadic operator which checks the TOS-1 for a
LOGIC value. The IF
will produce NIL on TOS if the TOS-1 is #FALSE,
otherwise leaves the TOS unchanged. It is commonly used with JUMP
,
as JUMP
will jump on an address, but just continue through
a programme if it finds NIL on TOS. IF does not change the size of
the stack, so it behaves rather like a monadic. Example: "'abc'
'abc' = ^REDUCE @%equal IF JUMP 'They difere' 0 JUMP %equal 'They are
same'.
" gives the result
'They are same'. Although IF
may be used, together with JUMP
,
even in directly inputed sentences, actually it is provided to be
part of FUNCTION
s,
not direct execution. (It is, quite obviously, easier to see, in the
above example, that 'abc' is equal to 'abc', than think about the
complicated IF
construction).
"MASK
"
MASK
is a triadic operator executing the MONADIC
function on TOS for each array which is the result
of applying the TOS-1 mask to the TOS-2 data. The mask is applied by
multiplication, and has to have the same rank as the TOS-2 data, and
the length of each of the mask's dimensions has to be odd. For an
unmodified MASK
it is presupposed that the data outside the TOS-2
indices is empty, i.e. 0 for numerical data or ' ' for character
data. The following example shows the input to the function on TOS:
9
INTERVAL (3 3) RESHAPE 0.5 (3 3) RESHAPE MONADIC PRINT ' ' PRINT;
MASK.
0 0 0
0
0.5 1
0 2 2.5
0 0 0
0.5 1 1.5
2 2.5 3
0 0 0
1
1.5 0
2.5 3 0
0 0.5 1
0 2 2.5
0 3.5 4
0.5 1
1.5
2 2.5 3
3.5 4 4.5
1 1.5 0
2.5 3 0
4 4.5 0
0
2 2.5
0 3.5 4
0 0 0
2 2.5 3
3.5 4 4.5
0 0 0
2.5
3 0
4 4.5 0
0 0 0
. -->
A #NIL is left on stack, as the exit from the FUNCTION
was empty stack.
The
following algorithm calculates the 'next generation' for Conway's
Game of Life (let's suppose that the current generation is saved
as a rank 2 array (two-dimensional matrix) in the variable _board
:
_board
1 (3 3) RESHAPE MONADIC RAVEL SUM 3 EQUAL; MASK.
"EACH
"
EACH
applies the function, which is on TOS, to each
element of the space (vector/array) on TOS-1 in case of a monadic
FUNCTION
(i.e. a dyadic EACH
– see under Dyadics), or on TOS-2 and TOS-1
in case of a dyadic FUNCTION
(i.e. a triadic EACH
).
Dyadic FUNCTION
EACH
,
i.e. the triadic EACH
is not implemented yet!
"LEFT
",
"DISCARD
"
Take the left
argument, discard the right one. This operator frees the TOS and
leaves the former TOS-1 on TOS. As a dyadic operator it is called
LEFT
,
as a monadic DISCARD
.
Their function is the same. If there is only one value on the stack,
the result is empty stack. Example: "1
2 LEFT
" gives 1.
"RIGHT
"
Take the right
argument, discard the left one. This operator frees the TOS-1 and
leaves TOS on TOS. Example: "1
2 RIGHT
" gives 2.
"DUP
",
"DUPLICATE
"
Duplicate the value
on TOS. It gives two same values on TOS. This can be used whenever a
value has to be temporary preserved, and in the same time used for
operations giving a second value. For example, to square a number,
do: "4 DUP
MULTIPLY
" gives 16.
"SWAP
"
Swap the TOS and
TOS-1. The former TOS-1 is now TOS, the former TOS is TOS-1. Example:
"2 4 SWAP DIVIDE
"
gives 2.
FUNCTION
s.
They can be
directly copy-pasted into Virtue"BEWARE: the functions in Virtue presently do not have local variables, so
all variables used inside a function definition are actually side effects,
and their changes are global. So be careful not to use the same names for
function variables and global variables. In the future there will be
local variables for functions in Virtue. Be patient!"
"The Hello world programme:"
'Hello world'.
"or, as a function:"
FUNCTION 'Hello world'; @.Hello SET.
"Execute it with:"
.Hello EXECUTE.
" This is the function which calculates the n'th Fibonacci number"
" Usage: .fib EXECUTE ."
"We define a" ARGS 1 FUNCTION "as follows:"
"First" DUP"licate the Top Of Stack (TOS)."
"Now, if the Fibonacci number requested is less then 2:"
2 < ", to the label" @%a IF "it is so" JUMP
"else execute fib(TOS - one) + fib(TOS - 2). Not to have temporary variables,
the DUP operator is used:"
DUP 1 - .fib EXECUTE SWAP 2 - .fib EXECUTE ADD RETURN
"0 JUMP is exit from the function"
"now we are at the label" %a", so we" DISCARD "the user input, put" 1 "on the
top of the stack and" FUNCTIONEND "its definition."
"The variable address into which we will put the function is"
@.fib", and we just have to" SET "it."
"That's all folks".
"This is the same function without the superflous comments:"
MONADIC "i.e. ARGS 1 FUNCTION"
DUP 2 < @%a IF JUMP
DUP 1 - .fib EXECUTE SWAP 2 - .fib EXECUTE + RETURN
%a DISCARD 1;
@.fib SET.
"To get the 26'st Fibonacci number type in:"
26 .fib EXECUTE.
" This is the function which calculates n!:
Usage: .fact EXECUTE ."
" This is a recursive function, it is much faster to calculate the n!
using INTERVAL MULTIPLY REDUCE, but even faster (very fast) is
to do it using FACTORIAL. So effectively the following function is
just a simple example of recursive functions, with no other merit than
the measurement of computer speed. Furthermore, the FACTORIAL operator
is defined for all numeric data and data types!"
MONADIC DUP 2 < 0 IF JUMP "i.e. return from function if TOS < 2"
DUP 1 - .fact EXECUTE MULTIPLY "n*fact(n-1)"
FUNCTIONEND "or ';'!"
@.fact SET. "set the function name, i.e. put the
FUNCTION into the variable .fact"
"To get 12! type in:"
12 .fact EXECUTE.
" This is the function which calculates n prime numbers:
Usage: .prime EXECUTE ."
" It is not recursive, but uses looping."
ARGS 1 FUNCTION
@_end SET "put the TOS into _end"
2 1 @_t ASSIGN RESHAPE @_p SET "_t is 1, _p is (1)"
%Test _p _end _p SHAPE NOTGREATER 0 IF JUMP DISCARD "_p stays on TOS if 0 JUMP"
%Add _p _t 2 + @_t ASSIGN | 0 = +REDUCE @%Add IF JUMP
_p _t CATENATE @_p SET
@%Test JUMP; "This FUNCTIONEND (';') is never reached, as the function exits
above on 0 JUMP. It must be here to end the FUNCTION."
@.prime SET.
"To get the first 100 primes do:"
100 .prime EXECUTE.
"And finally, an example of fully non-looping processing of multidimensional
arrays using complex numbers and quaternions."
"This function calculates the magnetic environment of a conductor.
It is a 5 arguments function, the usage is:
_wire_radius _current _current_vector
_render_space _position_in_space .Magnetic EXECUTE.
The current is in Amperes, the current vector, render space and position
have to be specified in complex numbers or quaternions for the function
to have sense."
ARGS 5 FUNCTION
"position in space" @_pos SET "the arguments are on stack from the"
"render space" @_size SET "last to the first imputed"
"current vector" @_dl SET
"current" @_I SET
"wire radius" @_d SET
1e-7 4 PITIMES MULTIPLY @_m0 SET "constant m0"
_size INTERVAL _pos SUBTRACT @_rv ASSIGN
MAGNITUDE @_dist ASSIGN
_d NOTGREATER @_B ASSIGN
NOT @_A SET
_m0 _I MULTIPLY 4 PITIMES DIVIDE
"_dl _rv DIRECTION MULTIPLY" "direction does not yet work on quaternions,
therefore it is better to use the following:"
_dl _rv _dist DIVIDE MULTIPLY
_A _dist _dist MULTIPLY MULTIPLY
_B _d _d MULTIPLY MULTIPLY
ADD
DIVIDE
MULTIPLY;
@.Magnetic SET.
"For example:"
2.3 100 10i10j10 30i30j30 15i15j15 .Magnetic EXECUTE @_mag SET.
"On a Linux mips (e.g. Cobalt Qube) the above expression gives a SIGFPE signal
(i.e. a Floating point exception) due to the calculation with NaN. If we move
the centre point a little bit, it will perform OK:"
2.3 100 10i10j10 30i30j30 15.5i15.5j15.5 .Magnetic EXECUTE @_mag SET.
To use the
.Magfield FUNCTION
in a batch processing environment, the Virtue
processor shall be used as a pipe, and it's output saved in a file.
To allow batch processing Virtue accepts the '-q' flag, and it will
produce only explicit Top Of Stack PRINT
or WRITE
,
and it will print TOS after each sentence, but will not produce any
other output, except error messages.
The .Magfield
FUNCTION
used as shown below, will produce a 4-dimensional
printout of 3-dimensional spaces with a wire crossing diagonally
through the given space, and the current behaving through time (the
4th dimension) as a sine wave.
The following file shall be saved as "Magfield.virt":
" .Magfield "
ARGS 3 FUNCTION
"current" 1e-7 "4 PITIMES MULTIPLY @_m0 ASSIGN"
"constant m0 - magnetic permeability of vacuum"
MULTIPLY "4 PITIMES DIVIDE" @_I SET "_I is m0/4pi*I"
"conductor vectors vector" SWAP
'render space' PRINT WRITE INTERVAL @_space ASSIGN
SHAPE 0 SWAP RESHAPE @_frame SET "make room for the final frame"
MONADIC
"current vectors" DUP
FIRST @_dl SET
REST 'Position: ' PRINT WRITE
_space SWAP SUBTRACT @_rv ASSIGN
MAGNITUDE @_dist SET
_dl
_rv DIRECTION
MULTIPLY
_dist _dist MULTIPLY
DIVIDE
_I MULTIPLY
_frame ADD @_frame SET;
EACH
DISCARD
_frame;
@.Magfield SET.
MONADIC INTERVAL MONADIC 1i1 MULTIPLY 1i1 SWAP CATENATE; EACH; "A 2-dimesional diagonal line"
@.line2 SET.
MONADIC INTERVAL MONADIC 1i1j1 MULTIPLY 1i1j1 SWAP CATENATE; EACH; "A 3-dimensional diagonal line"
@.line3 SET.
MONADIC INTERVAL MONADIC 1i1j1k1 MULTIPLY 1i1j1k1 SWAP CATENATE; EACH; "A 4-dimensional diagonal line"
@.line4 SET.
60i60j60 @_size SET
40 INTERVAL 10 DIVIDE PITIMES #SIN CIRCULAR 1e8 MULTIPLY
MONADIC
"We use a 3-dimensional diagonal in this example"
_size SWAP 60 .line3 EXECUTE SWAP
.Magfield EXECUTE MAGNITUDE FLOOR PRINT DISCARD;
EACH
DISCARD.
"And do not forget the OFF, if you use this file in non-interactive mode." OFF.
Start the batch processing of the above file ('Magfield.virt') as follows:
cat
Magfield.virt | Virtue
-q > Magfield.res
and the results of processing will be saved in the file 'Magfield.res'.