Preface
In this note we will see some internals about how M2000 interpreter works.
Introduction
M2000, is a programming language, an extended BASIC, interpreted only, for education purposes, and for fun. There is a M2000 Environment, a program, an implementation of M2000 Interpreter written and compiled to machine code in Visual Basic 6.
M2000 offer event driven programs, functional, procedural, modular and object oriented programming. Can use dynamic scope and lexical scope in same program. There is no Run command in M2000, we call a Module by name, so we can make a module as Run so we can define Run as the first module to run.
Module is a named block of statements. A module is responsible for reading arguments, and interpreter pass values to module without checking for signature. By default assigning a value to a name (user identifier), interpreter create a local variable.
A local variable Alfa in a module Beta has a name of two parts and a dot between Beta.Alfa but we use it as Alfa in the module. Sometimes a module Beta can be called as function, using Call Beta, and that change the module name in execution object as
current_name[number] so Alfa maybe has this name: A[1].Beta. All functions have this style of name, and when we call function in a function, the
current_name stay as constant and number change to number+1. From revision 14 version 9.0 we can use internal naming for modules like functions, so if we have a beta module and inside a delta and inside a beta, then a variable alfa in first beta have different name from one alfa in inner beta. By default M2000 use the simple two parts, name of module dot and identifier name, because we can get better feedback when code run "opened" in control form. We can watch commands as executed, and we can see the name of module. If we use the more secure way, we see a name with different numbers, and this is not convenient.
In the example below we use in a module the set statement which send the remaining part of line to CLI (work in global scope), and here we use switches statement (when we call interpreter we can set switches from command line). Some switches are for running interpreter only, and some stored for every new running interpreter. So here we can change the way of internal name store, for modules, two times in same module;
Internal name produced when a Module b { } statement create module b, and each time we use module b, simply using b. There is another call type, the Call b which uses the secure type of calling, and has an extra, can be used to call recursive a module. By default we can't call b in b using only the name of module, in either switch -SEC or +SEC.
set switches "-SEC"
Module b {
alfa=50 : Module c { Module b {alfa=100} : b } : c : Print alfa
}
b \\ print 100
set switches "+SEC"
Module b {
alfa=50 : Module c { Module b {alfa=100} : b } : c : Print alfa
}
b \\ print 50
An execution object, is an object created internally by interpreter to hold the code, and other information about a module or and function. Subroutines run in same execution object of caller. We say, we create variables, modules and functions, and then we use them. Subroutines are used by searching the code in module or function where we call it, so we say we use subs (subroutines), as private code. Searching happen the first time when we call a sub, and the next time interpreter finds the entry point using a hash table in a structure where source original code for current running entity stayed.
Three M2000 specific objects attached in an execution object. The stack object, for passing and returning values (named internal Soros, from Greek word Σωρός, and is a mStiva object, written in Vb6), the return stack for subroutines (is a mStiva object too), and a collection (a FastCollection object, a hash table (key/value) object) for static variables.
Interpretation in M2000 done in one pass, as code executed. Because
M2000 use curly brackets as block of code, interpreter can find a block
in advance, before execute it, and pass in a function which execute it.
So M2000 interpreter is a functional program which a block run in a
function, interpreter for statements/loops/flow structures run in a
function. Expressions Evaluators (there are some of them) run in
functions too (parsing and execution in one pass). All functions carry the execution object. plus a string
to interpret (the source, or part of source), and sometimes the lang flag, to indicate Greek or English statement (M2000 has two vocabularies for identifiers, a Greek and an English one). Some functions create
execution objects for threads. These threads stayed in a Task Manager,
when executed by specific interval. Threads are in the lexical scope of
the creator - or parent of thread-. Threads can be executed in a
sequential plan or in a concurrent plan. Block of code, in curly
brackets, inside thread always executed without stopping. In sequential
plan all thread code executed without stopping, except we want it, using
wait command. A thread has a handler, a number which task manager
define and export to a variable which we provide in a structure
Thread { code } as Alfa so
Alfa has the thread id or handler. Inside thread we can use This as a
replacement of id for statements about threads. When a thread run then
run as it is the Module's code, the module which create the thread. But
the execution object is different.
Source code for a module may have on to three copies, in any time:
- Exist as a definition in a source like Module Alfa { code lines }
- When interpret execute this definition create a lexical scope identifier (two parts, the current_name a dot and the defined nam, in the example Alfa) with value a structure, where one member is the original_code.
- When we call it a copy of the original_code passed to function Execute(), with a new execution object (and inform it where it found original_code if a label or a subroutine needed to found by the execution of code). Execution of a sub call is an internal process of Execute(), using execution object own return stack, so no real stack used for
Part of module
code can be used again
- A block of code in curly brackets executed by interpreter in a special function ExecuteBlock. This function executed in a Execute function and executes an Execute function. When a goto happen break the execution of Execute Function, search code in block for label, and if not found break continued
- Using Goto to a label (number or user identifier with a letter as first char). We can use On number Goto, but we can't use expression as number
- Using Gosub to a number or user identifier. Need to use Return (Return statement used without arguments for returning from routines, but there are other forms, using specific arguments to return values to fields in a database, and using an object, a container type, and specific arguments to return a series of values to container.
- Using Gosub (optional) and a name with parenthesis (with or without arguments) as a call to subroutine.
- Thread_code: is a module part of code that can be executed in intervals. We can use a thread loop using Thread.Main (is a thread too), or an Every milliseconds { code to execute } structure, or in a loop with Wait command. A thread may have own static variables (has own execution_object). In a thread we can define new variables for module.
- We can use Functions as substitutes of module part of code to service an event from Gui, or from a COM object.
- We can use Functions as substitutes of module part of code as CallBacks, passed as arguments, or using them in Event Object.
Example for Goto
M2000 code may have numbers as labels, but as labels there is no meaning of an order. Internal source editor insert automatic numbers if found number in left side of paragraph. In multiline string literal in curly brackets we have to erase numbers). If label not found then this is like an exit of module. Goto break Repeat {} Until, While {} and For .. to .. {}, but for For/ Next
(the slower version of For to {}) need to perform Exit For and place
the number (as label) to continue after exit.
In this example in line 107 a jump to 100 needed and internal Execute() return asking for the jump. Search for label happen from first line of block code. So here a label 100 exist after 107, so execution continue calling Execute cutting code from label 100. If we erase lines 105 and 140, so lines 107 and 100 are in same block as module code, then first 100 is after 090, so we get 4 times 100.
090
k=1
100
Print "100"
101
k++
102
if k>5
then exit
105
{
107
Goto 100
100
Print "100 inside block"
140
}
150
Print "ok"
Example of using a Gosub to define a variable;
If label didn't found in current block then interpreter look in above block, until stopped in module block.
010
{
020
Gosub 200
030
Print X
040
}
050
Exit
200
X=10
210
Return
Example using Gosub to call a subroutine.
When we call AnyName() a new z defined and shadow any z, including global one. When x assign a value then if x exist this is a new value, but if x not exist, then first created using this value. Because subs are in same lexical scope as the module where we call them, maybe x exist as local, so get new value. When we assign values to global variables we use <= and no =. When a sub exit then every new definition at the time of sub execution happen, erased. So z erased, but x in last call can't erased, because exist before the call. In first call, we have x definition because x not exist.
- Subroutines searched from bottom, using original_code.
- One only code may exist for a subroutine calling from a module/function/subroutine.
Print
valid(x) \\
0, x not exist
\\ Gosub is optional, we can call
AnyName() without it
\\ Except when exist array with same
name
Gosub
AnyName()
Print valid(x)
\\ 0, x not exist
Print
valid(z) \\
0, z not exist
x=10
Gosub
AnyName()
Print valid(z)
\\ 0, z not exist
Print
x \\ x =
100
\\ no need Exit or End, because Sub is like Exit
\\
Sub is used when search Anyname()
Sub
AnyName()
Local
z=200
x=100
End
Sub
Example for using thread to define a variable;
Special threads are Main.Task and After. There are also two internal type of threads, one for music playing notes and another for receiving data through a named pipe (a communication system for programs in M2000)
Print
Valid(x) \\
0 no, is not valid, x not exist
\\ After is a thread for
one execution only
After
20 {
x=10
}
Wait
100 \\ so thread run inside this time
frame
Print
x \\ now x
exist
For modules, functions and subroutines interpreter, before execute them, store the number of used variables, and number of used modules/functions and at the exist restore that number dropping every variables/module/function created. Exception for this rule, is the lifetime of static variables. Static variables are bind to execution object and before this object destroyed pass the static_bag, a Fastcollection object, to parent's execution object, and saved in own
static_bag, with a key to find it in a later call. Exception of this saving exist for threads. If a thread erased then
static_bag erased also.
Modules and Functions define own lexical scope, and except for special situations and calling ways, there is an isolation from parents/children, about definitions inside them. We can't call a module in a module form another module, we have to run module and then this module can run own inner module.
A thread can read variables of the same lexical scope as the module or
function which created (the thread creator), but can't read static
variables of the creator.
Modules and Functions can exist under:
- Global scope. So can be called from anywhere. A local definition shadow the global one. A global definition shadow global one
- Local scope. Parent is another module, or a function
- Group scope. Parent is a group
in example below, we use dynamic scoping. Module Delta call alfa two times, one time call the first Alfa, and the other time call the Alfa inside Beta. Dynamic scoping can be used when we have something global, or in a wide scope (as in subroutines, where module's variables/modules/functions are visible from subroutine)
Dynamic scoping example
Module
Global Alfa
{
Print
"I am global Alfa"
}
Module
Global Delta
{
\\ call
Alfa
Alfa
}
Module
Beta {
Module
Global Alfa
{
Print
"I am new Global
Alfa"
}
Alfa
Delta
}
Alfa \\ I
am global Alfa
Delta
\\ I am global Alfa
Beta \\
I am new Global Alfa, I am new Global Alfa
Alfa
\\ I am global Alfa
There is a way to change Alfa from Beta permanently. Set statement send line to command line interpreter. Script statement execute a module's code as script, and leave new definition. Set statement change temporary the lexical scope to global. Set g=10 make a g as global but if global exist then g get new value. Global g=10 always make a new variable and shadow the old one.
Module
Global Alfa
{
Print
"I am global Alfa"
}
Module
Global Delta
{
\\ call
Alfa
Alfa
}
Module
Beta {
Module
Global script1
{
Module
Alfa {
Print
"I am new Global Alfa"
}
}
Set
Script
script1
Alfa
Delta
}
Alfa \\ I
am global Alfa
Delta
\\ I am global Alfa
Beta \\
I am new Global Alfa, I am new Global Alfa
Alfa
\\ I am new Global Alfa
OOP in M2000, the basics
A group has the concept of an object. A group can be exist in one of two forms, the named one, and the float one. A named group can exist in any module/function or other group. A float group exist in containers, like arrays, inventories (key/value lists), stacks (objects which are implement both a LIFO and FIFO queues). Groups not expose pointers to them. So we can't assign a group to two names. We can assign reference to a name entity, as we can do for every name entity, except modules and subroutines, but including functions (we will see how a function can be passed by reference). We use groups as values only. We can make private and public members of a group. We can make operators for groups. A named group return a float group, as a copy, except we use Value member to return a specific value. We can't assign a float group to a named group, and assign symbol "=" works for merging. A group merge set same group members from float group to named group, and add members that named group haven't. Linking groups can be done using containers only, so pointers are indexes of an array if container is an array, or key if container is an inventory object, or position in stack object. A group may have containers inside and hold other groups, and may have inner groups in any level. When we assign a float group somewhere in a container the previous stored group erased.
Let see this example (suppose this code run in a module)
Group
Alfa {
Private:
x=10
Public:
a$="ok"
Module
Beta {
Print
.x
}
}
Print
Valid(Alfa.x),
Valid(Alfa.a$)
\\ 0 -1 (false true)
Print
Alfa.a$ \\
ok
Alfa.Beta \\
print 10
Beta=Alfa
For
Beta {
Print
.a$
.Beta
}
We make a group Alfa using Group statement, which have a block, with members, and specific labels. Label Private: sign that the following members are private, and label Public: sign that the following members are public. All members are in lexical scope of module using alfa dot and member name. Internal they have another name, the module name before with a dot. So if module is A then we get A.Alfa.a$ but not A.Alfa.x because there is character before x to indicate privacy. When we call Alfa.Beta (A.Alfa.Beta for interpreter), execution object created and informed that this module belong to Alfa. So when interpreter execute Print .x attach group name to .x and check if exist, or if not attach the character for privacy to check if private x exist.(We will see how interpreter store group info in a module/function).
We make a new group Beta through assigning Alfa to it. Interpreter first produce a float group as a copy of Alfa. A float group has a bag inside with all members. So at that point x, a$ and Beta are copied in float group. When interpreter finds a new name, like Beta, attach to it the
current_name for example we say A, so the name now is A.Beta and get what evaluator give, numeric value or object. Here we have an object, a float group. So interpreter create a new group with all members as float group has.
We can use a
For object block (this structure also erase all new entities), and we can use dot notation for accessing public members. A for object block may have inner for object blocks, or and a list of objects, and each added object has two or more dots:
For
Alfa, Beta
{
Print
.a$=..a$
}
For
Alfa {
For
Beta {
Print
.a$=..a$
}
}
We can use
List statement to see what variables we have (but we can't see the private members). We can use
Modules ? to see what modules/functions we have (but we can't see the private members.
How M2000 store Lexical Scoping entities
All variable's names are stored in a hash table, which has these features:
- Can get same keys
- Can delete only from the last to first
- Using a flag indicate if a key is a reference (to prevent interpreter to deleting of value)
- for each value there is an index to a Var() array (a variant type in Visual Basic 6)
A name reference to another name has a flag marked reference and an index same as the index of referenced name. There is a restriction, M2000 not allow to make a new reference in a name which exist. We can make a new reference with same name only if we make a second key with same name. References resolved only by Read and Link commands. We use &name to pass a value by reference and needed same symbol to parameter to get the reference:
In this example we use (&M) and interpreter copy that as Read &M as first line of module.
X=10
Module
inc (&M)
{
M++
}
Inc
&X
Print
X
\\ We can redefine inc (here is the same code as before)
Module
inc {
Read
&M
M++
}
inc
&X
Print
X
All module's/function's name are stored in another hash table, which as these features:
- Can get same keys
- Can delete only from the last to first
There is no reference flag. So how a function can be referenced? (we will see below).
All modules/functions written in a sbf() array of structure moffun
This is a part of VB 6 code of M2000 interpreter. Source code copied in sb field, subs is a hash table for subs, sbgroup is the absolute name of group which module exist (and with that can read the object members). Field sbc used by editor to know the last cursor position. Field Locked used when hidden code is loaded, to prevent editing. (hidden code is a text scrabbled code)
Public Type modfun
sb As String
sbc As Long
sbgroup As String
subs As FastCollection
locked As Boolean
End Type
Private sbf() As modfun
Weak References
All variable references are weak types, they are absolute names, as interpreter find them, and these are strings, so we can print them: If x exist in module then Print &x display the absolute name (including any part that we didn't see in code). When Read statement asked to take a reference then expect to find a string value and check if exist in variable's hash table, and if is ok then perform the linking (or referencing) else through an error.
Normal functions can be passed by reference. But functions are not in variables array var(), except for lambda functions (these are objects). Functions converted to anonymous function and pass them as source. Conversion is a bit easy. Interpreter insert curly brackets as first and last char, to form a block and that is. So if we have function Alfa() then Print &Alfa() print the code of function. When a Read &Beta() executed a string expected as weak reference, and if it is a string then we may have a reference to array or a reference to function. If it is a reference to function then first character is the left curly bracket. Because we can pass a reference to a function of a group, interpreter put after right curly bracket the sbgroup field of function. So when Read make a new function in a new lexical scope, enter the sbgroup from where dotted entities inside function resolved to absolute names of members of group:
Group
Alfa
{
Private:
x=10
Public:
n_last
Function
ReadX
(.n_last){
=.x*.n_last
}
}
Module
Kappa (&A()){
Print
A(5)
}
Kappa &Alfa.ReadX()
Print
Alfa.n_last \\
5
Until now we have see that values can be hold in an internal array var(), in execution objects (for static variables), and in float groups. We can say that internal array can be used for globals and module level variables. A static variable can't be referenced because interpreter can't find it in var() through hash table for variables. Static variables can be numbers, strings or pointer to container only. There is a garbage collector only for containers. Groups no need garbage collector (although members of groups maybe pointers to containers). How we can use values from float groups in an container, say an array? Can we get reference?
Using float groups
A float group can be named, which means that can be written in var() and sbf() arrays (all members created as individual entities, and modules/functions get the sbgroup with the absolute name of named group). When we done with the named group, the group float again and take place where we get it. Name for named group use automatic the interpreter. So we don't now the name, or we don't care about it.
In the example below, we make a special function named Class. This function return a float group (we can use constructor as Module Alfa inside group, and that module called as function before deliver the float group. We make an array as A(10). Array is base 0 by default, but we can change it, globally with Base 1, or for this array using Dim Base 1, A(10). Arrays can be hold any value/object, but we can't read strings, we have to use A$() for reading, or we can link a reference using Link A() to A$(), so A$() reference the same array object, mArray inside M2000 interpreter code.
Class
Alfa
{
Private:
x=10
Public:
n_last
Function
ReadX
(.n_last){
=.x*.n_last
}
}
Module
Kappa (&A()){
Print
A(5)
}
Dim
A(10)
A(3)=Alfa()
For A(3)
{
Kappa
&.ReadX()
}
Print
A(3).n_last
\\ 5
Using For A(3) {} interpreter open A(3) with a hidden name, and at the exit make it float again, and put it in A(3). The hidden group name, and all members erased. The same we have in Print A(3).n_last but here we don't include statements.
We can add these lines:
Print
A(3).ReadX(30)
\\ 300
Print
A(3).n_last
\\ 30
Or using Weak$() to get a weak reference to A(3) (this reference can be resolved from Eval() function. Arrays can be referenced, but members only using weak$() and Eval() for reading.
w$=weak$(A(3))
Print
Eval(w$.ReadX(50))
\\ 500
Print
Eval(w$.n_last)
\\ 50
We can use weak reference to pass in same module array item or a variable. Here we use a lambda function (an object with closures and a function) to generate a series of numbers feeding array (using << operator). Print statement can be used to print all members of containers (objects in containers are printed with a space, and each item printed in columns, and more lines if needed). Function Weak$() if found array evaluates all indexes and then pass numbers only.
We see that Lambda N has a closure X this is stored in lambda object and when lambda function executed, X is written in var(), and at the exit written back to object. Lambda functions can be members of groups, and can be written in containers. N can be assign to a new lambda. It is not a pointer to lambda. A M=N make a M as a copy of N (so we can hold state of X in M).
N=Lambda
X=1
->{
=X
X++
}
Dim
Base 1,
A(20)<<N()
Print
A() ' 1 .. 20
Module
PassWeak (w$)
{
w$.+=10
Print Eval(w$.)
\\ same as Eval(w$)
}
Print
A(3)
\\3
PassWeak
Weak$(A(3))
Print
A(3) \\
3
X=10
PassWeak
Weak$(X)
Print
X \\
20
PassWeak &X
Print
X \\ 30
SuperClass for Groups
latest version 9 (and some last revisions of version 8) have the SuperClass. A super class is a group with a float group and no members. Each group may have a pointer to a super class (and each inner group may have pointer to some other super class). When we perform a merging of B group to A group, eg A=B we merge the superclass too. If B is a superclass then A has anything that super class have except those members that are marked unique which are unique for superclass. (need interpreter 9.0, revision >=14)
In example below, we make a superclass Alfa with a unique counter. Each group made from alfa get all private and public members, but not the unique members. Alfa is an object typed Group which point to superclass object, with no members. We can't use members of superclass using Alfa. We can only copy Alfa to create new objects. Also new objects from alfa can be create other objects using the same superclass. To use the Alfa superclass we have to prepare a function which can hold Alfa, as a closure. This can be done with a lambda function. We want to increase the counter, the unique member of Alfa.
A lambda functions has label
Lambda then a part of closures, can be visible variables, or can be new variables. In any way we get a copy. Here we save alfa in M, and we read an argument in M.name$.
K1 and K4 are groups made it from SuperAlfa(), the function part of lambda SuperAlfa. K2 and K5 are groups as second generation. K3 is a third generation group. Each generation prepare the same "type" of group. We can check if a group has same "type": Print
Valid(@K2
as Alfa) return -1
A=(K1,
K2, K3,
K4,
K5) is a statement which create A as a pointer to array. Array (,) is an empty one, a (1,) is one element array, and in the example we make a five element array, each one has a copy of Kn (n=1 to 5).
Normally K1 return This, but we use Value { } member with a parameter and with no parameter. With no parameter is like K1 has no Value {member} and return a copy of itself (a copy of this). With an alphanumeric we get a new object, increment the superclass counter.
Ait is an iterator. An iterator has a cursor, a starting value, and an ending value, plus a link to object. Ait expose two things, the iterator object as Ait, and the cursor (read only) as Ait^, but here we use only Ait object. A while iterator {} block use the iterator, and we can change it inside, changing the starting value, by using Ait^ and a step, so with this way we can use steps bigger than one.
In super class we can define a Set {} member. Here we say that Set {} only read this, which means merge to this, the object from stack (for parameters).
Superclass Alfa
{
unique:
counter=0
public:
name$,
mynumber
Function
Counter {
For
SuperClass
{
=.counter
}
}
Module
AddOne {
For
SuperClass
{
.counter++
}
}
\\
we can leave set to merge only
Set
{ read this}
Value
() {
If
match("S")
then {
Read
Name$
For
this
{
M=This
M.name$<=name$
M.AddOne
M.mynumber<=M.counter()
=M
}
}
else =This
}
}
SuperAlfa
= lambda
M=alfa
(M.name$)
->
{
M.Addone
M.mynumber=M.counter()
=M
}
K1=SuperAlfa("George")
K2=K1("Peter")
K3=K2("Chris")
K4=SuperAlfa("Alex")
K5=K1("Thomas")
A=(K1,
K2, K3,
K4,
K5)
Ait=Each(A)
While
Ait {
kk=Array(Ait)
For
KK {
Print
.name$,
.mynumber,
.counter()
'
George 1 5
'
Peter 2 5
'
Chris 3 5
'
Alex 4 5
'
Thomas 5 5
}
}
Events store functions (as code only). In this example we make Event alfa. We write a signature (Read &K, z) and we can program a default function as member function {} (no need to write Read again).
Next, we male group B, with a Public function as Delegate1 with same signature. Also this group has another inner group as property, which we can read only value. We need to link parent K to K, (this can be done at execution only)
\\ Event-Driven
Programming
Event
alfa {
Read
&K,
z
function
{
Print
K,
z
}
}
Group b
{
Private:
K=10
Public:
Function
Delegate1 (&M,
N)
{
'
this is an event handler
' called
with returning something
If
.K>0
then .K--
: M+=1+N
}
Group
GetK {
value
{
link
parent K to
K
=K
}
}
}
d=b
\\ we get a copy
\\Event
alfa new &B.Delegate1()
\\ Now we import
references to alfa event
Event
alfa new
&B.Delegate1(),
&B.Delegate1(),
&D.Delegate1()
z=0
\\
now we can call event 10 times.
For
i=1 to
10 : Call Event
alfa, &z,
i : Next
i
Print
z, B.GetK,
D.GetK \\
105, 0, 0
We can define operators for groups (Example Rev. 2 ). From 9.2 revision 3 we have change the order in pairs, for parameters in operators. (operators from 9.2 rev 3 works perfect)
Also this example uses sign for negatives. We can call operators inside a group using Call Operator. Also there is a Unary operator too.
form 80,50Class Num { Val$="0" Sign$ Module final Num { If Match("S") then { Read .Val$ if left$(.Val$,1)="-" then .Sign$<="-" : .Val$<=Mid$(.Val$, 2) } Else { .Val$<=Str$(Number,"0") } } Module final Complement { read many b$="" : m%=1 If many>len(.Val$) then .Val$<=string$("0",many-len(.Val$))+.Val$ For i=len(.Val$) to 1 { m%=9-val(mid$(.Val$, i,1))+m% mm%=m%-10 if m%<10 then { b$=str$(m%,"0")+b$ : m%=0 } else b$=str$(mm%,"0") +b$: m%=1 } .Val$<=Right$(b$, many) } Operator final "=" { Read A Push A.Val$=.Val$ and A.Sign$=.Sign$ } Operator final "<" { Read A if .Sign$=A.Sign$ then { If Len(A.Val$)=Len(.Val$) then { Push .Val$>A.Val$ xor .Sign$="" } Else.If Len(A.Val$)<Len(.Val$) then { Push .Sign$<>"" } Else Push .Sign$="" } Else Push .Sign$>A.Sign$ } Operator final "<=" { Read A if .Sign$=A.Sign$ then { If Len(A.Val$)=Len(.Val$) then { Push .Val$>=A.Val$ xor .Sign$="" } Else.If Len(A.Val$)<Len(.Val$) then { Push .Sign$<>"" } Else Push .Sign$="" } Else Push .Sign$>A.Sign$=0 } Operator final ">" { Read A if .Sign$=A.Sign$ then { If Len(A.Val$)=Len(.Val$) then { Push .Val$<A.Val$ xor .Sign$="" } Else.If Len(A.Val$)<Len(.Val$) then { Push .Sign$="" } Else Push .Sign$<>"" } Else Push .Sign$<A.Sign$ } Operator final ">=" { Read A if .Sign$=A.Sign$ then { If Len(A.Val$)=Len(.Val$) then { Push .Val$<=A.Val$ xor .Sign$="" } Else.If Len(A.Val$)<Len(.Val$) then { Push .Sign$="" } Else Push .Sign$<>"" } Else Push .Sign$<A.Sign$ } Operator final "<>" { Read A Push Not (A.Val$=.Val$ and A.Sign$=.Sign$) } Operator final Unary { if .Sign$<>"-" then { if .val$<>"0" then .Sign$<="-" } else .Sign$<="" } Operator final "-" { Read b if .val$<>"0" then b.sign$<= If$( b.sign$="-" ->"","-") Call Operator "+", b } Operator final "+" { Read B if B.val$="0" then exit Dim Base 0, M$(Max.Data(len(.Val$)+1,Len(B.Val$)+1)+1) def shiftone as boolean If len( B.Val$)<len(.Val$) then { swap .Val$, B.Val$ : swap .Sign$, B.Sign$ } Else.if len( B.Val$)=len(.Val$) Then { If B.Val$<.Val$ then swap .Val$, B.Val$ : swap .Sign$, B.Sign$ } if .Sign$<>B.Sign$ then .complement Len(B.Val$) : shiftone=true M%=0 ofs=Len(B.Val$)-Len(.Val$) if ofs<0 then error "??????" For I=Len(.Val$) to 1 { M%=M%+Val(Mid$(.Val$, I,1))+Val(Mid$(B.Val$, I+ofs,1)) M$(I+ofs+1)=Str$(M% mod 10,"0") M%=-(M%>9) } If M%>0 Then { While ofs>0 { M%=M%+Val(Mid$(B.Val$, ofs,1)) M$(I+ofs)=Str$(M% mod 10,"0") M%=-(M%>9) ofs-- } If M%>0 Then { M$(0)=Str$(M% mod 10,"0") } } F$="" For i=0 to Dimension(M$(),1)-1 { F$=F$+M$(i) } If ofs>0 Then { .Num Left$(B.Val$,ofs)+F$ } Else .Num F$ if shiftone then { For this { Many=1 i=len(.Val$) If i>many then { while many<i { If mid$(.val$,many+1,1)<>"0" then exit many++ } if many=i then many-- .val$<=mid$(.val$, many+1) } .sign$<=If$(.val$="0"->"",B.sign$) } } }}
Function Place$(A, X=20) { =Right$(String$(" ",X)+A.Sign$+A.Val$,X)}A=Num("20")B=Num("3")F=A-B \\ 17F=F+Num(50) \\ 67Print Place$(F,len(A.Val$)+2)A=Num("999999999999999999999999999999999999999999999999999999999999999999999999")B=Num("3")M=Num(50)F=(A-B)+MPrint Place$(F,len(A.Val$)+2)Print Num(5)>Num(3) '' -1Print Num("5")>Num("12313") '' 0Print Place$(Num("5")+Num("10")+Num("150"), 20) ' 165A=Num("99999")B=Num("100001")F=A+BPrint Place$(F,len(F.Val$)+2)Print F>AM=A+BPrint Place$(A+B-M)Print Place$(B+A-M)Print Place$(A-M+B)Print Place$(B-M+A)Print Place$(A+(B-M))Print Place$(B+(A-M))Print Place$((A-M)+B)Print Place$((B-M)+A)Print Place$(A+(-M)+B)Print Place$(B+(-M)+A)
We can write some statements without block { } if we don't use Goto, This is the class Num. Look the code, We use If Then End without blocks. We have to find the End If or an error Missing End If produced. We can use break, continue, restart, exit but these have effect on a block (so if no block { } defined somewhere in module, the module is the only block of code, and considered as the top block). Exit break one block, Break break all blocks including last, the top block, unless find the Try {} which stop break. A For Next also has a hidden block, so now is slight slower from For { }, we can use goto to jump and finish the loop to desired label. Look the For object_type_of_group { } is not a loop block, just make the object methods and properties used by dot(s).
While End While has a hidden block so we can use goto.
Also after class definition we have an one line definition for function using Def statement/
Class Num
{ Val$
="0" Sign$
Module final Num
{ If Match("S") Then Read .Val$
If left$(.Val$
,1)="-" Then .Sign$
<="-" : .Val$
<=Mid$(.Val$
, 2) Else .Val$
<=Str$(Number,"0") End If } Module final Complement
{ read many
b$
="" : m%
=1 If many
>len(.Val$
) Then .Val$
<=string$("0",many
-len(.Val$
))+.Val$
For i
=len(.Val$
) to 1 m%
=9-val(mid$(.Val$
, i
,1))+m%
: mm%
=m%
-10 If m%
<10 Then b$
=str$(m%
,"0")+b$
: m%
=0 Else b$
=str$(mm%
,"0") +b$
: m%
=1 End If Next .Val$
<=Right$(b$
, many
) } Operator final "=" { Read A
Push A.Val$
=.Val$
and A.Sign$
=.Sign$
} Operator final "<" { Read A
If .Sign$
=A.Sign$
Then If Len(A.Val$
)=Len(.Val$
) Then Push .Val$
>A.Val$
xor .Sign$
="" Else.If Len(A.Val$
)<Len(.Val$
) Then Push .Sign$
<>"" Else Push .Sign$
="" End if Else Push .Sign$
>A.Sign$
End if } Operator final "<=" { Read A
If .Sign$
=A.Sign$
Then If Len(A.Val$
)=Len(.Val$
) Then Push .Val$
>=A.Val$
xor .Sign$
="" Else.If Len(A.Val$
)<Len(.Val$
) Then Push .Sign$
<>"" Else Push .Sign$
="" End If Else Push .Sign$
>A.Sign$
=0 End If } Operator final ">" { Read A
If .Sign$
=A.Sign$
Then If Len(A.Val$
)=Len(.Val$
) Then Push .Val$
<A.Val$
xor .Sign$
="" Else.If Len(A.Val$
)<Len(.Val$
) Then Push .Sign$
="" Else Push .Sign$
<>"" End If Else Push .Sign$
<A.Sign$
End If } Operator final ">=" { Read A
If .Sign$
=A.Sign$
Then If Len(A.Val$
)=Len(.Val$
) Then Push .Val$
<=A.Val$
xor .Sign$
="" Else.If Len(A.Val$
)<Len(.Val$
) Then Push .Sign$
="" Else Push .Sign$
<>"" End if Else Push .Sign$
<A.Sign$
End If } Operator final "<>" { Read A
Push Not (A.Val$
=.Val$
and A.Sign$
=.Sign$
) } Operator final Unary { If .Sign$
<>"-" Then If .val$
<>"0" Then .Sign$
<="-" Else .Sign$
<="" End If } Operator final "-" { Read b
If .val$
<>"0" Then b.sign$
<= If$( b.sign$
="-" ->"","-") Call Operator "+", b
} Operator final "+" { Read B
if B.val$
="0" then exit Dim Base 0, M$(
Max.Data(len(.Val$
)+1,Len(B.Val$
)+1)+1)
def shiftone
as boolean If len( B.Val$
)<len(.Val$
) then swap .Val$
, B.Val$
: swap .Sign$
, B.Sign$
Else.if len( B.Val$
)=len(.Val$
) Then If B.Val$
<.Val$
then swap .Val$
, B.Val$
: swap .Sign$
, B.Sign$
end if if .Sign$
<>B.Sign$
then .complement
Len(B.Val$
) : shiftone
=true M%
=0 ofs
=Len(B.Val$
)-Len(.Val$
) if ofs
<0 then error "??????" For I
=Len(.Val$
) to 1 M%
=M%
+Val(Mid$(.Val$
, I
,1))+Val(Mid$(B.Val$
, I
+ofs
,1)) M$(I
+ofs
+1)
=Str$(M%
mod 10,"0") : M%
=-(M%
>9) next If M%
>0 Then While ofs
>0 M%
=M%
+Val(Mid$(B.Val$
, ofs
,1)) M$(I
+ofs)
=Str$(M%
mod 10,"0") M%
=-(M%
>9) : ofs
-- end while If M%
>0 Then M$(
0)
=Str$(M%
mod 10,"0") end if end if F$
="" For i
=0 to Dimension(M$()
,1)-1 F$
=F$
+M$(i)
next If ofs
>0 Then .Num
Left$(B.Val$
,ofs
)+F$
Else .Num F$
end if if shiftone
then For this { Many
=1 i
=len(.Val$
) If i
>many
then while many
<i
If mid$(.val$
,many
+1,1)<>"0" then exit many
++ End While if many
=i
then many
-- .val$
<=mid$(.val$
, many
+1) end if .sign$
<=If$(.val$
="0"->"",B.sign$
) } end if }}
Def Place$(A, X=20)=Right$(String$(" ",X)+A.Sign$+A.Val$,X)