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:
-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).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 0
(inside the comparison tolerance QuadCT) is FALSE (the LOGIC is 0), any other
value is TRUE, and gives as the LOGIC 1. Results of LOGIC operations are always
only the integers 0 or 1.
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, it 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 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 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
not yet be modified by the IF_YES
operator modifier, but FUNCTION
s can. 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.
"IF_NO
", "?N
"
The IF_NO
is an operator modifier. An
operator or a function 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 |
- |
- |
- |
AND |
Binary |
- |
Yes boolean |
Yes boolean |
- |
- |
- |
DIVIDE |
X |
X |
Yes |
Yes |
- |
- |
- |
MULTIPLY |
X |
X |
Yes |
Yes |
- |
- |
- |
OR |
Binary |
X |
Yes boolean |
Yes boolean |
- |
- |
- |
SUBTRACT |
X |
X |
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 |
- |
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 |
- |
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:
.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
_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'.