Full Code of lboasso/oberonc for AI

master 46647de07d45 cached
322 files
386.4 KB
137.0k tokens
113 symbols
1 requests
Download .txt
Showing preview only (448K chars total). Download the full file or copy to clipboard to get everything.
Repository: lboasso/oberonc
Branch: master
Commit: 46647de07d45
Files: 322
Total size: 386.4 KB

Directory structure:
gitextract_k_kcp7o4/

├── LICENSE.txt
├── Makefile
├── README.md
├── bin/
│   ├── ClassFormat.smb
│   ├── CpCache.smb
│   ├── Files.smb
│   ├── In.smb
│   ├── Math.smb
│   ├── OJB.smb
│   ├── OJG.smb
│   ├── OJP.smb
│   ├── OJS.smb
│   ├── Opcodes.smb
│   ├── Os.smb
│   ├── Out.smb
│   ├── Strings.smb
│   └── oberonc.smb
├── doc/
│   ├── TypeRules.md
│   └── oberon07.g
├── examples/
│   ├── GuessNumber.Mod
│   ├── Hello.Mod
│   ├── Powers.Mod
│   ├── PrimeNumbers.Mod
│   ├── Util.Mod
│   └── fern/
│       ├── IFS.Mod
│       ├── RandomNumbers.Mod
│       ├── XYplane.Mod
│       └── java/
│           └── XYplane.java
├── make.bat
├── src/
│   ├── ClassFormat.Mod
│   ├── CpCache.Mod
│   ├── Files.Mod
│   ├── In.Mod
│   ├── Math.Mod
│   ├── OJB.Mod
│   ├── OJG.Mod
│   ├── OJP.Mod
│   ├── OJS.Mod
│   ├── Opcodes.Mod
│   ├── Os.Mod
│   ├── Out.Mod
│   ├── Strings.Mod
│   ├── java/
│   │   ├── Files.java
│   │   ├── Files_FileDesc.java
│   │   ├── In.java
│   │   ├── Math.java
│   │   ├── OberonRuntime.java
│   │   ├── Os.java
│   │   └── Out.java
│   └── oberonc.Mod
└── tests/
    ├── TestRunner.java
    └── base/
        ├── ArrayAssignment.Mod
        ├── ArrayAssignment.txt
        ├── ArrayConstantSize.Mod
        ├── Arrays2.Mod
        ├── Arrays2.txt
        ├── Arrays3.Mod
        ├── Arrays3.txt
        ├── BitFunc.Mod
        ├── BitFunc.txt
        ├── CaseChar0.Mod
        ├── CaseChar0.txt
        ├── CaseNum0.Mod
        ├── CaseNum0.txt
        ├── CaseNum1.Mod
        ├── CaseNum1.txt
        ├── CaseRecord0.Mod
        ├── CaseRecord0.txt
        ├── CaseRecord1.Mod
        ├── CaseRecord1.txt
        ├── CaseRecord2.Mod
        ├── CaseRecord2.txt
        ├── CaseRecord3.Mod
        ├── CaseRecord3.txt
        ├── CaseRecord4.Mod
        ├── CaseRecord4.txt
        ├── CommutativeSwap.Mod
        ├── CommutativeSwap.txt
        ├── ConstantFoldingAndLoadOp.Mod
        ├── ConstantFoldingAndLoadOp.txt
        ├── DivMul.Mod
        ├── DivMul.txt
        ├── EmptyArrayAndRecord.Mod
        ├── EmptyArrayAndRecord.txt
        ├── ExtTypes.Mod
        ├── ForwardPointerRef.Mod
        ├── ForwardPointerRef.txt
        ├── Fractions.Mod
        ├── Fractions.txt
        ├── FragileBaseClass.Mod
        ├── FragileBaseClass.txt
        ├── MagicSquares.Mod
        ├── MagicSquares.txt
        ├── OpenArrays.Mod
        ├── OpenArrays.txt
        ├── OpenArrays2.Mod
        ├── OpenArrays2.txt
        ├── OpenArrays3.Mod
        ├── OpenArrays3.txt
        ├── Out0.Mod
        ├── Out0.txt
        ├── Out1.Mod
        ├── Out1.txt
        ├── Out2.Mod
        ├── Out2.txt
        ├── Out3.Mod
        ├── Out3.txt
        ├── Out4.Mod
        ├── Out4.txt
        ├── Out5.Mod
        ├── Out5.txt
        ├── Out6.Mod
        ├── Out6.txt
        ├── OutTest.Mod
        ├── OutTest.txt
        ├── Pattern1.Mod
        ├── Pattern1.txt
        ├── Pattern2a.Mod
        ├── Pattern2a.txt
        ├── Pattern2b.Mod
        ├── Pattern2b.txt
        ├── Pattern2c.Mod
        ├── Pattern2c.txt
        ├── Permutations.Mod
        ├── Permutations.txt
        ├── Powers.Mod
        ├── Powers.txt
        ├── PrimeNumbers.Mod
        ├── PrimeNumbers.txt
        ├── ProcComparisons.Mod
        ├── ProcComparisons.txt
        ├── ProcType.Mod
        ├── ProcType.txt
        ├── ProcVariables0.Mod
        ├── ProcVariables0.txt
        ├── ProcVariables1.Mod
        ├── ProcVariables1.txt
        ├── ProcVariables2.Mod
        ├── ProcVariables2.txt
        ├── ProcVariables3.Mod
        ├── ProcVariables3.txt
        ├── ProcVariables4.Mod
        ├── ProcVariables4.txt
        ├── ProcVariables5.Mod
        ├── ProcVariables5.txt
        ├── ProcVariables6.Mod
        ├── ProcVariables6.txt
        ├── ProcVariables7.Mod
        ├── ProcVariables7.txt
        ├── RealExpressions.Mod
        ├── RealExpressions.txt
        ├── RecordAndTypeExtension.Mod
        ├── RecordAndTypeExtension.txt
        ├── RecordAssignment.Mod
        ├── RecordAssignment.txt
        ├── RecordAssignment2.Mod
        ├── RecordAssignment2.txt
        ├── RecordParam.Mod
        ├── RecordParam.txt
        ├── Samples0.Mod
        ├── Samples0.txt
        ├── Samples1.Mod
        ├── Samples1.txt
        ├── Samples2.Mod
        ├── Samples2.txt
        ├── SetTest.Mod
        ├── SetTest.txt
        ├── Strings0.Mod
        ├── Strings0.txt
        ├── Strings1.Mod
        ├── Strings1.txt
        ├── Strings2.Mod
        ├── Strings2.txt
        ├── TestABS.Mod
        ├── TestABS.txt
        ├── TestAnonymousName.Mod
        ├── TestAnonymousName.txt
        ├── TestAssert.Mod
        ├── TestAssert.txt
        ├── TestAssignmentMix.Mod
        ├── TestAssignmentMix.txt
        ├── TestByteType.Mod
        ├── TestByteType.txt
        ├── TestCPS.Mod
        ├── TestCPS.txt
        ├── TestCmdLineArgs.Mod
        ├── TestCmdLineArgs.txt
        ├── TestConstFunc.Mod
        ├── TestConstFunc.txt
        ├── TestCyclicImport00A.Mod
        ├── TestCyclicImport00B.Mod
        ├── TestCyclicImport01A.Mod
        ├── TestCyclicImport01B.Mod
        ├── TestCyclicImport10A.Mod
        ├── TestCyclicImport10B.Mod
        ├── TestCyclicImport11.Mod
        ├── TestCyclicImport12.Mod
        ├── TestEqualSignature00.Mod
        ├── TestEqualSignature00.txt
        ├── TestExprVarPar.Mod
        ├── TestFor.Mod
        ├── TestFor.txt
        ├── TestFor1.Mod
        ├── TestFor1.txt
        ├── TestFunction0.Mod
        ├── TestFunction0.txt
        ├── TestINC0.Mod
        ├── TestINC0.txt
        ├── TestINC1.Mod
        ├── TestINC1.txt
        ├── TestINCLAndEXCL.Mod
        ├── TestINCLAndEXCL.txt
        ├── TestImport00.Mod
        ├── TestImport00.txt
        ├── TestImport01.Mod
        ├── TestImport01.txt
        ├── TestImport10.Mod
        ├── TestImport10.txt
        ├── TestImport100.Mod
        ├── TestImport100.txt
        ├── TestImport11.Mod
        ├── TestImport11.txt
        ├── TestImport110.Mod
        ├── TestImport110.txt
        ├── TestImport111.Mod
        ├── TestImport112.Mod
        ├── TestImport112.txt
        ├── TestImport120.Mod
        ├── TestImport120.txt
        ├── TestImport121.Mod
        ├── TestImport121.txt
        ├── TestImport122.Mod
        ├── TestImport122.txt
        ├── TestImport130.Mod
        ├── TestImport130.txt
        ├── TestImport131.Mod
        ├── TestImport131.txt
        ├── TestImport140.Mod
        ├── TestImport140.txt
        ├── TestImport141.Mod
        ├── TestImport141.txt
        ├── TestImport142.Mod
        ├── TestImport142.txt
        ├── TestImport150.Mod
        ├── TestImport150.txt
        ├── TestImport151.Mod
        ├── TestImport151.txt
        ├── TestImport20.Mod
        ├── TestImport20.txt
        ├── TestImport21.Mod
        ├── TestImport21.txt
        ├── TestImport22.Mod
        ├── TestImport22.txt
        ├── TestImport30.Mod
        ├── TestImport30.txt
        ├── TestImport31.Mod
        ├── TestImport31.txt
        ├── TestImport40.Mod
        ├── TestImport40.txt
        ├── TestImport41.Mod
        ├── TestImport41.txt
        ├── TestImport42.Mod
        ├── TestImport42.txt
        ├── TestImport50.Mod
        ├── TestImport50.txt
        ├── TestImport51.Mod
        ├── TestImport51.txt
        ├── TestImport52.Mod
        ├── TestImport52.txt
        ├── TestImport53.Mod
        ├── TestImport53.txt
        ├── TestImport60.Mod
        ├── TestImport60.txt
        ├── TestImport61.Mod
        ├── TestImport61.txt
        ├── TestImport62.Mod
        ├── TestImport62.txt
        ├── TestImport70.Mod
        ├── TestImport70.txt
        ├── TestImport71.Mod
        ├── TestImport71.txt
        ├── TestImport80.Mod
        ├── TestImport80.txt
        ├── TestImport81.Mod
        ├── TestImport81.txt
        ├── TestImport82.Mod
        ├── TestImport82.txt
        ├── TestImport90.Mod
        ├── TestImport90.txt
        ├── TestImport91.Mod
        ├── TestImport91.txt
        ├── TestMath.Mod
        ├── TestMath.txt
        ├── TestNestedProcs.Mod
        ├── TestNestedProcs.txt
        ├── TestODD.Mod
        ├── TestODD.txt
        ├── TestOOP.Mod
        ├── TestOOP.txt
        ├── TestReadOnlyPar.Mod
        ├── TestReturn0.Mod
        ├── TestReturn0.txt
        ├── TestShift.Mod
        ├── TestShift.txt
        ├── TestStringsMod.Mod
        ├── TestStringsMod.txt
        ├── TestSystemVal.Mod
        ├── TestSystemVal.txt
        ├── TestTypeConvFun.Mod
        ├── TestTypeConvFun.txt
        ├── TestTypeGuardExt.Mod
        ├── TestTypeGuardExt.txt
        ├── TestTypeTest.Mod
        ├── TestTypeTest.txt
        ├── UTF8String.Mod
        ├── UTF8String.txt
        ├── UniqueTypeAndProcNames.Mod
        ├── UniqueTypeAndProcNames.txt
        ├── VarInit.Mod
        ├── VarInit.txt
        ├── VarParGuard.Mod
        └── VarParGuard.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE.txt
================================================
MIT License

Copyright (c) 2017 Luca Boasso

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
.POSIX:
.SUFFIXES:

JAVA_SOURCES = src/java/Files_FileDesc.java src/java/Files.java \
               src/java/OberonRuntime.java src/java/Os.java src/java/Out.java \
               src/java/In.java src/java/Math.java
MOD_SOURCES = src/Out.Mod src/Os.Mod src/Files.Mod src/Strings.Mod src/OJS.Mod \
              src/CpCache.Mod src/Opcodes.Mod src/ClassFormat.Mod src/OJB.Mod \
              src/OJG.Mod src/OJP.Mod src/oberonc.Mod src/In.Mod src/Math.Mod

OBERON_BIN = ./bin

build:
	mkdir -p out/
	javac -d out $(JAVA_SOURCES)
	OBERON_BIN=${OBERON_BIN} java -cp $(OBERON_BIN) oberonc out $(MOD_SOURCES)

bootstrap:
	javac -d bin $(JAVA_SOURCES)
	OBERON_BIN=${OBERON_BIN} java -cp $(OBERON_BIN) oberonc bin $(MOD_SOURCES)

bootstrapTest:
	rm -rf bootstrapOut/
	mkdir -p bootstrapOut/
	OBERON_BIN=${OBERON_BIN} java -cp $(OBERON_BIN) oberonc bootstrapOut $(MOD_SOURCES)
	sha1sum -b bootstrapOut/* > sha1sums0.txt
	sed s/bootstrapOut/bin/ sha1sums0.txt > sha1sums1.txt
	sha1sum -c --quiet sha1sums1.txt
	rm sha1sums0.txt sha1sums1.txt

runFern:
	rm -rf examples/fern/out/
	mkdir -p examples/fern/out/
	javac -cp $(OBERON_BIN) -d examples/fern/out examples/fern/java/*.java
	OBERON_BIN=${OBERON_BIN} java -cp $(OBERON_BIN) oberonc examples/fern/out \
	  examples/fern/RandomNumbers.Mod \
	  examples/fern/XYplane.Mod examples/fern/IFS.Mod
	java -cp $(OBERON_BIN):examples/fern/out IFS

test:
	rm -rf tests/out/
	mkdir -p tests/out/
	javac -cp $(OBERON_BIN) -d tests/out tests/TestRunner.java
	OBERON_BIN=${OBERON_BIN} java -Dfile.encoding=UTF-8 -cp $(OBERON_BIN):tests/out TestRunner

clean:
	rm -rf out/ tests/out/ bootstrapOut/ examples/fern/out/


================================================
FILE: README.md
================================================

# Oberon-07 compiler

`oberonc` is a single pass, self-hosting compiler for the
[Oberon-07](https://en.wikipedia.org/wiki/Oberon_(programming_language))
programming language. It targets the Java Virtual Machine (version >= 1.8).

This project was started to showcase Niklaus Wirth's approach to writing
compilers (see
["Compiler Construction -
The Art of Niklaus Wirth"](https://github.com/lboasso/oberonc/blob/master/doc/Moe00b.pdf)
by Hanspeter Mössenböck for more details).

`oberonc` is inspired by Niklaus Wirth's compiler for a RISC processor available
[here](http://www.inf.ethz.ch/personal/wirth/).

The compiler is compact and does not depend on any third party libraries. It
produces Java bytecode in one pass while parsing the source file. Although
generating code for a stack machine is straightforward, this task is exacerbated
by a complex class file format and the fact that the JVM was designed with the
Java language in mind. In fact the JVM lacks many of the primitives required to
support Oberon's features, specifically:

* value types
* pass by reference evaluation strategy
* procedure variables (pointer to functions) and relative structural
  compatibility of types

Implementing those features with workarounds increased significantly the size
of the compiler, totaling roughly 6000 lines of Oberon.

The source code is written following as much as possible Niklaus Wirth's
coding style. `oberonc` compile itself in less than 300 ms on an old
Intel i5 @ 2.80GHz (~ 100 ms with a hot VM).

## How to build

You can build the compiler on Linux or Windows, you need a JDK >= 1.8
installed, with java and javac in the environment path.

Because you need an Oberon compiler to compile the sources in `src`, I have
added to the repository the binaries of the compiler to perform the
bootstrapping.

By typing `make build` on the shell, the compiler will compile itself and
write the files in the `out` folder. The `make bootstrap` command is equivalent
to `make build`, but it overwrites the files in the `bin` folder.

To run the compiler, you need to have the OBERON_BIN environmental variable set
to the `bin` folder of the repository. This is taken care for you when
using `make`.

## How to run the tests

One typical test is to make sure that, by compiling the compiler, we get the
same (bit by bit) class files originally included in the `bin` folder.
To run this test simply type `make bootstrapTest` (available only on Linux).
This will compile the sources into the `bootstrapOut` folder and compare these
resulting class files with the ones in `bin`. If something goes wrong
`sha1sums` will complain.

To run the tests included in the `tests` folder, type `make test`. The output
should look like this:

    ...
    TOTAL: 101
    SUCCESSFUL: 101
    FAILED: 0

## Using the compiler

To use the compiler, you need to have the OBERON_BIN environmental variable set
to the `bin` folder of the repository, for example on Linux
`export OBERON_BIN=~/oberonc/bin` or `set OBERON_BIN=C:\oberonc\bin`
on Windows.
The command line syntax of `oberonc` is simple.
Let's compile examples/Hello.Mod:

    MODULE Hello;
      IMPORT Out; (* Import Out to print on the console *)
    BEGIN
      Out.String("Hello 世界");
      Out.Ln (* print a new line *)
    END Hello.

Assuming you are at the root of the repository, the following command will
compile the Hello.Mod example and place the generated classes in the current
folder:

    Linux
    java -cp $OBERON_BIN oberonc . examples/Hello.Mod

    Windows
    java -cp %OBERON_BIN% oberonc . examples/Hello.Mod

The first argument of oberonc is `.`, this is the existing folder where the
generated class will be written, the next arguments specify module files to
be compiled.

This will generate Hello.class and Hello.smb. The second file is a symbol file,
it is used only during compilation and enables `oberonc` to perform separate
compilation of modules that import Hello. In this simple case Hello.Mod
does not export anything, but the other modules in the `examples` folder do.

To run Hello.class, you need the OberonRuntime.class and Out.class. These are
present in the `bin` folder so they are already in the class path, we just need
to include the current folder as well to locate Hello.class:

    Linux
    java -cp $OBERON_BIN:. Hello

    Windows
    java -cp %OBERON_BIN%;. Hello

If you want to compile and run automatically a simple example called `fern`,
type `make runFern`. It should open a window like this one:

![Fern](examples/fern/fern.png)

Lastly, `make clean` will delete the output folders generated by `build`,
`test`, `runFern` and `bootstrapTest`.

## License

The compiler is distributed under the MIT license found in the LICENSE.txt file.


================================================
FILE: doc/TypeRules.md
================================================
# Type rules

## Same types [A]
Two variables *a* and *b* with types *Ta* and *Tb* are of the *same* type if
1. *Ta* and *Tb* are both denoted by the same type identifier, or
2. *Ta* is declared in a type declaration of the form *Ta* = *Tb*, or
3. *a* and *b* appear in the same identifier list in a variable, record field,
   or formal parameter declaration.

## Equal types [B]
Two types *Ta* and *Tb* are *equal* if
1. *Ta* and *Tb* are the *same* type, or
2. *Ta* and *Tb* are open array types with *equal* element types, or
3. *Ta* and *Tb* are array types with *equal* element types and length, or
4. *Ta* and *Tb* are procedure types whose formal parameter lists *match*, or
5. *Ta* and *Tb* are pointer types with *equal* base types.

## Matching formal parameter lists [C]
Two formal parameter lists *match* if
1. they have the same number of parameters, and
2. they have either *equal* function result types or none, and
3. parameters at corresponding positions have *equal* types, and
4. parameters at corresponding positions are both either value or VAR
   parameters.

## Type extension (base type) [D]
Given a type declaration *Tb* = RECORD (*Ta*) ... END, *Tb* is a
*direct extension* of *Ta*, and *Ta* is a *direct base type* of *Tb*. A type
*Tb* is an *extension* of a type *Ta* (*Ta* is a *base type* of *Tb*) if
1. *Ta* and *Tb* are the *same* types, or
2. *Tb* is a *direct extension* of an *extension* of *Ta*.

If *Pa* = POINTER TO *Ta* and *Pb* = POINTER TO *Tb*, *Pb* is an *extension* of
*Pa* (*Pa* is a *base type* of *Pb*) if *Tb* is an *extension* of *Ta*.

## Assignment compatible [E]
An expression *e* of type *Te* is *assignment compatible* with a variable *v*
of type *Tv* if one of the following conditions hold:
1. *Te* and *Tv* are *equal* and are not open array types;
2. *Te* and *Tv* are record types and *Te* is an *extension* of *Tv* and the
   dynamic type of *v* is *Tv*;
3. *Te* and *Tv* are pointer types and *Te* is an *extension* of *Tv*;
4. *Tv* is a pointer or a procedure type and *e* is NIL;
5. *Tv* is array of CHAR, *e* is a string constant with *n* characters,
    and *n* < *LEN(v)*;
6. *Tv* is ARRAY *n* OF *Ta*, *e* is ARRAY OF *Tb* where *Ta* and *Tb* are
   *equal* and *LEN(e) <= LEN(v)*;
7. *Tv* is a procedure type and *e* is the name of a procedure whose formal
   parameters *match* those of *Tv*.

## Parameters

Let *f* be the formal parameter and *a* the corresponding actual parameter. If
*f* is an open array, then *a* must be *array compatible* to *f* and the lengths
of *f* are taken from *a*. Otherwise *a* must be *parameter compatible* to *f*.

### Array compatible [F]
An actual parameter *a* of type *Ta* is *array compatible* with a formal
parameter *f* of type *Tf* if
1. *Tf* and *Ta* are *equal* types, or
2. *Tf* is an open array, *Ta* is any array, and their element types are *array
   compatible*, or
3. *f* is a value parameter of type ARRAY OF CHAR and *a* is a string.

### Parameter compatible [G]
An actual parameter *a* of type *Ta* is *parameter compatible* with a formal
parameter *f* of type *Tf* if:
1. *Tf* and *Ta* are *equal* types, or
2. *f* is a value parameter and *Ta* is *assignment compatible* (except E.5 and
   E.6) with *Tf*, or
3. *f* is an VAR parameter and *Tf* and *Ta* are record types and *Ta* is an
   *extension* of *Tf*, or
4. *f* is a parameter of type ARRAY *m* OF BYTE and *Ta* is any type with size
   *n* = *m* bytes.

## RETURN expression [H]
The type of the expression must be *assignment compatible* with the result type
specified in the procedure heading and can be neither a record nor an array.

## BYTE and INTEGER [I]
The type BYTE is compatible with the type INTEGER, and vice versa.

## String and CHAR [J]
A string of length 1 can be used wherever a character constant is allowed and
vice versa.


================================================
FILE: doc/oberon07.g
================================================
// ANTLR v3 grammar
grammar oberon07;

// LL(1) with few ambiguities resolved with the help of the symbol table
options {k = 1;}

module : 'MODULE' IDENT ';' (importList)?
         declarationSequence ('BEGIN' statementSequence)? 'END' IDENT '.' ;
importList : 'IMPORT'  import_ (',' import_)* ';';
import_ : IDENT (':=' IDENT)? ;
qualident : (IDENT '.')? IDENT;
identdef : IDENT ('*')?;
constDeclaration : identdef '=' constExpression;
constExpression : expression;
typeDeclaration : identdef '=' type;
type : qualident | arrayType | recordType | pointerType | procedureType;
arrayType : 'ARRAY' length (',' length)* 'OF' type;
length : constExpression;
recordType : 'RECORD' ('(' baseType ')')? (fieldListSequence)? 'END';
baseType : qualident;
fieldListSequence : fieldList (';' fieldList)*;
fieldList : identList ':' type;
identList : identdef (',' identdef)*;
pointerType : 'POINTER' 'TO' type;
procedureType : 'PROCEDURE' (formalParameters)?;
variableDeclaration : identList ':' type;
expression : simpleExpression (relation simpleExpression)?;
relation : '=' | '#' | '<' | '<=' | '>' | '>=' | 'IN' | 'IS';
simpleExpression : ('+' | '-')? term (addOperator term)*;
addOperator : '+' | '-' | 'OR';
term : factor (mulOperator factor)*;
mulOperator : '*' | '/' | 'DIV' | 'MOD' | '&';
factor : number | STRING | 'NIL' | 'TRUE' | 'FALSE' | set |
         designator (actualParameters)? | '(' expression ')' | '~' factor;
designator : qualident (selector)*;
selector : '.' IDENT | '[' expList ']' | '^' | '(' qualident ')';
set : '{' (element (',' element)*)?'}';
element : expression ('..' expression)?;
expList : expression (',' expression)*;
actualParameters : '(' (expList)? ')' ;
statement : (assignment | procedureCall | ifStatement | caseStatement |
            whileStatement | repeatStatement | forStatement)?;
assignment : designator ':=' expression;
procedureCall : designator (actualParameters)?;
statementSequence : statement (';' statement)*;
ifStatement : 'IF' expression 'THEN' statementSequence
              ('ELSIF' expression 'THEN' statementSequence)*
              ('ELSE' statementSequence)? 'END';
caseStatement : 'CASE' expression 'OF' case ('|' case)* 'END';
case : (caseLabelList ':' statementSequence)?;
caseLabelList : labelRange (',' labelRange)*;
labelRange : label ('..' label)?;
label : INTEGER | STRING | qualident;
whileStatement : 'WHILE' expression 'DO' statementSequence
                 ('ELSIF' expression 'DO' statementSequence)* 'END';
repeatStatement : 'REPEAT' statementSequence 'UNTIL' expression;
forStatement : 'FOR' IDENT ':=' expression 'TO' expression
               ('BY' constExpression)? 'DO' statementSequence 'END';
procedureDeclaration : procedureHeading ';' procedureBody IDENT;
procedureHeading : 'PROCEDURE' identdef (formalParameters)?;
procedureBody : declarationSequence ('BEGIN' statementSequence)?
               ('RETURN 'expression)? 'END';
declarationSequence : ('CONST' (constDeclaration ';')*)?
                      ('TYPE' (typeDeclaration ';')*)?
                      ('VAR' (variableDeclaration ';')*)?
                      (procedureDeclaration ';')*;
formalParameters : '(' (fpsection (';' fpsection)*)? ')' (':' qualident)?;
fpsection : ('VAR')? IDENT (',' IDENT)* ':' formalType;
formalType : ('ARRAY' 'OF')* qualident;

number : INTEGER | REAL;

INTEGER  : DIGIT (DIGIT)* | DIGIT (HEX_DIGIT)* 'H';

fragment
STR :  '"' ( ~('"') )* '"' ;

fragment
DIGIT : ('0'..'9') ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
REAL : DIGIT (DIGIT)* '.' (DIGIT)* (SCALE_FACTOR)?;

fragment
SCALE_FACTOR : 'E' ('+' | '-')? DIGIT (DIGIT)*;

STRING : STR | DIGIT (HEX_DIGIT)* 'X';

IDENT  :  ('a'..'z'|'A'..'Z') ('a'..'z'|'A'..'Z'|'0'..'9')* ;

COMMENT :   '(*' ( options {greedy=false;} : . )* '*)' {$channel=HIDDEN;} ;

WS  :  (' ' | '\t' | '\r' | '\n') {$channel=HIDDEN;} ;


================================================
FILE: examples/GuessNumber.Mod
================================================
MODULE GuessNumber;
  IMPORT In, Out;
  VAR
    name: ARRAY 20 OF CHAR;
    number, left, right, old: INTEGER;
    choice: CHAR;

PROCEDURE CharLn(VAR ch: CHAR);
VAR discard: CHAR;
BEGIN
  In.Char(ch);
  REPEAT
    In.Char(discard)
  UNTIL In.Done & (discard = 0AX);
END CharLn;

BEGIN
  Out.String("What's your name? ");
  In.String(name);
  Out.String("Hi "); Out.String(name); Out.Char("!"); Out.Ln;
  Out.String("Please think of a number from 0 to 50 and I'll guess it.");
  Out.Ln;
  left := 0;
  right := 50;
  number := 27;
  REPEAT
    REPEAT
      Out.String("Is "); Out.Int(number, 3);
      Out.String(" the correct number? [(h)igher (l)ower (c)orrect] ");
      CharLn(choice)
    UNTIL In.Done & ((choice = "h") OR (choice = "l") OR (choice = "c"));
    IF choice = "h" THEN left := number + 1
    ELSIF choice = "l" THEN right := number - 1
    END ;
    old := number;
    number := left + (right - left) DIV 2;
    IF (choice # "c") & (number = old) THEN
       Out.Ln; Out.String("You lied :)");
       choice := "c"
    END
  UNTIL choice = "c";
  Out.Ln
END GuessNumber.


================================================
FILE: examples/Hello.Mod
================================================
MODULE Hello;
  IMPORT Out; (* Import Out to print on the console *)
BEGIN
  Out.String("Hello 世界");
  Out.Ln (* print a new line *)
END Hello.


================================================
FILE: examples/Powers.Mod
================================================
MODULE Powers;  (*Tabulate positive and negative powers of 2*)
  IMPORT Out, Util;
  CONST N = 32; M = 11;  (*M ~ N*log2*)

  PROCEDURE Power(n: INTEGER);
    VAR i, k, exp: INTEGER;
      c, r, t: INTEGER;
      d: ARRAY M OF INTEGER;
      f: ARRAY N OF INTEGER;
  BEGIN
      d[0] := 1; k := 1; exp := 1;
      WHILE exp < n DO
        (*compute d = 2^exp*)
        c := 0;  (*carry*) i := 0;
        WHILE i < k DO
          t := 2*d[i] + c;
          IF t < 10 THEN d[i] := t; c := 0 ELSE d[i] := t - 10; c := 1 END ;
          i := i+1
        END ;
        IF c = 1 THEN d[k] := 1; k := k+1 END ;
        (*write d*) i := M;
        WHILE i > k DO i := i-1; Out.Char(" ") END ;
        WHILE i > 0 DO i := i-1; Out.Char(CHR(d[i] + ORD("0"))) END ;
        Out.Int(exp, 5);
        (*compute  f = 2^-exp*)
        Out.String("   "); Out.Char("0"); Out.Char(".");
        r := 0; i := 1;
        WHILE i < exp DO
          r := 10*r + f[i]; f[i] := r DIV 2; r := r MOD 2;
          Out.Char(CHR(f[i] + ORD("0"))); i := i+1
        END ;
        f[exp] := 5; Out.Char("5"); Out.Ln; exp := exp + 1
      END
  END Power;

  PROCEDURE Main;
    VAR n, i: INTEGER;
      arg: ARRAY 3 OF CHAR;
  BEGIN
    n := ARGNUM();
    IF n # 1 THEN
      Out.String("usage: Powers number in [0..32]");
      Out.Ln
    ELSE
      ARGS(0, arg);
      i := Util.strToInt(arg);
      IF i <= 32 THEN  Power(i) END
    END
  END Main;
END Powers.


================================================
FILE: examples/PrimeNumbers.Mod
================================================
MODULE PrimeNumbers;  (*Tabulate prime numbers*)
  IMPORT Out, Util;

  PROCEDURE Primes(n: INTEGER);
    VAR i, k, m, x, inc, lim, sqr: INTEGER;
      prim: BOOLEAN;
      p: ARRAY 400 OF INTEGER;
      v: ARRAY 20 OF INTEGER;
  BEGIN
    x := 1; inc := 4;
    lim := 1; sqr := 4;
     m := 0; i := 3;
    WHILE i <= n DO
      REPEAT x := x + inc; inc := 6 - inc;
        IF sqr <= x THEN  (*sqr = p[lim]^2*)
          v[lim] := sqr; lim := lim + 1; sqr := p[lim]*p[lim]
        END ;
        k := 2; prim := TRUE;
        WHILE prim & (k < lim) DO
          k := k+1;
          IF v[k] < x THEN v[k] := v[k] + p[k] END ;
          prim := x # v[k]
        END
      UNTIL prim;
      p[i] := x; Out.Int(x, 5);
      IF m = 10 THEN Out.Ln; m := 0 ELSE m := m+1 END ;
      i := i+1
    END ;
    IF m > 0 THEN Out.Ln END
  END Primes;

  PROCEDURE Main;
    VAR n: INTEGER;
      arg: ARRAY 3 OF CHAR;
  BEGIN
    n := ARGNUM();
    IF n # 1 THEN
      Out.String("usage: PrimeNumbers number in [0..99]");
      Out.Ln
    ELSE
      ARGS(0, arg);
      Primes(Util.strToInt(arg))
    END
  END Main;
END PrimeNumbers.


================================================
FILE: examples/Util.Mod
================================================
MODULE Util;

  PROCEDURE strToInt*(str: ARRAY OF CHAR): INTEGER;
    VAR res, i, x: INTEGER;
  BEGIN
    res := 0;
    FOR i := 0 TO LEN(str)-1 DO
      x := ORD(str[i]) - ORD("0");
      IF (x >= 0) & (x <= 9) THEN res := res * 10 + x END
    END
    RETURN res
  END strToInt;

END Util.



================================================
FILE: examples/fern/IFS.Mod
================================================
MODULE  IFS;
  IMPORT RandomNumbers, XYplane;

  VAR
    a1, b1, c1, d1, e1, f1, p1: REAL;   (* IFS parameters *)
    a2, b2, c2, d2, e2, f2, p2: REAL;   (* IFS parameters *)
    a3, b3, c3, d3, e3, f3, p3: REAL;   (* IFS parameters *)
    a4, b4, c4, d4, e4, f4, p4: REAL;   (* IFS parameters *)
    X, Y: REAL;   (* the position of the pen *)
    x0: INTEGER;  (* Distance of origin fm left edge[pixels] *)
    y0: INTEGER;  (* Distance of origin from bottom edge[pixels] *)
    e: INTEGER; (* Size of unit interval [pixels] *)

  PROCEDURE Draw;
  VAR
    x, y: REAL;         (* new position *)
    xi, eta: INTEGER;    (* pixel coordinates of pen *)
    rn: REAL;
    i : INTEGER;
  BEGIN
    i := 0;
    REPEAT
      rn := RandomNumbers.Uniform();
      IF rn < p1 THEN
        x := a1 * X + b1 * Y + e1;  y := c1 * X + d1 * Y + f1
      ELSIF rn < (p1 + p2) THEN
        x := a2 * X + b2 * Y + e2;  y := c2 * X + d2 * Y + f2
      ELSIF rn < (p1 + p2 + p3) THEN
        x := a3 * X + b3 * Y + e3;  y := c3 * X + d3 * Y + f3
      ELSE
        x := a4 * X + b4 * Y + e4;  y := c4 * X + d4 * Y + f4
      END;
      X := x;  xi := x0 + FLOOR(X*FLT(e));
      Y := y;  eta := y0 + FLOOR(Y*FLT(e));
      XYplane.Dot(xi, eta, XYplane.draw);
      INC(i)
    UNTIL i = 100000
  END Draw;

  PROCEDURE Init;
  BEGIN
    X := 0.0; Y := 0.0;   (* Initial position of pen *)
    RandomNumbers.InitSeed(1);
    x0 := 320; y0 := 75; e := 64;
    a1 := 0.0; a2 := 0.85; a3 := 0.2; a4 := -0.15;
    b1 := 0.0; b2 := 0.04; b3 := -0.26; b4 := 0.28;
    c1 := 0.0; c2 := -0.04; c3 := 0.23; c4 := 0.26;
    d1 := 0.16; d2 := 0.85; d3 := 0.22; d4 := 0.24;
    e1 := 0.0; e2 := 0.0; e3 := 0.0; e4 := 0.0;
    f1 := 0.0; f2 := 1.6; f3 := 1.6; f4 := 0.44;
    p1 := 0.01; p2 := 0.85; p3 := 0.07; p4 := 0.07;
  END Init;

BEGIN
  XYplane.Open; Init; Draw
END IFS.


================================================
FILE: examples/fern/RandomNumbers.Mod
================================================
MODULE RandomNumbers;
  IMPORT Math;

  VAR Z: INTEGER;

  PROCEDURE Uniform*(): REAL;
    CONST
      a = 16807;  m = 2147483647;
      q = m DIV a;  r = m MOD a;
    VAR g: INTEGER;
  BEGIN
    g := a*(Z MOD q) - r*(Z DIV q);
    IF g > 0 THEN Z := g ELSE Z := g + m END;
    RETURN FLT(Z)*(1.0/FLT(m))
  END Uniform;

  PROCEDURE Exp*(mu: REAL): REAL;
  BEGIN
    RETURN -Math.ln(Uniform())/mu
  END Exp;

  PROCEDURE InitSeed*(seed: INTEGER);
  BEGIN
    Z := seed
  END InitSeed;

BEGIN
  Z := 1;
END RandomNumbers.


================================================
FILE: examples/fern/XYplane.Mod
================================================
DEFINITION XYplane;  
  CONST erase = 0; draw = 1;
  VAR X, Y, W, H: INTEGER;

  PROCEDURE Open;
  PROCEDURE Dot(x, y, mode: INTEGER);
  PROCEDURE IsDot(x, y: INTEGER): BOOLEAN;
  PROCEDURE Key(): CHAR;
  PROCEDURE Clear;
END XYplane.

================================================
FILE: examples/fern/java/XYplane.java
================================================
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public final class XYplane {
  public static final int erase = 0;
  public static final int draw = 1;
  public static int X, Y, W, H;
  private static char key;
  private static Viewer viewer;
  private static final int white = Color.WHITE.getRGB();
  private static final int black = Color.BLACK.getRGB();

  // Ensure non-instantiability
  private XYplane() {}

  public static void Open() {
    W = 800;
    H = 800;
    JFrame frame = new JFrame("XYPlane");
    viewer = new Viewer(W, H);
    frame.add(viewer);
    frame.pack();
    frame.setVisible(true);
    frame.setResizable(false);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  }

  public static void Dot(int x, int y, int mode) {
    y = H - y - 1;
    if(mode == erase) {
      viewer.canvas.setRGB(x, y, black);
    } else {
      viewer.canvas.setRGB(x, y, white);
    }
    viewer.updateUI();
  }

  public static boolean isDot(int x, int y) {
    y = H - y - 1;
    return viewer.canvas.getRGB(x, y) == white;
  }

  public static char Key() {
    return key;
  }

  public static void Clear() {
    viewer.fillCanvas(black);
  }

  private static class Viewer extends JPanel {
    private BufferedImage canvas;

    public Viewer(int width, int height) {
      canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
      fillCanvas(black);
      setFocusable(true);
      requestFocus(true);
    }

    public Dimension getPreferredSize() {
      return new Dimension(canvas.getWidth(), canvas.getHeight());
    }

    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D) g;
      g2.drawImage(canvas, null, null);
    }

    public void fillCanvas(int color) {
      for(int x = 0; x < canvas.getWidth(); x++) {
        for(int y = 0; y < canvas.getHeight(); y++) {
          canvas.setRGB(x, y, color);
        }
      }
      repaint();
    }
  }
}


================================================
FILE: make.bat
================================================
@echo off

set JAVA_SOURCES=src/java/Files_FileDesc.java src/java/Files.java src/java/OberonRuntime.java src/java/Os.java src/java/Out.java src/java/In.java src/java/Math.java
set MOD_SOURCES=src/Out.Mod src/Os.Mod src/Files.Mod src/Strings.Mod src/OJS.Mod src/CpCache.Mod src/Opcodes.Mod src/ClassFormat.Mod src/OJB.Mod src/OJG.Mod src/OJP.Mod src/oberonc.Mod src/In.Mod src/Math.Mod

set OBERON_BIN=./bin

if "%~1"=="" goto build
if "%~1"=="build" goto build
if "%~1"=="bootstrap" goto bootstrap
if "%~1"=="runFern" goto runFern
if "%~1"=="test" goto test
if "%~1"=="clean" goto clean

echo "%~1": invalid target
goto end

:build
mkdir "out/"
javac -d out %JAVA_SOURCES%
java -cp %OBERON_BIN% oberonc out %MOD_SOURCES%
echo build done
goto end

:bootstrap
javac -d bin %JAVA_SOURCES%
java -cp %OBERON_BIN% oberonc bin %MOD_SOURCES%
echo bootstrap done
goto end

:runFern
mkdir "examples/fern/out/"
javac -cp %OBERON_BIN% -d examples/fern/out examples/fern/java/*.java
java -cp %OBERON_BIN% oberonc examples/fern/out examples/fern/RandomNumbers.Mod examples/fern/XYplane.Mod examples/fern/IFS.Mod
java -cp %OBERON_BIN%;examples/fern/out IFS
goto end

:test
mkdir "tests/out/"
javac -cp %OBERON_BIN% -d tests/out tests/TestRunner.java
java -Dfile.encoding=UTF-8 -cp %OBERON_BIN%;tests/out TestRunner
goto end

:clean
rmdir out /s /q
rmdir tests\out /s /q
rmdir examples\fern\out /s /q

:end



================================================
FILE: src/ClassFormat.Mod
================================================
(*
  Copyright 2017 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(*
  This module deals with the low level generation of a class file according to
  the Java Virtual Machine Specification. It uses CpCache to keep track of the
  constant pool entries written so far in the CFDesc record.
*)
MODULE ClassFormat;
  IMPORT Strings, Files, CpCache, Opcodes, OJS;

  CONST
    nestedIdLen* = 5;
    CpMax = 10000H;
    DescLenMax = 500;
    FieldsMax = CpMax;
    LineNumTabMax = CpMax;
    CodeMax = CpMax;
    MethodsMax = 34 + LineNumTabMax + CodeMax;
    ClassMax = 32 + CpMax + MethodsMax + FieldsMax;
    ShortMaxValue = 32767;
    UTF8 = 1;
    CLASS = 7;
    NAMExTYPE = 12;
    METH = 10;
    FIELD = 9;
    STR = 8;
    INT* = 3;
    FLOAT* = 4;

  TYPE
    Descriptor* = ARRAY DescLenMax OF CHAR;
    CF* = POINTER TO CFDesc;
    CFDesc = RECORD
      constantPool: ARRAY CpMax OF BYTE;
      cpCount, cpIndex: INTEGER;
      access: INTEGER;
      thisIndex, superIndex, sourceFileIndex: INTEGER;
      methods: ARRAY MethodsMax OF BYTE;
      methIndex, methCount: INTEGER;
      fields: ARRAY FieldsMax OF BYTE;
      fieldsIndex, fieldsCount: INTEGER;
      cpCache, procMap: CpCache.Cache;
    END;
    MethodInfo* = POINTER TO MethodInfoDesc;
    MethodInfoDesc = RECORD
      cf: CF;
      access: INTEGER;
      name: ARRAY OJS.IdLen + nestedIdLen OF CHAR;
      descriptor: ARRAY DescLenMax OF CHAR;
      code: ARRAY CodeMax OF BYTE;
      i*: INTEGER;
      maxStack, curStack*, maxLocals: INTEGER;
      lineNumTab: ARRAY LineNumTabMax OF BYTE;
      lineIndex: INTEGER;
    END;

  VAR classMap: CpCache.Cache;

  PROCEDURE Init*;
  BEGIN
    NEW(classMap)
  END Init;

  PROCEDURE lengthUTF8(s: ARRAY OF CHAR; add0X: BOOLEAN): INTEGER;
    VAR
      i,j, length: INTEGER;
      c: CHAR;
  BEGIN
    length := LEN(s);
    i := 0;
    j := 0;
    WHILE (j < length) & (s[j] # 0X) DO
      c := s[j];
      IF (c >= 1X) & (c <= 7FX) THEN INC(i)
      ELSIF c > 7FFX THEN INC(i,3)
      ELSE INC(i, 2)
      END;
      INC(j);
    END;
    IF add0X THEN INC(i, 2) END
    RETURN i
  END lengthUTF8;

  PROCEDURE putUTF8(VAR buf: ARRAY OF BYTE; i: INTEGER; s: ARRAY OF CHAR;
                    add0X: BOOLEAN): INTEGER;
    VAR
      j, c, slen, buflen: INTEGER;
  BEGIN
    slen := LEN(s);
    buflen := LEN(buf);
    j := 0;
    WHILE (j < slen) & (ORD(s[j]) # 0H) DO
      c := ORD(s[j]);
      IF (c >= 1H) & (c <= 7FH) & (i < buflen) THEN
        buf[i] := c;
        INC(i)
      ELSIF (c > 7FFH) & (i+2 < buflen) THEN
        buf[i] := BOR(0E0H, AND(ASR(c, 12), 0FH));
        buf[i+1] := BOR(80H, AND(ASR(c, 6), 3FH));
        buf[i+2] := BOR(80H, AND(c, 3FH));
        INC(i, 3)
      ELSIF (c >= 80H) & (c <= 7FFH) & (i+1 < buflen) THEN
        buf[i] := BOR(0C0H, AND(ASR(c, 6), 1FH));
        buf[i+1] := BOR(80H, AND(c, 3FH));
        INC(i, 2)
      ELSE
        OJS.Mark("Class file buffer limit reached");
        j := slen
      END;
      INC(j);
    END;
    IF add0X & (i+1 < buflen) THEN
      buf[i] := 0C0H;
      buf[i+1] := 80H;
      INC(i, 2)
    END
    RETURN i
  END putUTF8;

  PROCEDURE putByte(VAR buf: ARRAY OF BYTE; i, x: INTEGER): INTEGER;
  BEGIN
    IF i < LEN(buf) THEN
      buf[i] := x;
      INC(i)
    ELSE OJS.Mark("Class file buffer limit reached") END
    RETURN i
  END putByte;

  PROCEDURE putNBytes(VAR buf: ARRAY OF BYTE; i: INTEGER; x: BYTE;
                      n: INTEGER): INTEGER;
  BEGIN
    n := n + i;
    IF n-1 < LEN(buf) THEN
      WHILE(i < n) DO
        buf[i] := x;
        INC(i)
      END
    ELSE OJS.Mark("Class file buffer limit reached") END
    RETURN i
  END putNBytes;

  PROCEDURE putInt(VAR buf: ARRAY OF BYTE; i, x: INTEGER): INTEGER;
  BEGIN
    IF i+3 < LEN(buf) THEN
      buf[i] := ASR(x, 24);
      buf[i+1] := ASR(x, 16);
      buf[i+2] := ASR(x, 8);
      buf[i+3] := x;
      INC(i, 4)
    ELSE OJS.Mark("Class file buffer limit reached") END
    RETURN i
  END putInt;

  PROCEDURE putShort(VAR buf: ARRAY OF BYTE; i, x: INTEGER): INTEGER;
  BEGIN
    IF i+1 < LEN(buf) THEN
      buf[i] := ASR(x, 8);
      buf[i+1] := x;
      INC(i, 2)
    ELSE OJS.Mark("Class file buffer limit reached") END
    RETURN i
  END putShort;

  PROCEDURE putArray(VAR buf: ARRAY OF BYTE; i: INTEGER; x: ARRAY OF BYTE;
                     len: INTEGER): INTEGER;
    VAR j: INTEGER;
  BEGIN
    j := 0;
    IF i+len-1 < LEN(buf) THEN
      WHILE j < len DO
        buf[i+j] := x[j];
        INC(j)
      END;
      INC(i, j)
    ELSE OJS.Mark("Class file buffer limit reached") END
    RETURN i
  END putArray;

  PROCEDURE cpWriteUTF8(cf: CF; s: ARRAY OF CHAR; add0X: BOOLEAN): INTEGER;
    VAR
      i, z: INTEGER;
      key: CpCache.Key;
  BEGIN
    z := Strings.Write(s, key, 0);
    IF add0X THEN
      z := Strings.WriteChar("$", key, z)
    END;
    z := Strings.WriteInt(UTF8, 0, key, z);
    IF z = -1 THEN
      OJS.Mark("internal cache buffer limit reached")
    END;
    i := CpCache.get(cf.cpCache, key);
    IF i = -1 THEN
      i := cf.cpCount;
      cf.cpIndex := putByte(cf.constantPool, cf.cpIndex, UTF8);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, lengthUTF8(s, add0X));
      cf.cpIndex := putUTF8(cf.constantPool, cf.cpIndex, s, add0X);
      INC(cf.cpCount);
      CpCache.put(cf.cpCache, key, i);
    END
    RETURN i
  END cpWriteUTF8;

  PROCEDURE cpWriteClass(cf: CF; s: ARRAY OF CHAR): INTEGER;
    VAR
      i, j, z: INTEGER;
      key: CpCache.Key;
  BEGIN
    z := Strings.Write(s, key, 0);
    z := Strings.WriteInt(CLASS, 0, key, z);
    IF z = -1 THEN
      OJS.Mark("internal cache buffer limit reached")
    END;
    j := CpCache.get(cf.cpCache, key);
    IF j = -1 THEN
      i := cpWriteUTF8(cf, s, FALSE);
      j := cf.cpCount;
      cf.cpIndex := putByte(cf.constantPool, cf.cpIndex, CLASS);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, i);
      INC(cf.cpCount);
      CpCache.put(cf.cpCache, key, j)
    END
    RETURN j
  END cpWriteClass;

  PROCEDURE cpWriteNameAndType(cf: CF; name, desc: ARRAY OF CHAR): INTEGER;
    VAR
      i, j, k, z: INTEGER;
      key: CpCache.Key;
  BEGIN
    z := Strings.Write(name, key, 0);
    z := Strings.Write(desc, key, z);
    z := Strings.WriteInt(NAMExTYPE, 0, key, z);
    IF z = -1 THEN
      OJS.Mark("internal cache buffer limit reached")
    END;
    k := CpCache.get(cf.cpCache, key);
    IF k = -1 THEN
      i := cpWriteUTF8(cf, name, FALSE);
      j := cpWriteUTF8(cf, desc, FALSE);
      k := cf.cpCount;
      cf.cpIndex := putByte(cf.constantPool, cf.cpIndex, NAMExTYPE);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, i);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, j);
      INC(cf.cpCount);
      CpCache.put(cf.cpCache, key, k)
    END
    RETURN k
  END cpWriteNameAndType;

  PROCEDURE cpWriteString(cf: CF; val: ARRAY OF CHAR; add0X: BOOLEAN): INTEGER;
    VAR
      i, j, z: INTEGER;
      key: CpCache.Key;
  BEGIN
    z := Strings.Write(val, key, 0);
    IF add0X THEN
      z := Strings.WriteChar("$", key, z)
    END;
    z := Strings.WriteInt(STR, 0, key, z);
    IF z = -1 THEN
      OJS.Mark("internal cache buffer limit reached")
    END;
    j := CpCache.get(cf.cpCache, key);
    IF j = -1 THEN
      i := cpWriteUTF8(cf, val, add0X);
      j := cf.cpCount;
      cf.cpIndex := putByte(cf.constantPool, cf.cpIndex, STR);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, i);
      INC(cf.cpCount);
      CpCache.put(cf.cpCache, key, j)
    END
    RETURN j
  END cpWriteString;

  PROCEDURE cpWriteConst(cf: CF; tag, val: INTEGER): INTEGER;
    VAR
      i, z: INTEGER;
      key: CpCache.Key;
  BEGIN
    z := Strings.WriteInt(tag, 0, key, 0);
    z := Strings.WriteInt(val, 0, key, z);
    IF z = -1 THEN
      OJS.Mark("internal cache buffer limit reached")
    END;
    i := CpCache.get(cf.cpCache, key);
    IF i = -1 THEN
      i := cf.cpCount;
      cf.cpIndex := putByte(cf.constantPool, cf.cpIndex, tag);
      cf.cpIndex := putInt(cf.constantPool, cf.cpIndex, val);
      INC(cf.cpCount);
      CpCache.put(cf.cpCache, key, i)
    END
    RETURN i
  END cpWriteConst;

  PROCEDURE cpWriteRef(cf: CF; tag: INTEGER;
                       owner, name, desc: ARRAY OF CHAR): INTEGER;
    VAR
      i, j, k, z: INTEGER;
      key: CpCache.Key;
  BEGIN
    z := Strings.Write(owner, key, 0);
    z := Strings.Write(name, key, z);
    z := Strings.Write(desc, key, z);
    z := Strings.WriteInt(tag, 0, key, z);
    IF z = -1 THEN
      OJS.Mark("internal cache buffer limit reached")
    END;
    k := CpCache.get(cf.cpCache, key);
    IF k = -1 THEN
      i := cpWriteClass(cf, owner);
      j := cpWriteNameAndType(cf, name, desc);
      k := cf.cpCount;
      cf.cpIndex := putByte(cf.constantPool, cf.cpIndex, tag);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, i);
      cf.cpIndex := putShort(cf.constantPool, cf.cpIndex, j);
      INC(cf.cpCount);
      CpCache.put(cf.cpCache, key, k)
    END
    RETURN k
  END cpWriteRef;

  PROCEDURE cpWriteFiledRef(cf: CF; owner, name, desc: ARRAY OF CHAR): INTEGER;
    RETURN cpWriteRef(cf, FIELD, owner, name, desc)
  END cpWriteFiledRef;

  PROCEDURE cpWriteMethodRef(cf: CF; owner, name, desc: ARRAY OF CHAR): INTEGER;
    RETURN cpWriteRef(cf, METH, owner, name, desc)
  END cpWriteMethodRef;

  PROCEDURE addField*(cf: CF; access: INTEGER; name, desc: ARRAY OF CHAR);
  BEGIN
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex, access);
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex,
                               cpWriteUTF8(cf, name, FALSE));
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex,
                               cpWriteUTF8(cf, desc, FALSE));
    (* attribute_count *)
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex, 0);
    INC(cf.fieldsCount)
  END addField;

  PROCEDURE addConstField*(cf: CF; name, desc: ARRAY OF CHAR; val: INTEGER);
    VAR i: INTEGER;
  BEGIN
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex,
                               Opcodes.ACCxPUBLIC + Opcodes.ACCxFINAL +
                               Opcodes.ACCxSTATIC);
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex,
                               cpWriteUTF8(cf, name, FALSE));
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex,
                               cpWriteUTF8(cf, desc, FALSE));
    (* attribute_count: ConstantValue *)
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex, 1);
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex,
                               cpWriteUTF8(cf, "ConstantValue", FALSE));
    cf.fieldsIndex := putInt(cf.fields, cf.fieldsIndex, 2);
    IF desc[0] = "F" THEN
      i := cpWriteConst(cf, FLOAT, val)
    ELSE
      i := cpWriteConst(cf, INT, val)
    END;
    cf.fieldsIndex := putShort(cf.fields, cf.fieldsIndex, i);
    INC(cf.fieldsCount)
  END addConstField;

  PROCEDURE finalizeMethod*(cf: CF; mi: MethodInfo);
  BEGIN
    cf.methIndex := putShort(cf.methods, cf.methIndex, mi.access);
    cf.methIndex := putShort(cf.methods, cf.methIndex,
                             cpWriteUTF8(cf, mi.name, FALSE));
    cf.methIndex := putShort(cf.methods, cf.methIndex,
                             cpWriteUTF8(cf, mi.descriptor, FALSE));
    IF AND(mi.access, Opcodes.ACCxABSTRACT) = 0 THEN  (* is abstract? *)
      (* attribute_count: code *)
      cf.methIndex := putShort(cf.methods, cf.methIndex, 1);
      cf.methIndex := putShort(cf.methods, cf.methIndex,
                               cpWriteUTF8(cf, "Code", FALSE));
      (* attribute_length *)
      cf.methIndex := putInt(cf.methods, cf.methIndex,
                            20 + mi.i + mi.lineIndex);
      cf.methIndex := putShort(cf.methods, cf.methIndex, mi.maxStack);
      cf.methIndex := putShort(cf.methods, cf.methIndex, mi.maxLocals);
      cf.methIndex := putInt(cf.methods, cf.methIndex, mi.i);
      cf.methIndex := putArray(cf.methods, cf.methIndex, mi.code, mi.i);
      (* exception_table_length *)
      cf.methIndex := putShort(cf.methods, cf.methIndex, 0);
      (* attribute_count: LineNumberTable *)
      cf.methIndex := putShort(cf.methods, cf.methIndex, 1);
      cf.methIndex := putShort(cf.methods, cf.methIndex,
                               cpWriteUTF8(cf, "LineNumberTable", FALSE));
      (* attribute_length *)
      cf.methIndex := putInt(cf.methods, cf.methIndex,  2 + mi.lineIndex);
      (* line_number_table_length *)
      cf.methIndex := putShort(cf.methods, cf.methIndex,  mi.lineIndex DIV 4);
      cf.methIndex := putArray(cf.methods, cf.methIndex,
                               mi.lineNumTab, mi.lineIndex)
    ELSE
      (* attribute_count: code *)
      cf.methIndex := putShort(cf.methods, cf.methIndex, 0)
    END;
    INC(cf.methCount)
  END finalizeMethod;

  PROCEDURE toFile*(cf: CF; path: ARRAY OF CHAR);
    VAR
      out: ARRAY ClassMax OF BYTE;
      i, err, sourceFile: INTEGER;
      f: Files.File;
  BEGIN
    (* CpCache.debug(cf.cpCache); *)
    sourceFile := cpWriteUTF8(cf, "SourceFile", FALSE);
    i := putInt(out, 0, 0CAFEBABEH); (* magic *)
    i := putInt(out, i, 49); (* Java 1.5 *)
    i := putShort(out, i, cf.cpCount);
    i := putArray(out, i, cf.constantPool, cf.cpIndex);
    i := putShort(out, i, cf.access);
    i := putShort(out, i, cf.thisIndex);
    i := putShort(out, i, cf.superIndex);
    i := putShort(out, i, 0); (* interfaces_count *)
    i := putShort(out, i, cf.fieldsCount);
    i := putArray(out, i, cf.fields, cf.fieldsIndex);
    i := putShort(out, i, cf.methCount);
    i := putArray(out, i, cf.methods, cf.methIndex);
    i := putShort(out, i, 1); (* attributes_count: SourceFile *)
    i := putShort(out, i, sourceFile);
    i := putInt(out, i, 2); (* attribute_length *)
    i := putShort(out, i, cf.sourceFileIndex);
    err := Files.IOERROR;
    IF OJS.errcnt = 0 THEN
      f := Files.Create(path);
      IF f # NIL THEN
        Files.WriteNBytes(f, out, i);
        Files.Close(f);
        err := Files.Status(f)
      END
    END;
    IF (err # Files.OK) & (OJS.errcnt = 0) THEN
      OJS.MarkAppend("Failed to write ", path)
    END
  END toFile;

  PROCEDURE NewCF*(acc: INTEGER; n, sn: ARRAY OF CHAR): CF;
    VAR
      cf: CF;
      className: Descriptor;
  BEGIN
    NEW(cf);
    Strings.Append(n, className);
    Strings.Append(".Mod", className);
    cf.access := acc;
    cf.cpCount := 1;
    cf.cpCache := CpCache.New();
    cf.procMap := CpCache.New();
    cf.methCount := 0;
    cf.fieldsCount := 0;
    cf.thisIndex := cpWriteClass(cf, n);
    cf.sourceFileIndex := cpWriteUTF8(cf, className, FALSE);
    cf.superIndex := cpWriteClass(cf, sn);
    IF CpCache.get(classMap, n) = 1 THEN
      OJS.Mark("type names must be unique")
    ELSE
      CpCache.put(classMap, n, 1)
    END
    RETURN cf
  END NewCF;

  PROCEDURE NewMI*(classFormat: CF; acc: INTEGER;
                   n, desc: ARRAY OF CHAR): MethodInfo;
    VAR mi: MethodInfo;
  BEGIN
    NEW(mi);
    mi.cf := classFormat;
    mi.access := acc;
    mi.name := n;
    mi.descriptor := desc;
    mi.maxStack := 0;
    mi.maxLocals := 0;
    mi.curStack := 0;
    mi.lineIndex := 0;
    mi.i := 0;
    IF CpCache.get(mi.cf.procMap, mi.name) = 1 THEN
      OJS.Mark("procedure names must be unique")
    ELSE
      CpCache.put(mi.cf.procMap, mi.name, 1)
    END
    RETURN mi
  END NewMI;

  PROCEDURE setMaxStack(mi: MethodInfo);
  BEGIN
    IF mi.curStack > mi.maxStack THEN
      mi.maxStack := mi.curStack
    END
  END setMaxStack;

  PROCEDURE fix(mi: MethodInfo; at, with: INTEGER);
    VAR x: INTEGER;
  BEGIN
    x := putShort(mi.code, at+1, with)
  END fix;

  PROCEDURE FixLink*(mi: MethodInfo; L: INTEGER);
    VAR L1: INTEGER;
  BEGIN
    WHILE (L # 0) & (OJS.errcnt = 0) DO
      L1 := BOR(LSL(mi.code[L + 1], 8), mi.code[L + 2]);
      fix(mi, L, mi.i-L);
      L := L1
    END
  END FixLink;

  PROCEDURE FixLinkWith*(mi: MethodInfo; L0, dst: INTEGER);
    VAR L1: INTEGER;
  BEGIN
    WHILE (L0 # 0) & (OJS.errcnt = 0) DO
      L1 := BOR(LSL(mi.code[L0 + 1], 8), mi.code[L0 + 2]);
      fix(mi, L0, dst-L0);
      L0 := L1
    END
  END FixLinkWith;

  PROCEDURE merged*(mi: MethodInfo; L0, L1: INTEGER): INTEGER;
    VAR L2, L3: INTEGER;
  BEGIN
    IF L0 # 0 THEN
      L3 := L0;
      REPEAT
        L2 := L3;
        L3 := BOR(LSL(mi.code[L2 + 1], 8), mi.code[L2 + 2]);
      UNTIL L3 = 0;
      fix(mi, L2, L1);
      L1 := L0
    END
    RETURN L1
  END merged;

  PROCEDURE putMethodInsn*(mi: MethodInfo; opcode: INTEGER;
                           owner, name, desc: ARRAY OF CHAR; args: INTEGER);
  BEGIN
    IF (opcode = Opcodes.INVOKEVIRTUAL) OR
       (opcode = Opcodes.INVOKESPECIAL) OR
       (opcode = Opcodes.INVOKEINTERFACE) THEN
      mi.curStack := mi.curStack - (args + 1)
    ELSE
      mi.curStack := mi.curStack - args;
    END;
    IF desc[Strings.Length(desc)-1] # "V" THEN
      INC(mi.curStack)
    END;
    setMaxStack(mi);
    mi.i := putByte(mi.code, mi.i, opcode);
    mi.i := putShort(mi.code, mi.i, cpWriteMethodRef(mi.cf, owner, name, desc));
  END putMethodInsn;

  PROCEDURE addLineNumber*(mi: MethodInfo; line: INTEGER);
  BEGIN
    mi.lineIndex := putShort(mi.lineNumTab, mi.lineIndex, mi.i);
    mi.lineIndex := putShort(mi.lineNumTab, mi.lineIndex, line)
  END addLineNumber;

  PROCEDURE incStack(mi: MethodInfo; opcode: INTEGER);
  BEGIN
    CASE opcode OF
      Opcodes.AALOAD, Opcodes.ASTORE, Opcodes.ATHROW, Opcodes.BALOAD,
      Opcodes.CALOAD, Opcodes.D2I, Opcodes.FADD, Opcodes.FALOAD,
      Opcodes.FCMPG, Opcodes.FCMPL,  Opcodes.FDIV, Opcodes.FMUL,
      Opcodes.FSTORE, Opcodes.FSUB, Opcodes.IADD, Opcodes.IALOAD,
      Opcodes.IAND, Opcodes.IFEQ, Opcodes.IFGE, Opcodes.IFGT,
      Opcodes.IFLE, Opcodes.IFLT, Opcodes.IFNE, Opcodes.IFNONNULL,
      Opcodes.IFNULL, Opcodes.IMUL, Opcodes.IOR, Opcodes.ISHL,
      Opcodes.ISHR, Opcodes.ISTORE, Opcodes.ISUB, Opcodes.IXOR,
      Opcodes.POP, Opcodes.PUTSTATIC, Opcodes.TABLESWITCH:
        INC(mi.curStack, -1)
    | Opcodes.IFACMPEQ, Opcodes.IFACMPNE, Opcodes.IFICMPEQ,
      Opcodes.IFICMPGE, Opcodes.IFICMPGT, Opcodes.IFICMPLE,
      Opcodes.IFICMPLT, Opcodes.IFICMPNE, Opcodes.POP2,
      Opcodes.PUTFIELD:
        INC(mi.curStack, -2)
    | Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE,
      Opcodes.FASTORE, Opcodes.IASTORE:
        INC(mi.curStack, -3)
    | Opcodes.DUP2:
        INC(mi.curStack, 2)
    | Opcodes.ACONSTNULL, Opcodes.ALOAD, Opcodes.BIPUSH,
      Opcodes.DUP, Opcodes.F2D, Opcodes.FCONST0, Opcodes.FCONST1,
      Opcodes.FCONST2, Opcodes.FLOAD, Opcodes.GETSTATIC,
      Opcodes.ICONSTM1, Opcodes.ICONST0, Opcodes.ICONST1,
      Opcodes.ICONST2, Opcodes.ICONST3, Opcodes.ICONST4,
      Opcodes.ICONST5, Opcodes.ILOAD, Opcodes.LDC, Opcodes.NEW,
      Opcodes.SIPUSH:
        INC(mi.curStack, 1)
    | Opcodes.ANEWARRAY, Opcodes.ARETURN, Opcodes.ARRAYLENGTH,
      Opcodes.CHECKCAST, Opcodes.FNEG,  Opcodes.FRETURN,
      Opcodes.GETFIELD, Opcodes.GOTO, Opcodes.I2F, Opcodes.IINC,
      Opcodes.INEG, Opcodes.INSTANCEOF, Opcodes.IRETURN,
      Opcodes.NEWARRAY, Opcodes.RETURNx, Opcodes.SWAP:
        (* nothing to do *)
    END;
    setMaxStack(mi)
  END incStack;

  PROCEDURE putTypeInsn*(mi: MethodInfo; opcode: INTEGER; type: ARRAY OF CHAR);
  BEGIN
    incStack(mi, opcode);
    mi.i := putByte(mi.code, mi.i, opcode);
    mi.i := putShort(mi.code, mi.i, cpWriteClass(mi.cf, type))
  END putTypeInsn;

  PROCEDURE putMultiANewArrayInsn*(mi: MethodInfo; desc: ARRAY OF CHAR;
                                   dims: INTEGER);
  BEGIN
    mi.curStack := mi.curStack - (dims - 1);
    setMaxStack(mi);
    mi.i := putByte(mi.code, mi.i, Opcodes.MULTIANEWARRAY);
    mi.i := putShort(mi.code, mi.i, cpWriteClass(mi.cf, desc));
    mi.i := putByte(mi.code, mi.i, dims)
  END putMultiANewArrayInsn;

  PROCEDURE putTableSwitchInsn*(mi: MethodInfo; min, max, dflt, nLables: INTEGER;
                                labels: ARRAY OF INTEGER);
    VAR j: INTEGER;
   BEGIN
    incStack(mi, Opcodes.TABLESWITCH);
    mi.i := putByte(mi.code, mi.i, Opcodes.TABLESWITCH);
    mi.i := putNBytes(mi.code, mi.i, 0, (4 - mi.i MOD 4) MOD 4);
    mi.i := putInt(mi.code, mi.i, dflt);
    mi.i := putInt(mi.code, mi.i, min);
    mi.i := putInt(mi.code, mi.i, max);
    FOR j := 0 TO nLables-1 DO
      mi.i := putInt(mi.code, mi.i, labels[j])
    END
  END putTableSwitchInsn;

  PROCEDURE putIincInsn*(mi: MethodInfo; var, increment: INTEGER);
  BEGIN
    incStack(mi, Opcodes.IINC);
    mi.i := putByte(mi.code, mi.i, Opcodes.IINC);
    mi.i := putByte(mi.code, mi.i, var);
    mi.i := putByte(mi.code, mi.i, increment)
  END putIincInsn;

  PROCEDURE putLdcInsnInt*(mi: MethodInfo; type, c: INTEGER);
    VAR x: INTEGER;
  BEGIN
    incStack(mi, Opcodes.LDC);
    x := cpWriteConst(mi.cf, type, c);
    IF x <= 255 THEN
      mi.i := putByte(mi.code, mi.i, Opcodes.LDC);
      mi.i := putByte(mi.code, mi.i, x)
    ELSE
      mi.i := putByte(mi.code, mi.i, Opcodes.LDCW);
      mi.i := putShort(mi.code, mi.i, x)
    END
  END putLdcInsnInt;

  PROCEDURE putLdcInsnStr*(mi: MethodInfo; c: ARRAY OF CHAR; add0X: BOOLEAN);
    VAR x: INTEGER;
  BEGIN
    incStack(mi, Opcodes.LDC);
    x := cpWriteString(mi.cf, c, add0X);
    IF x <= 255 THEN
      mi.i := putByte(mi.code, mi.i, Opcodes.LDC);
      mi.i := putByte(mi.code, mi.i, x)
    ELSE
      mi.i := putByte(mi.code, mi.i, Opcodes.LDCW);
      mi.i := putShort(mi.code, mi.i, x)
    END
  END putLdcInsnStr;

  PROCEDURE putVarInsn*(mi: MethodInfo; opcode, var: INTEGER);
    VAR opt: INTEGER;
  BEGIN
    incStack(mi, opcode);
    IF var < 4 THEN
      IF opcode < Opcodes.ISTORE THEN
        (* ILOAD_0 *)
        opt := 26 + LSL((opcode - Opcodes.ILOAD), 2) + var
      ELSE
        (* ISTORE_0 *)
        opt := 59 + LSL((opcode - Opcodes.ISTORE), 2) + var
      END;
      mi.i := putByte(mi.code, mi.i, opt)
    ELSE
      mi.i := putByte(mi.code, mi.i, opcode);
      mi.i := putByte(mi.code, mi.i, var)
    END
  END putVarInsn;

  PROCEDURE putFieldInsn*(mi: MethodInfo; opcode: INTEGER;
                          owner, name, desc: ARRAY OF CHAR);
  BEGIN
    incStack(mi, opcode);
    mi.i := putByte(mi.code, mi.i, opcode);
    mi.i := putShort(mi.code, mi.i, cpWriteFiledRef(mi.cf, owner, name, desc))
  END putFieldInsn;

  PROCEDURE putIntInsn*(mi: MethodInfo; opcode, operand: INTEGER);
  BEGIN
    incStack(mi, opcode);
    mi.i := putByte(mi.code, mi.i, opcode);
    IF opcode = Opcodes.SIPUSH THEN
      mi.i := putByte(mi.code, mi.i, ASR(operand, 8));
      mi.i := putByte(mi.code, mi.i, operand)
    ELSE (* BIPUSH or NEWARRAY *)
      mi.i := putByte(mi.code, mi.i, operand)
    END
  END putIntInsn;

  PROCEDURE putJumpInsn*(mi: MethodInfo; opcode, to: INTEGER);
  BEGIN
    incStack(mi, opcode);
    mi.i := putByte(mi.code, mi.i, opcode);
    mi.i := putShort(mi.code, mi.i, to)
  END putJumpInsn;

  PROCEDURE putGotoInsn*(mi: MethodInfo; to, incr: INTEGER);
  BEGIN
    INC(mi.curStack, incr);
    IF to <= ShortMaxValue THEN
      mi.i := putByte(mi.code, mi.i, Opcodes.GOTO);
      mi.i := putShort(mi.code, mi.i, to)
    ELSE
      mi.i := putByte(mi.code, mi.i, Opcodes.GOTOW);
      mi.i := putInt(mi.code, mi.i, to)
    END
  END putGotoInsn;

  PROCEDURE putInsn*(mi: MethodInfo; opcode: INTEGER);
  BEGIN
    incStack(mi, opcode);
    mi.i := putByte(mi.code, mi.i, opcode)
  END putInsn;

  PROCEDURE setMaxVars*(mi: MethodInfo; locals: INTEGER);
  BEGIN
    mi.maxLocals := locals
  END setMaxVars;
END ClassFormat.


================================================
FILE: src/CpCache.Mod
================================================
(*
  Copyright 2017 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(* A string to integer map used by ClassFormat *)
MODULE CpCache;
  IMPORT Strings, OJS;
  CONST
    N = 997; (* prime number *)
    R = 31; (* prime number *)
    MaxKey* = OJS.stringBufSize;

  TYPE
    Node = POINTER TO NodeDesc;
    Key* = ARRAY MaxKey OF CHAR;
    NodeDesc = RECORD
      key: Key;
      val: INTEGER;
      next: Node
    END;
    Cache* = POINTER TO CacheDesc;
    CacheDesc* = RECORD
       map: ARRAY N OF Node;
    END;


  PROCEDURE hash(s: ARRAY OF CHAR): INTEGER;
    VAR h, i, len: INTEGER;
  BEGIN
    h := 0;
    i := 0;
    len := LEN(s);
    WHILE (i < len) & (s[i] # 0X) DO
      h := R * h + ORD(s[i]);
      INC(i)
    END
    (* MOD is >= 0 when N > 0 *)
    RETURN h MOD N
  END hash;

  PROCEDURE get*(m: Cache; k: ARRAY OF CHAR): INTEGER;
    VAR h, ret: INTEGER;
      c: Node;
  BEGIN
    h := hash(k);
    c := m.map[h];
    WHILE (c # NIL) & (c.key # k) DO
      c := c.next
    END;
    IF c = NIL THEN
      ret := -1
    ELSE
      ret := c.val
    END;
    RETURN ret
  END get;

  PROCEDURE put*(m: Cache; k: ARRAY OF CHAR; v: INTEGER);
    VAR h: INTEGER;
      r, c, tmp: Node;
  BEGIN
    h := hash(k);
    r := m.map[h];
    c := r;
    WHILE (c # NIL) & (c.key # k) DO
      c := c.next
    END;
    IF c = NIL THEN
      NEW(tmp);
      Strings.Copy(k, tmp.key);
      tmp.val := v;
      tmp.next := r;
      m.map[h] := tmp
    ELSE
      c.val := v
    END
  END put;

  PROCEDURE New*(): Cache;
    VAR c: Cache;
  BEGIN
    NEW(c);
    RETURN c
  END New;
END CpCache.


================================================
FILE: src/Files.Mod
================================================
(*
  Copyright 2017 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

DEFINITION Files;
  CONST
    OK = 0;
    EOF = -1;
    IOERROR = -2;
    UTF8ERROR = -3;

  TYPE
    File = POINTER TO FileDesc;
    FileDesc = RECORD END;
  VAR
    SEPARATOR: ARRAY 2 OF CHAR;

  PROCEDURE Create(name: ARRAY OF CHAR): File;
  PROCEDURE Open(name: ARRAY OF CHAR): File;
  PROCEDURE Close(file: File);

  PROCEDURE WriteAsciiStr(file: File; str: ARRAY OF CHAR);
  PROCEDURE WriteStr(file: File; str: ARRAY OF CHAR);
  PROCEDURE Write(file: File; b: BYTE);
  PROCEDURE WriteChar(file: File; c: CHAR);
  PROCEDURE WriteBytes(file: File; b: ARRAY OF BYTE);
  PROCEDURE WriteNBytes(file: File; b: ARRAY OF BYTE; len: INTEGER);
  PROCEDURE WriteInt(file: File; x: INTEGER);
  PROCEDURE WriteNum(file: File; x: INTEGER);

  PROCEDURE ReadNum(file: File): INTEGER;
  PROCEDURE Read(file: File): BYTE;
  PROCEDURE ReadChar(file: File): CHAR;
  PROCEDURE ReadInt(file: File): INTEGER;
  PROCEDURE ReadBytes(file: File; VAR b: ARRAY OF BYTE; VAR n: INTEGER);
  PROCEDURE ReadAsciiStr(file: File; VAR str: ARRAY OF CHAR);
  PROCEDURE ReadStr(file: File; VAR str: ARRAY OF CHAR): INTEGER;

  PROCEDURE Status(file: File): INTEGER;
  PROCEDURE Rename(from, to: ARRAY OF CHAR): INTEGER;
  PROCEDURE Exists(name: ARRAY OF CHAR): BOOLEAN;
  PROCEDURE Delete(name: ARRAY OF CHAR): INTEGER;
  PROCEDURE Seek(file: File; pos: INTEGER): INTEGER;
  PROCEDURE Size(file: File): INTEGER;
END Files.


================================================
FILE: src/In.Mod
================================================
(*
  Copyright 2019 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(*
  Simple procedures to read from standard input in an interactive fashion.

  Done. Indicates the success of an input operation. If Done is TRUE after an
  input operation, the operation was successful and its result is valid.
  An unsuccessful input operation sets Done to FALSE; it remains FALSE until
  the next successful input operation.

  Each procedure reads the input stream and fills its parameter with
  input data when successful. String reads an entire line of input.
*)
DEFINITION In;
  VAR Done: BOOLEAN;
  PROCEDURE Char(VAR ch: CHAR);
  PROCEDURE String(VAR str: ARRAY OF CHAR);
  PROCEDURE Real(VAR x: REAL);
  PROCEDURE Int(VAR x: INTEGER);
END In.


================================================
FILE: src/Math.Mod
================================================
(*
  Copyright 2020 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(* Math provides a basic set of general purpose mathematical functions using
   REAL arithmetic. It implements the interface described in "The Oakwood
   Guidelines for Oberon-2 Compiler Developers". *)
DEFINITION Math;
  CONST
    pi = 3.14159265358979323846;
    e = 2.71828182845904523536;

  (* sqrt returns the square root of x, where x must be positive *)
  PROCEDURE sqrt(x: REAL): REAL;

  (* power returns base raised to the power of x. *)
  PROCEDURE power(x, base: REAL): REAL;

  (* exp returns Math.e raised to the power of x: power(x, e)*)
  PROCEDURE exp(x: REAL): REAL;

  (* ln returns the natural logarithm (base e) of x. *)
  PROCEDURE ln(x: REAL): REAL;

  (* log returns the logarithm of x base b. All positive arguments are allowed.
     The base b must be positive.*)
  PROCEDURE log(x, b: REAL): REAL;

  (* If the fraction part of x is in range 0.0 to 0.5 (excluded) then the result
     of round is the largest integer not greater than x, otherwise the result
     is x rounded up to the next highest whole number.
     Note that integer values cannot always be exactly represented in REAL
     format. *)
  PROCEDURE round(x: REAL): REAL;

  (* sin returns the sine value of x, where x is in radians. *)
  PROCEDURE sin(x: REAL): REAL;

  (* cos returns the cosine value of x, where x is in radians. *)
  PROCEDURE cos(x: REAL): REAL;

  (* tan returns the tangent value of x, where x is in radians. *)
  PROCEDURE tan(x: REAL): REAL;

  (* arcsin returns the arcsine value in radians of x, where x is
     in the sine value. *)
  PROCEDURE arcsin(x: REAL): REAL;

  (* arcos returns the arcos value in radians of x, where x is
    in the cosine value. *)
  PROCEDURE arccos(x: REAL): REAL;

  (* arctan returns the arctan value in radians of x, where x is
    in the tangent value. *)
  PROCEDURE arctan(x: REAL): REAL;

  (* arctan2 returns the quadrant-correct arc tangent atan(x/y).
     If the denominator y is zero, then the numerator x must not be zero.
     All arguments are legal except x = y = 0. *)
  PROCEDURE arctan2(x, y: REAL): REAL;

  (* sinh returns the hyperbolic sine of x. *)
  PROCEDURE sinh(x: REAL): REAL;

  (* cosh returns the hyperbolic cosine of x. *)
  PROCEDURE cosh(x: REAL): REAL;

  (* tanh returns the hyperbolic tangent of x. *)
  PROCEDURE tanh(x: REAL): REAL;

  (* arcsinh returns the arc hyperbolic sine of x. *)
  PROCEDURE arcsinh(x: REAL): REAL;

  (* arccosh returns the arc hyperbolic cosine of x. All arguments greater than
     or equal to 1 are legal. *)
  PROCEDURE arccosh(x: REAL): REAL;

  (* arctanh returns the arc hyperbolic tangent of x. *)
  PROCEDURE arctanh(x: REAL): REAL;
END Math.


================================================
FILE: src/OJB.Mod
================================================
(*
  Copyright 2017 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(*
  Definition of data types Object, Type, and Module which together form the data
  structure called "symbol table". Contains procedures for creation of
  Objects, and for search.
  Handling of import and export, i.e. reading and writing of "symbol files"
  is done by procedures Import and Export. This module contains the list of
  standard identifiers, with which the symbol table (universe), and that of the
  pseudo-module SYSTEM are initialized.
*)
MODULE OJB;
  IMPORT Files, OJS, ClassFormat, Strings, Os;

  CONST versionkey = 1;  keypos = 2;
    MaxTyptab = 500; MaxLModtab = 100; MaxGModtab = 200;

    (* class values *) Head = 0;
      Const* = 1; Var* = 2; Par* = 3; ParStruct* = 4;
      Fld* = 5; Typ* = 6; SProc* = 7; SFunc* = 8; Mod* = 9;

    (* form values *)
      Byte* = 1; Bool* = 2; Char* = 3; Int* = 4; Real* = 5;  Set* = 6;
      Pointer* = 7; NilTyp* = 8; NoTyp* = 9; Proc* = 10; String* = 11;
      Array* = 12; Record* = 13;

  TYPE Object* = POINTER TO ObjDesc;
    Type* = POINTER TO TypeDesc;

    ObjDesc* = RECORD
      class*, modref: INTEGER;
      (*
         when class = Typ, lev <= 0 -> mno = -lev, 0 is the compiling module
         otherwise lev >= 0 -> scope level, 0 is the global scope
       *)
      lev*: INTEGER;
      expo*, rdo*: BOOLEAN;   (*exported / read-only*)
      used: BOOLEAN;
      next*, dsc*: Object;
      type*: Type;
      recordType*, caseOrgType*: Type; (* computed, not serialized *)
      name*: OJS.Ident;
      (*
        Nested procedures and types (lev > 1, expo = FALSE) must have an unique
        bytecode name, as the nesting is flattened during code generation.
        nestedId is an unique integer that is appended to name in the bytecode.
        A valid nestedId is >= 1.
      *)
      nestedId*: INTEGER;
      val*, len*: INTEGER
    END ;

    TypeDesc* = RECORD
      form*, ref*: INTEGER;  (*ref is only used for import/export*)
      nofpar*: INTEGER;  (*for procedures, extension level for records*)
      len*: INTEGER;  (*for arrays, len < 0 -> open array*)
      dsc*, typobj*: Object;
      base*: Type;  (*for arrays, records, pointers*)
      signature*: ClassFormat.Descriptor;
    END;

  VAR topScope*, universe, system*: Object;
    byteType*, boolType*, charType*: Type;
    intType*, realType*, setType*, nilType*, noType*, strType*: Type;
    nofGMods, nofLMods, nofLTypes, anonRecIdx: INTEGER;
    GModtab: ARRAY MaxGModtab OF Object; (* GModtab[0] = compiling module *)
    LModtab: ARRAY MaxLModtab OF Object;
    LTyptab: ARRAY MaxTyptab OF Type; (* LTyptab[0] = NIL *)
    outFolder, homeFolder: ARRAY OJS.maxPath OF CHAR;

  PROCEDURE getOutputFolder*(VAR folder: ARRAY OF CHAR): INTEGER;
    VAR i: INTEGER;
  BEGIN
    i := Strings.Write(outFolder, folder, 0)
    RETURN i
  END getOutputFolder;

  PROCEDURE GetModFrom*(obj: Object): Object;
    VAR modIdx: INTEGER;
  BEGIN
    modIdx := 0;
    IF (obj # NIL) & (obj.lev < 0) THEN
      (* external type *)
      modIdx := -obj.lev;
    END
    RETURN GModtab[modIdx]
  END GetModFrom;

  (*insert new Object with name id*)
  PROCEDURE InsertObj*(id: OJS.Ident; class: INTEGER): Object;
    VAR new, x, obj: Object;
  BEGIN x := topScope;
    WHILE (x.next # NIL) & (x.next.name # id) DO x := x.next END ;
    IF x.next = NIL THEN
      NEW(new); new.name := id; new.nestedId := 0;
      new.class := class; new.next := NIL; new.rdo := FALSE; new.dsc := NIL;
      new.modref := -1; (*un-marked*); new.lev := 0; new.len := 0; new.val := 0;
      new.expo := FALSE; new.type := noType; new.recordType := NIL;
      new.caseOrgType := NIL; new.used := FALSE;
      x.next := new; obj := new
    ELSE obj := x.next; OJS.Mark("mult def")
    END ;
    RETURN obj
  END InsertObj;

  PROCEDURE generateAnonymousTypeObj*(type: Type): Object;
    VAR anon: Object;
  BEGIN
    NEW(anon);
    Strings.Append("$Anonymous", anon.name);
    Strings.AppendInt(anonRecIdx, 0, anon.name);
    anon.class := Typ;
    anon.type := type;
    anon.lev := 0;
    anon.expo := FALSE;
    type.typobj := anon;
    INC(anonRecIdx)
    RETURN anon
  END generateAnonymousTypeObj;

  PROCEDURE thisObj*(name: ARRAY OF CHAR): Object;
    VAR s, x: Object;
  BEGIN s := topScope;
    REPEAT x := s.next;
      WHILE (x # NIL) & (x.name # name) DO x := x.next END ;
      IF (x # NIL) & (s # topScope) & (x.lev > 0) & (* no local or global *)
          (x.class IN {Var, Par, ParStruct}) THEN
        OJS.Mark("not accessible");
        x := NIL
      END ;
      s := s.dsc
    UNTIL (x # NIL) OR (s = NIL);
    IF x # NIL THEN x.used := TRUE END
    RETURN x
  END thisObj;

  PROCEDURE thisimport*(mod: Object; name: ARRAY OF CHAR): Object;
    VAR obj: Object;
  BEGIN
    obj := NIL;
    IF (mod # NIL) & (mod.class = Mod) THEN
      obj := mod.dsc;
      WHILE (obj # NIL) & (~obj.expo OR (obj.name # name)) DO
        obj := obj.next
      END
    END
    RETURN obj
  END thisimport;

  PROCEDURE thisfield*(rec: Type): Object;
    VAR fld: Object;
  BEGIN fld := rec.dsc;
    WHILE (fld # NIL) & (fld.name # OJS.id) DO fld := fld.next END ;
    RETURN fld
  END thisfield;

  PROCEDURE FindObj*(modid, modName, name: ARRAY OF CHAR): Object;
    VAR obj: Object;
  BEGIN
    IF modid # modName THEN
      obj := thisObj(modName);
      obj := thisimport(obj, name)
    ELSE
      obj := thisObj(name)
    END
    RETURN obj
  END FindObj;

  PROCEDURE OpenScope*;
    VAR s: Object;
  BEGIN NEW(s); s.class := Head; s.dsc := topScope; s.next := NIL; topScope := s
  END OpenScope;

  PROCEDURE CheckUnused*(parsNum: INTEGER);
    VAR err: ARRAY OJS.IdLen*30 OF CHAR;
      i: INTEGER; x: Object;
  BEGIN
    x := topScope.next;
    i := 0;
    (* skip parameters *)
    WHILE i < parsNum DO x := x.next; INC(i) END;
    i := 0;
    WHILE x # NIL DO
      IF ~x.expo & ~x.used & ((x.class = Var) OR (x.class = Mod)) THEN
        i := Strings.Write(x.name, err, i);
        i := Strings.WriteChar(" ", err, i)
      END ;
      x := x.next
    END ;
    IF i # 0 THEN OJS.MarkAppend("Unused: ", err) END
  END CheckUnused;

  PROCEDURE CloseScope*;
  BEGIN topScope := topScope.dsc
  END CloseScope;

  (*------------------------------- Import ---------------------------------*)

  PROCEDURE MakeFileName*(useHome: BOOLEAN; VAR FName: ARRAY OF CHAR;
                          name, ext: ARRAY OF CHAR);
    VAR i: INTEGER;
  BEGIN
    IF useHome THEN
      i := Strings.Write(homeFolder, FName, 0)
    ELSE
      i := getOutputFolder(FName)
    END ;
    i := Strings.Write(Files.SEPARATOR, FName, i);
    i := Strings.Write(name, FName, i);
    i := Strings.Write(ext, FName, i);
    IF i = -1 THEN OJS.Mark("Maximum file path length reached") END
  END MakeFileName;

  PROCEDURE findSymFile(VAR fname: ARRAY OF CHAR;
                        modName: ARRAY OF CHAR): Files.File;
    VAR f: Files.File;
  BEGIN
    MakeFileName(FALSE, fname, modName, ".smb");
    f := Files.Open(fname);
    IF f = NIL THEN
      MakeFileName(TRUE, fname, modName, ".smb");
      f := Files.Open(fname)
    END
    RETURN f
  END findSymFile;

  PROCEDURE InsertImport(obj, mod: Object): Object;
    VAR prev, cur: Object;
  BEGIN
    IF mod.dsc = NIL THEN
      mod.dsc := obj
    ELSE
      prev := NIL;
      cur := mod.dsc;
      WHILE (cur # NIL) & (cur.name # obj.name) DO
        prev := cur;
        cur := cur.next
      END ;
      IF cur = NIL THEN prev.next := obj ELSE obj := cur END
    END ;
    obj.lev := mod.lev
    RETURN obj
  END InsertImport;

  PROCEDURE InsertMod(name: OJS.Ident; key: INTEGER): Object;
    VAR mod: Object; i: INTEGER;
  BEGIN
    i := 0;
    WHILE (i < nofGMods) & (name # GModtab[i].name) DO INC(i) END;
    IF i < nofGMods THEN (* module already imported *)
      mod := GModtab[i];
      IF mod.val # key THEN OJS.Mark("key inconsistency of imported module") END
    ELSE
      NEW(mod); mod.class := Mod; mod.rdo := TRUE; mod.expo := FALSE;
      Strings.Copy(name, mod.name); mod.val := key;
      mod.lev := -nofGMods; mod.type := noType;
      mod.dsc := NIL; mod.next := NIL;
      mod.modref := -1;
      IF nofGMods < MaxGModtab THEN
        GModtab[nofGMods] := mod; INC(nofGMods)
      ELSE
        OJS.Mark("too many imported modules")
      END
    END
    RETURN mod
  END InsertMod;

  PROCEDURE InMod(f: Files.File; selfName: OJS.Ident): Object;
    VAR
      ref, key: INTEGER;
      name: OJS.Ident;
      mod: Object;
  BEGIN
    ref := Files.ReadNum(f);
    IF ref > 0 THEN (* first occurrence *)
      key := Files.ReadInt(f);
      Files.ReadAsciiStr(f, name);
      IF name = selfName THEN OJS.Mark("recursive import not allowed") END;
      mod := InsertMod(name, key);
      IF nofLMods < MaxLModtab THEN
        LModtab[nofLMods] := mod; INC(nofLMods)
      ELSE
        OJS.Mark("too many imported modules")
      END
    ELSE
      mod := LModtab[-ref]
    END
    RETURN mod
  END InMod;

  PROCEDURE InType(f: Files.File; selfName: OJS.Ident): Type;
    VAR
      class, form, np: INTEGER;
      fld, par, obj, last, mod: Object;
      typ, htyp: Type;
      name: OJS.Ident;
  BEGIN
    typ := NIL;
    IF Files.Status(f) = Files.OK THEN
      form := Files.ReadNum(f);
      IF form <= 0 THEN typ := LTyptab[-form] (* already read or NIL *)
      ELSE
        NEW(htyp); htyp.form := form;
        Files.ReadAsciiStr(f, name);
        IF name[0] # 0X THEN (* named type *)
          NEW(obj); Strings.Copy(name, obj.name);
          obj.expo := Files.ReadNum(f) = 1;
          obj.class := Typ; obj.type := htyp;
          htyp.typobj := obj; mod := InMod(f, selfName);
          obj := InsertImport(obj, mod);
          typ := obj.type
        ELSE
          typ := htyp
        END ;
        IF nofLTypes < MaxTyptab THEN LTyptab[nofLTypes] := typ; INC(nofLTypes)
        ELSE OJS.Mark("too many imported types")
        END;
        IF form = Pointer THEN htyp.base := InType(f, selfName)
        ELSIF form = Array THEN
          htyp.base := InType(f, selfName); htyp.len := Files.ReadNum(f)
        ELSIF form = Record THEN
          htyp.base := InType(f, selfName);
          IF htyp.base = NIL THEN obj := NIL
          ELSE obj := htyp.base.dsc
          END;
          class := Files.ReadNum(f);
          last := NIL;
          WHILE class # 0 DO (* fields *)
            NEW(fld); fld.class := class;
            Files.ReadAsciiStr(f, fld.name);
            fld.expo := TRUE; fld.type := InType(f, selfName);
            fld.recordType := htyp;
            fld.val := 0; class := Files.ReadNum(f);
            IF last = NIL THEN htyp.dsc := fld ELSE last.next := fld END ;
            last := fld
          END ;
          (* append base type fields *)
          IF last = NIL THEN htyp.dsc := obj ELSE last.next := obj END
        ELSIF form = Proc THEN
          htyp.base := InType(f, selfName);
          Files.ReadAsciiStr(f, htyp.signature);
          np := Files.ReadNum(f);
          htyp.nofpar := np;
          par := NIL;
          last := NIL;
          WHILE np > 0 DO (* parameters *)
            NEW(obj); obj.class := Files.ReadNum(f);
            obj.rdo := Files.ReadNum(f) = 1;
            obj.type := InType(f, selfName);
            IF par = NIL THEN par := obj ELSE last.next := obj END;
            last := obj; DEC(np)
          END ;
          htyp.dsc := par
        END
      END
    END
    RETURN typ
  END InType;

  PROCEDURE Import*(VAR aliasName, impName, selfName: OJS.Ident);
    VAR
      class, version, i: INTEGER;
      obj, mod, mod0, dummyMod: Object;
      dummyType: Type;
      name: OJS.Ident;
      fname: ARRAY OJS.maxPath OF CHAR;
      str: ARRAY OJS.stringBufSize OF CHAR;
      f: Files.File;
  BEGIN
    IF impName = "SYSTEM" THEN
      mod := InsertObj(aliasName, Mod);
      mod.dsc := system; mod.rdo := TRUE
    ELSE
      f := findSymFile(fname, impName);
      IF f # NIL THEN
        nofLMods := 0;
        nofLTypes := Record + 1;
        version := Files.ReadNum(f);
        IF version # versionkey THEN OJS.Mark("wrong symbol version key") END;
        mod0 := InMod(f, selfName);
        IF mod0.name # impName THEN OJS.Mark("inconsistent module name") END;
        (* Read imported modules *)
        i := Files.ReadNum(f);
        WHILE i > 0 DO dummyMod := InMod(f, selfName); DEC(i) END;
        class := Files.ReadNum(f);
        WHILE (class # 0) & (Files.Status(f) = Files.OK) DO
          IF class = Typ THEN
            Files.ReadAsciiStr(f, name);
            IF name[0] # 0X THEN (* alias type *)
              NEW(obj); Strings.Copy(name, obj.name);
              obj.class := class; obj.expo := TRUE;
              obj.type := InType(f, selfName);
              obj := InsertImport(obj, mod0)
            ELSE (* other types *)
              dummyType := InType(f, selfName)
            END
          ELSE
            Files.ReadAsciiStr(f, name); NEW(obj);
            Strings.Copy(name, obj.name); obj.class := class;
            obj.expo := TRUE;
            obj.type := InType(f, selfName);
            IF class = Const THEN
              IF obj.type.form = Real THEN obj.val := Files.ReadInt(f)
              ELSIF obj.type.form = String THEN
                obj.len := Files.ReadStr(f, str) + 1; (* length + 0X *)
                obj.val := OJS.InsertStr(str, obj.len)
              ELSE obj.val := Files.ReadNum(f)
              END
            ELSIF class = Var THEN obj.rdo := TRUE
            END ;
            obj := InsertImport(obj, mod0)
          END ;
          class := Files.ReadNum(f)
        END ;
        mod := InsertObj(aliasName, Mod);
        mod.rdo := TRUE;
        mod.val := mod0.val; mod.lev := mod0.lev; mod.dsc := mod0.dsc;
        Files.Close(f);
        IF Files.Status(f) = Files.IOERROR THEN
          OJS.MarkAppend("error importing ", fname)
        END
      ELSE
        OJS.MarkAppend("import not available: ", fname)
      END
    END
  END Import;

  (*-------------------------------- Export ---------------------------------*)

  PROCEDURE OutMod(f: Files.File; mod: Object);
  BEGIN
    IF mod.modref < 0 THEN (* first occurrence *)
      mod.modref := nofLMods;
      INC(nofLMods);
      Files.WriteNum(f, Mod); Files.WriteInt(f, mod.val);
      Files.WriteAsciiStr(f, mod.name)
    ELSE
      Files.WriteNum(f, -mod.modref)
    END
  END OutMod;

  PROCEDURE OutType(f: Files.File; t: Type);
    VAR fld, par, bot: Object;
      np: INTEGER;
  BEGIN
    IF Files.Status(f) = Files.OK THEN
      IF t = NIL THEN Files.WriteNum(f, 0)
      ELSIF t.ref > 0 THEN (*type was already output*) Files.WriteNum(f, -t.ref)
      ELSE
        Files.WriteNum(f, t.form);
        t.ref := nofLTypes; INC(nofLTypes);
        IF t.typobj # NIL THEN (* named type *)
          Files.WriteAsciiStr(f, t.typobj.name);
          IF ~t.typobj.expo THEN (* invisible type *)
            Files.WriteNum(f, 0)
          ELSE
            Files.WriteNum(f, 1)
          END ;
          OutMod(f, GModtab[-t.typobj.lev])
        ELSE
          Files.WriteNum(f, 0)
        END ;
        IF t.form = Pointer THEN OutType(f, t.base)
        ELSIF t.form = Array THEN OutType(f, t.base); Files.WriteNum(f, t.len)
        ELSIF t.form = Record THEN
          OutType(f, t.base);
          IF t.base # NIL THEN
            bot := t.base.dsc
          ELSE
            bot := NIL
          END ;
          fld := t.dsc;
          WHILE fld # bot DO  (*fields*)
            IF fld.expo THEN
              Files.WriteNum(f, Fld); Files.WriteAsciiStr(f, fld.name);
              OutType(f, fld.type)
            END ;
            fld := fld.next
          END ;
          Files.WriteNum(f, 0)
        ELSIF t.form = Proc THEN
          OutType(f, t.base);
          Files.WriteAsciiStr(f, t.signature);
          par := t.dsc;
          np := t.nofpar;
          Files.WriteNum(f, np);
          WHILE np > 0 DO
            Files.WriteNum(f, par.class);
            IF par.rdo THEN Files.WriteNum(f, 1) ELSE Files.WriteNum(f, 0) END;
            OutType(f, par.type);
            par := par.next;
            DEC(np)
          END
        END
      END
    END
  END OutType;

  PROCEDURE readOldKey(filename: ARRAY OF CHAR; VAR oldkey: INTEGER): BOOLEAN;
    VAR f: Files.File;
      ok: BOOLEAN;
  BEGIN
    ok := FALSE;
    f := Files.Open(filename);
    IF (f # NIL) & (Files.Seek(f, keypos) = Files.OK) THEN
      oldkey := Files.ReadInt(f);
      Files.Close(f);
      ok := Files.Status(f) = Files.OK
    END
    RETURN ok
  END readOldKey;

  PROCEDURE Export*(VAR modid: OJS.Ident; newSF: BOOLEAN);
    VAR x, sum, i, r, len, rename, oldkey: INTEGER;
      obj: Object;
      filename, tmpFile: ARRAY OJS.maxPath OF CHAR;
      str: ARRAY OJS.stringBufSize OF CHAR;
      f: Files.File;
      found: BOOLEAN;
  BEGIN
    rename := Files.OK;
    nofLMods := 0;
    nofLTypes := Record + 1; MakeFileName(FALSE, filename, modid, ".smb");
    MakeFileName(FALSE, tmpFile, modid, ".smb.tmp");
    f := Files.Create(tmpFile);
    IF f # NIL THEN
      Files.WriteNum(f, versionkey);
      OutMod(f, GModtab[0]);
      (* Write imported modules *)
      Files.WriteNum(f, nofGMods - 1);
      i := 1;
      WHILE i < nofGMods DO OutMod(f, GModtab[i]); INC(i) END;
      obj := topScope.next;
      WHILE (obj # NIL) & (Files.Status(f) = Files.OK) DO
        IF obj.expo THEN
          Files.WriteNum(f, obj.class);
          IF (obj.class # Typ) OR (obj.type.typobj # obj) THEN
            (* no type or alias type *)
            Files.WriteAsciiStr(f, obj.name)
          ELSE
            (* other type, write name in OutType *)
            Files.WriteNum(f, 0)
          END ;
          OutType(f, obj.type);
          IF obj.class = Const THEN
            IF obj.type.form = Real THEN Files.WriteInt(f, obj.val)
            ELSIF obj.type.form = String THEN
              OJS.ExtractStr(obj.val, obj.len, str);
              Files.WriteStr(f, str)
            ELSE Files.WriteNum(f, obj.val)
            END
          END
        END ;
        obj := obj.next;
      END ;
      len := Files.Size(f);
      IF len # -1 THEN
        REPEAT Files.WriteNum(f, 0); INC(len) UNTIL len MOD 4 = 0;
      END ;
      (* reset local type table *)
      FOR nofLTypes := Record+1 TO MaxTyptab-1 DO LTyptab[nofLTypes] := NIL END ;
      (* compute key (checksum) *)
      r := Files.Seek(f, 0); sum := Files.ReadInt(f);
      i := 4;
      WHILE (i < len) & (Files.Status(f) = Files.OK) DO
        x := Files.ReadInt(f); sum := sum + x; INC(i, 4)
      END ;
      found := readOldKey(filename, oldkey);
      IF ~found OR (sum # oldkey) THEN
        IF newSF OR ~found THEN
          r := Files.Seek(f, keypos);
          Files.WriteInt(f, sum);  (*insert checksum*)
          Files.Close(f);
          rename := Files.Rename(tmpFile, filename)
        ELSE Files.Close(f); OJS.Mark("new symbol file inhibited")
        END
      ELSE
        Files.Close(f);
        r := Files.Delete(tmpFile)
      END ;
      IF (Files.Status(f) = Files.IOERROR) OR (rename = Files.IOERROR) OR
         (r = Files.IOERROR) THEN
        OJS.MarkAppend("error exporting ", filename)
      END
    ELSE
      OJS.MarkAppend("error while creating symbol file ", filename)
    END
  END Export;

  PROCEDURE Init*(outputFolder: ARRAY OF CHAR; modid: OJS.Ident);
    VAR dummy: Object;
  BEGIN
    topScope := universe; nofGMods := 0; anonRecIdx := 0;
    Strings.Copy(outputFolder, outFolder);
    dummy := InsertMod(modid, 0)
  END Init;

  PROCEDURE type(ref, form: INTEGER): Type;
    VAR tp: Type;
  BEGIN NEW(tp); tp.form := form; tp.ref := ref; tp.base := NIL;
    LTyptab[ref] := tp
    RETURN tp
  END type;

  PROCEDURE enter(name: ARRAY OF CHAR; cl: INTEGER; type: Type; n: INTEGER);
    VAR obj: Object;
  BEGIN NEW(obj); Strings.Copy(name, obj.name); obj.class := cl;
    obj.type := type; obj.val := n; obj.dsc := NIL;
    IF cl = Typ THEN type.typobj := obj END ;
    obj.next := system; system := obj
  END enter;

BEGIN
  byteType := type(Byte, Int);
  boolType := type(Bool, Bool);
  charType := type(Char, Char);
  intType := type(Int, Int);
  realType := type(Real, Real);
  setType := type(Set, Set);
  nilType := type(NilTyp, NilTyp);
  noType := type(NoTyp, NoTyp);
  strType := type(String, String);

  (*initialize universe with data types and in-line procedures;
    LONGINT is synonym to INTEGER, LONGREAL to REAL. *)
  system := NIL;  (*n = procno*10 + nofpar*)
  enter("BOR", SFunc, intType, 122);  (*functions*)
  enter("AND", SFunc, intType, 112);
  enter("NOT", SFunc, intType, 151);
  enter("ROR", SFunc, intType, 92);
  enter("ASR", SFunc, intType, 82);
  enter("LSL", SFunc, intType, 72);
  enter("LEN", SFunc, intType, 61);
  enter("CHR", SFunc, charType, 51);
  enter("ORD", SFunc, intType, 41);
  enter("FLT", SFunc, realType, 31);
  enter("FLOOR", SFunc, intType, 21);
  enter("ODD", SFunc, boolType, 11);
  enter("ABS", SFunc, intType, 1);
  enter("NEW", SProc, noType, 51);
  enter("ASSERT", SProc, noType, 41);
  enter("EXCL", SProc, noType, 32);
  enter("INCL", SProc, noType, 22);
  enter("DEC", SProc, noType, 11);
  enter("INC", SProc, noType, 1);
  enter("SET", Typ, setType, 0);   (*types*)
  enter("BOOLEAN", Typ, boolType, 0);
  enter("BYTE", Typ, byteType, 0);
  enter("CHAR", Typ, charType, 0);
  enter("LONGREAL", Typ, realType, 0);
  enter("REAL", Typ, realType, 0);
  enter("LONGINT", Typ, intType, 0);
  enter("INTEGER", Typ, intType, 0);
  enter("ARGNUM", SFunc, intType, 230);
  enter("ARGS", SProc, noType, 242);

  (* Useful during bootstrapping *)
  enter("eot", SFunc, boolType, 210);
  enter("ReadInt", SFunc, intType, 220);
  enter("WriteChar", SProc, noType, 151);
  enter("WriteInt", SProc, noType, 161);
  enter("WriteLn", SProc, noType, 170);
  enter("WriteReal", SProc, noType, 181);

  topScope := NIL; OpenScope; topScope.next := system; universe := topScope;

  system := NIL;  (* initialize "unsafe" pseudo-module SYSTEM*)
  enter("VAL", SFunc, intType, 162);
  system.expo := TRUE; (* export VAL *)
  Os.GetEnv(homeFolder, "OBERON_BIN");
  IF homeFolder = "" THEN homeFolder := "." END
END OJB.


================================================
FILE: src/OJG.Mod
================================================
(*
  Copyright 2017 Luca Boasso.
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(*Code generator for Oberon compiler for the Java Virtual Machine.*)
MODULE OJG;
  IMPORT SYSTEM, Files, Opcodes, Strings, ClassFormat, OJS, OJB;

  CONST NofCases* = 256;
    MaxSetElement* = 32;
    ParamsMax* = 20;
    Stack = 10; RegI = 11; Cond = 12; Field = 13; (*internal item modes*)

  TYPE
    Item* = RECORD
      mode*: INTEGER;
      type*, oldType*: OJB.Type;
      a*, b*, r: INTEGER;
      rdo*: BOOLEAN;  (*read only*)
      name*: ARRAY OJS.IdLen + ClassFormat.nestedIdLen OF CHAR;
      modName*: OJS.Ident;
      recordName: ClassFormat.Descriptor
    END ;

    LabelRange* = RECORD low*, high*, L*: INTEGER END ;

    StoreStmt = POINTER TO StoreStmtDesc;
    StoreStmtDesc = RECORD
      x, y: Item;
      next: StoreStmt
    END ;

    ClassContext = POINTER TO ClassContextDesc;
    ClassContextDesc = RECORD
      c: ClassFormat.CF;
      m: ClassFormat.MethodInfo;
      className:  ClassFormat.Descriptor;
      numTmpVars: INTEGER;
      storeStmt: StoreStmt;
      next: ClassContext
    END ;

  VAR
    check: BOOLEAN;  (*emit run-time checks*)
    topCtx: ClassContext;
    relmap: ARRAY 6 OF INTEGER;  (*condition codes for relations*)
    relmap0: ARRAY 6 OF INTEGER;  (*condition codes for relations with 0*)
    relmapNil: ARRAY 2 OF INTEGER;  (*condition codes for relations with NIL*)
    relmapAdr: ARRAY 2 OF INTEGER;  (*condition codes for relations with adr*)
    dummyMethod: ClassFormat.MethodInfo;
  PROCEDURE curStack*(): INTEGER;
    RETURN topCtx.m.curStack
  END curStack;

  PROCEDURE clearCtx(ctx: ClassContext);
  BEGIN
    ctx.numTmpVars := 0
  END clearCtx;

  PROCEDURE closeContext;
  BEGIN
    ClassFormat.finalizeMethod(topCtx.c, topCtx.m);
    clearCtx(topCtx);
    topCtx.m := dummyMethod
  END closeContext;

  PROCEDURE SetCC(VAR x: Item; n: INTEGER);
  BEGIN
    x.mode := Cond;
    x.a := 0;
    x.b := 0;
    x.r := n;
  END SetCC;

  PROCEDURE negated(op: INTEGER): INTEGER;
    VAR ret: INTEGER;
  BEGIN
    ret := 0;
    CASE op OF
       Opcodes.IFACMPEQ: ret := Opcodes.IFACMPNE
      | Opcodes.IFACMPNE: ret := Opcodes.IFACMPEQ
      | Opcodes.IFEQ: ret := Opcodes.IFNE
      | Opcodes.IFNE: ret := Opcodes.IFEQ
      | Opcodes.IFLT: ret := Opcodes.IFGE
      | Opcodes.IFGE: ret := Opcodes.IFLT
      | Opcodes.IFGT: ret := Opcodes.IFLE
      | Opcodes.IFLE: ret := Opcodes.IFGT
      | Opcodes.IFICMPEQ: ret := Opcodes.IFICMPNE
      | Opcodes.IFICMPNE: ret := Opcodes.IFICMPEQ
      | Opcodes.IFNULL: ret := Opcodes.IFNONNULL
      | Opcodes.IFNONNULL: ret := Opcodes.IFNULL
      | Opcodes.IFICMPLT: ret := Opcodes.IFICMPGE
      | Opcodes.IFICMPGE: ret := Opcodes.IFICMPLT
      | Opcodes.IFICMPLE: ret := Opcodes.IFICMPGT
      | Opcodes.IFICMPGT: ret := Opcodes.IFICMPLE
    END
    RETURN ret
  END negated;

  PROCEDURE normalize(s: ARRAY OF CHAR; VAR out: ARRAY OF CHAR;
                      i: INTEGER): INTEGER;
    VAR ch: CHAR;
      j, slen, olen: INTEGER;
  BEGIN
    IF i >= 0 THEN
      j := 0;
      slen := LEN(s);
      olen := LEN(out)-1;
      WHILE (j < slen) & (i < olen) & (s[j] # 0X) DO
        ch := s[j];
        IF (ch < "0") OR (ch > "9") & (ch < "A") OR
           (ch > "Z") & (ch < "a") OR (ch > "z") THEN
          ch := "x"
        END ;
        out[i] := ch;
        INC(i);
        INC(j)
      END ;
      out[i] := 0X
    END
    RETURN i
  END normalize;

  PROCEDURE internalNameAt(type: OJB.Type; VAR out: ARRAY OF CHAR;
                           i: INTEGER): INTEGER;
    VAR mod: OJB.Object;
  BEGIN
    IF type.form = OJB.Record THEN
      mod := OJB.GetModFrom(type.typobj);
      i := Strings.Write(mod.name, out, i);
      i := Strings.WriteChar("_", out, i);
      i := Strings.Write(type.typobj.name, out, i);
      IF type.typobj.nestedId > 0 THEN
        i := Strings.WriteInt(type.typobj.nestedId, 0, out, i)
      END
    ELSIF type.form = OJB.Pointer THEN
      mod := OJB.GetModFrom(type.base.typobj);
      i := Strings.Write(mod.name, out, i);
      i := Strings.WriteChar("_", out, i);
      i := Strings.Write(type.base.typobj.name, out, i);
      IF type.base.typobj.nestedId > 0 THEN
        i := Strings.WriteInt(type.base.typobj.nestedId, 0, out, i)
      END
    ELSIF type.form = OJB.Proc THEN
      i := normalize(type.signature, out, i)
    END
    RETURN i
  END internalNameAt;


  PROCEDURE internalName(type: OJB.Type; VAR out: ARRAY OF CHAR);
    VAR i: INTEGER;
  BEGIN
    i := internalNameAt(type, out, 0);
    IF (i = -1) THEN OJS.Mark("Maximum descriptor length reached") END
  END internalName;

  PROCEDURE DescriptorR(type: OJB.Type; VAR out: ARRAY OF CHAR;
                        i: INTEGER): INTEGER;
  BEGIN
    IF type = OJB.boolType THEN
      i := Strings.WriteChar("Z", out, i)
    ELSIF (type = OJB.intType) OR (type = OJB.setType) THEN
      i := Strings.WriteChar("I", out, i)
    ELSIF type = OJB.byteType THEN
      i := Strings.WriteChar("B", out, i)
    ELSIF type = OJB.charType THEN
      i := Strings.WriteChar("C", out, i)
    ELSIF type = OJB.realType THEN
      i := Strings.WriteChar("F", out, i)
    ELSIF type = OJB.noType THEN
      i := Strings.WriteChar("V", out, i)
    ELSIF (type.form = OJB.Record) OR (type.form = OJB.Pointer) THEN
      i := Strings.WriteChar("L", out, i);
      i := internalNameAt(type, out, i);
      i := Strings.WriteChar(";", out, i)
    ELSIF type.form = OJB.Array THEN
      i := Strings.WriteChar("[", out, i);
      i := DescriptorR(type.base, out, i)
    ELSIF type.form = OJB.Proc THEN
      i := Strings.WriteChar("L", out, i);
      i := normalize(type.signature, out, i);
      i := Strings.WriteChar(";", out, i)
    END
    RETURN i
  END DescriptorR;

  PROCEDURE DescriptorProc(type: OJB.Type; end: INTEGER;
                           VAR desc: ARRAY OF CHAR);
    VAR args: OJB.Object;
      i, j: INTEGER;
  BEGIN
    i := 0;
    j := 0;
    j := Strings.WriteChar("(", desc, j);
    args := type.dsc;
    WHILE (args # NIL) & (i < end) DO
      IF args.class = OJB.Par THEN
        j := Strings.WriteChar("[", desc, j)
      END ;
      j := DescriptorR(args.type, desc, j);
      args := args.next;
      INC(i)
    END ;
    j := Strings.WriteChar(")", desc, j);
    j := DescriptorR(type.base, desc, j);
    IF j = -1 THEN OJS.Mark("Maximum descriptor length reached") END
  END DescriptorProc;

  PROCEDURE DescriptorAt(type: OJB.Type; VAR desc: ARRAY OF CHAR; i: INTEGER);
    VAR x: INTEGER;
  BEGIN
    x := DescriptorR(type, desc, i);
    IF x = -1 THEN OJS.Mark("Maximum descriptor length reached") END
  END DescriptorAt;

  PROCEDURE Descriptor(type: OJB.Type; VAR desc: ARRAY OF CHAR);
  BEGIN DescriptorAt(type, desc, 0)
  END Descriptor;

  PROCEDURE pushTypedLocal(VAR x: Item);
  BEGIN
    IF (x.type = OJB.intType) OR (x.type = OJB.boolType) OR
       (x.type = OJB.charType) OR (x.type = OJB.setType) OR
       (x.type = OJB.byteType ) THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, x.a)
    ELSIF x.type = OJB.realType THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.FLOAD, x.a)
    ELSE
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a)
    END
  END pushTypedLocal;

  PROCEDURE pushTypedArray(type: OJB.Type);
  BEGIN
    IF (type = OJB.intType) OR (type = OJB.setType) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.IALOAD)
    ELSIF type = OJB.realType THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FALOAD)
    ELSIF (type = OJB.boolType) OR (type = OJB.byteType) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.BALOAD)
    ELSIF type = OJB.charType THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.CALOAD)
    ELSE
      ClassFormat.putInsn(topCtx.m, Opcodes.AALOAD)
    END
  END pushTypedArray;

  PROCEDURE storeTypedLocal(VAR x: Item);
  BEGIN
    IF (x.type = OJB.intType) OR (x.type = OJB.boolType) OR
       (x.type = OJB.charType) OR (x.type = OJB.setType) OR
       (x.type = OJB.byteType) THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ISTORE, x.a)
    ELSIF x.type = OJB.realType THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.FSTORE, x.a)
    ELSE
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ASTORE, x.a)
    END
  END storeTypedLocal;

  PROCEDURE storeTypedArray(type: OJB.Type);
  BEGIN
    IF (type = OJB.intType) OR (type = OJB.setType) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.IASTORE)
    ELSIF (type = OJB.boolType) OR (type = OJB.byteType) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.BASTORE)
    ELSIF type = OJB.charType THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.CASTORE)
    ELSIF type = OJB.realType THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FASTORE)
    ELSE
      ClassFormat.putInsn(topCtx.m, Opcodes.AASTORE)
    END
  END storeTypedArray;

  PROCEDURE pushConst*(a: INTEGER);
  BEGIN
    IF (a = -1) OR (a >= 0) & (a <= 5) THEN
      CASE a OF
         -1: ClassFormat.putInsn(topCtx.m, Opcodes.ICONSTM1)
        | 0: ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0)
        | 1: ClassFormat.putInsn(topCtx.m, Opcodes.ICONST1)
        | 2: ClassFormat.putInsn(topCtx.m, Opcodes.ICONST2)
        | 3: ClassFormat.putInsn(topCtx.m, Opcodes.ICONST3)
        | 4: ClassFormat.putInsn(topCtx.m, Opcodes.ICONST4)
        | 5: ClassFormat.putInsn(topCtx.m, Opcodes.ICONST5)
      END
    ELSIF (a >= -128) & (a <= 127) THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.BIPUSH, a)
    ELSIF (a >= -32768) & (a <= 32767) THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.SIPUSH, a)
    ELSE
      ClassFormat.putLdcInsnInt(topCtx.m, ClassFormat.INT, a)
    END
  END pushConst;

  PROCEDURE pushRealConst(a: INTEGER);
    VAR num: REAL;
  BEGIN
    num := SYSTEM.VAL(REAL, a);
    IF num = 0.0 THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FCONST0)
    ELSIF num = 1.0 THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FCONST1)
    ELSIF num = 2.0 THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FCONST2)
    ELSE
      ClassFormat.putLdcInsnInt(topCtx.m, ClassFormat.FLOAT, a)
    END
  END pushRealConst;

  PROCEDURE isPrimitiveType(type: OJB.Type): BOOLEAN;
    RETURN (type = OJB.boolType) OR
           (type = OJB.charType) OR
           (type = OJB.realType) OR
           (type = OJB.setType) OR
           (type = OJB.byteType) OR
           (type = OJB.intType)
  END isPrimitiveType;

  PROCEDURE findMultiArrayDimension(type: OJB.Type;
                                   skipPrimitiveAndPtrArrays: BOOLEAN): INTEGER;
    VAR i: INTEGER; tmp: OJB.Type;
  BEGIN
    i := 1;
    tmp := type.base;
    WHILE tmp.form = OJB.Array DO
      tmp := tmp.base;
      INC(i)
    END ;
    IF skipPrimitiveAndPtrArrays &
       (isPrimitiveType(tmp) OR (tmp.form = OJB.Pointer) OR
        (tmp.form = OJB.Proc)) THEN
      i := 0
    END
    RETURN i
  END findMultiArrayDimension;

  PROCEDURE NewTmpObj(i: INTEGER; root: OJB.Object; type: OJB.Type;
                      size: INTEGER): OJB.Object;
    VAR new: OJB.Object;
  BEGIN
    NEW(new);
    Strings.Append("@tmp", new.name);
    Strings.AppendInt(i, 0, new.name);
    new.class := OJB.Var;
    new.type := type; new.val := size;
    new.next := root;
    RETURN new
  END NewTmpObj;

  PROCEDURE storeRef(x: OJB.Object);
    VAR desc, iname: ClassFormat.Descriptor;
  BEGIN
    IF x.lev = 0 THEN
      Descriptor(x.type, desc);
      IF x.class = OJB.Fld THEN
        internalName(x.recordType, iname);
        ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTFIELD, iname, x.name,
                                 desc)
      ELSE
        ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTSTATIC, topCtx.className,
                                 x.name, desc)
      END
    ELSE
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ASTORE, x.val)
    END
  END storeRef;

  PROCEDURE loadRef(x: OJB.Object);
    VAR desc, iname: ClassFormat.Descriptor;
  BEGIN
    IF x.lev = 0 THEN
      Descriptor(x.type, desc);
      IF x.class = OJB.Fld THEN
        internalName(x.recordType, iname);
        ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETFIELD, iname, x.name,
                                 desc)
      ELSE
        ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETSTATIC, topCtx.className,
                                 x.name, desc)
      END
    ELSE
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.val)
    END
  END loadRef;

  PROCEDURE getLocalVars(obj: OJB.Object; end: INTEGER): OJB.Object;
    VAR i: INTEGER;
  BEGIN
    i := 0;
    WHILE (obj # NIL) & (i < end) DO
      obj := obj.next;
      INC(i)
    END
    RETURN obj
  END getLocalVars;

  PROCEDURE createArraysTempVars(topScope: OJB.Object; VAR size: INTEGER;
                                skipPrimitiveAndPtrArrays: BOOLEAN): OJB.Object;
    VAR maxArrayDimension: INTEGER;
      tmp, vars: OJB.Object;
      dim, i: INTEGER;
  BEGIN
    maxArrayDimension := 0;
    tmp := topScope;
    vars := NIL;
    i := 0;
    WHILE tmp # NIL DO
      IF ((tmp.class = OJB.Var) OR (tmp.class = OJB.Fld)) &
         (tmp.type.form = OJB.Array) THEN
        dim := findMultiArrayDimension(tmp.type, skipPrimitiveAndPtrArrays);
        IF dim > maxArrayDimension THEN
          WHILE maxArrayDimension < dim DO
            vars := NewTmpObj(i, vars, OJB.intType, size);
            INC(maxArrayDimension);
            INC(i);
            INC(size)
          END
        END
      END ;
      tmp := tmp.next
    END
    RETURN vars
  END createArraysTempVars;

  PROCEDURE initializeLocalVar(x: OJB.Object);
  BEGIN
    IF (x.type = OJB.intType) OR (x.type = OJB.boolType) OR
       (x.type = OJB.setType) OR (x.type = OJB.byteType) OR
       (x.type = OJB.charType) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ISTORE, x.val)
    ELSIF x.type = OJB.realType THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FCONST0);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.FSTORE, x.val)
    ELSE  (* x.class = ORB.Pointer *)
      ClassFormat.putInsn(topCtx.m, Opcodes.ACONSTNULL);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ASTORE, x.val)
    END
  END initializeLocalVar;

  PROCEDURE emitPrimitiveNewArray(type: OJB.Type);
  BEGIN
    IF type = OJB.boolType THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.NEWARRAY, Opcodes.TBOOLEAN)
    ELSIF type = OJB.charType THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.NEWARRAY, Opcodes.TCHAR)
    ELSIF type = OJB.realType THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.NEWARRAY, Opcodes.TFLOAT)
    ELSIF type = OJB.byteType THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.NEWARRAY, Opcodes.TBYTE)
    ELSIF (type = OJB.intType) OR (type = OJB.setType) THEN
      ClassFormat.putIntInsn(topCtx.m, Opcodes.NEWARRAY, Opcodes.TINT)
    END
  END emitPrimitiveNewArray;

  PROCEDURE getLastArray(type: OJB.Type): OJB.Type;
    VAR last: OJB.Type;
  BEGIN
    last := NIL;
    WHILE type.form = OJB.Array DO
      last := type;
      type := type.base
    END
    RETURN last
  END getLastArray;

  PROCEDURE Fixup*(L: INTEGER);
  BEGIN
    ClassFormat.FixLink(topCtx.m, L)
  END Fixup;

  PROCEDURE pushIndexes(i: INTEGER; tmpVars: OJB.Object): OJB.Object;
    VAR k: INTEGER;
  BEGIN
    k := 0;
    WHILE k # i-1 DO
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, tmpVars.val);
      ClassFormat.putInsn(topCtx.m, Opcodes.AALOAD);
      tmpVars := tmpVars.next;
      INC(k)
    END
    RETURN tmpVars
  END pushIndexes;

  PROCEDURE pushIndexes2(i: INTEGER): INTEGER;
    VAR tmpVars, k: INTEGER;
  BEGIN
    tmpVars := topCtx.numTmpVars - i;
    k := 0;
    WHILE k < i-1 DO
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, tmpVars);
      ClassFormat.putInsn(topCtx.m, Opcodes.AALOAD);
      INC(tmpVars);
      INC(k)
    END
    RETURN tmpVars
  END pushIndexes2;

  PROCEDURE genNew(typeName: ARRAY OF CHAR);
  BEGIN
    ClassFormat.putTypeInsn(topCtx.m, Opcodes.NEW, typeName);
    ClassFormat.putInsn(topCtx.m, Opcodes.DUP);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESPECIAL, typeName,
                              "<init>", "()V", 0)
  END genNew;

  PROCEDURE initializeMultiDimArray(i: INTEGER; x: OJB.Object; type: OJB.Type;
                                    tmpVars, currTmpVar: OJB.Object);
    VAR iname: ClassFormat.Descriptor;
      cond, end: INTEGER;
  BEGIN
    IF type.form = OJB.Array THEN
      pushConst(type.len - 1);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ISTORE, currTmpVar.val);
      cond := topCtx.m.i;
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, currTmpVar.val);
      end := topCtx.m.i;
      ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFLT, 0);
      initializeMultiDimArray(i + 1, x, type.base, tmpVars, currTmpVar.next);
      ClassFormat.putIincInsn(topCtx.m, currTmpVar.val, -1);
      ClassFormat.putGotoInsn(topCtx.m, cond-topCtx.m.i, 0);
      Fixup(end)
    ELSE
      IF x.class = OJB.Fld THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0)
      END ;
      loadRef(x);
      tmpVars := pushIndexes(i, tmpVars);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, tmpVars.val);
      internalName(type, iname);
      genNew(iname);
      ClassFormat.putInsn(topCtx.m, Opcodes.AASTORE)
    END
  END initializeMultiDimArray;

  PROCEDURE initializeRecord(x: OJB.Object);
    VAR iname: ClassFormat.Descriptor;
  BEGIN
    IF x.class = OJB.Fld THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0)
    END ;
    internalName(x.type, iname);
    genNew(iname);
    storeRef(x)
  END initializeRecord;

  PROCEDURE initializeArray(x, tmpVars: OJB.Object);
    VAR
      desc, iname: ClassFormat.Descriptor;
      arrDim, i: INTEGER;
      type, tbase, base, t: OJB.Type;
  BEGIN
    type := x.type;
    arrDim := 1;
    tbase := type.base;
    IF x.class = OJB.Fld THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0)
    END ;
    i := Strings.WriteChar("[", desc, 0);
    pushConst(type.len);
    WHILE (tbase # NIL) & (tbase.form = OJB.Array) DO
      pushConst(tbase.len);
      INC(arrDim);
      i := Strings.WriteChar("[", desc, i);
      tbase := tbase.base
    END ;
    IF (arrDim = 1) & (tbase # NIL) THEN
      IF isPrimitiveType(tbase) THEN
        emitPrimitiveNewArray(tbase);
        storeRef(x)
      ELSE
        internalName(tbase, iname);
        ClassFormat.putTypeInsn(topCtx.m, Opcodes.ANEWARRAY, iname);
        storeRef(x);
        IF (tbase.form # OJB.Pointer) & (tbase.form # OJB.Proc) THEN
          initializeMultiDimArray(0, x, type, tmpVars, tmpVars)
        END
      END
    ELSE
      DescriptorAt(tbase, desc, i);
      ClassFormat.putMultiANewArrayInsn(topCtx.m, desc, arrDim);
      storeRef(x);
      t := getLastArray(x.type);
      base := t.base;
      IF ~isPrimitiveType(base) & (base.form # OJB.Pointer) &
         (base.form # OJB.Proc) THEN
        initializeMultiDimArray(0, x, type, tmpVars, tmpVars)
      END
    END
  END initializeArray;

  PROCEDURE initializeScope(x: OJB.Object; offset: INTEGER);
    VAR type, recordType: OJB.Type;
      num, numTmpVars: INTEGER;
      tempVars: OJB.Object;
  BEGIN
    num := offset;
    (* skipPrimitiveAndPtrArrays = TRUE, as we don't want to create local
       variables to index multi-dimensional primitive/reference arrays as by
       default they are already initialized by MULTIANEWARRAY *)
    tempVars := createArraysTempVars(x, num, TRUE);
    numTmpVars := num;
    IF x # NIL THEN
      recordType := x.recordType;
      WHILE (x # NIL) & (recordType = x.recordType) DO
        IF (x.class = OJB.Var) OR (x.class = OJB.Fld) THEN
          type := x.type;
          IF type.form = OJB.Record THEN
            initializeRecord(x)
          ELSIF type.form = OJB.Array THEN
            initializeArray(x, tempVars)
          ELSIF x.lev > 0 THEN
            initializeLocalVar(x)
          END
        END ;
        x := x.next
      END
    END ;
    topCtx.numTmpVars := numTmpVars
  END initializeScope;

  PROCEDURE Constructor(obj: OJB.Object);
    VAR baseName: ClassFormat.Descriptor;
      tmp: OJB.Object;
  BEGIN
    topCtx.m := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxPUBLIC, "<init>",
                                  "()V");
    clearCtx(topCtx);
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
    Strings.Append("java/lang/Object", baseName);
    tmp := NIL;
    IF obj # NIL THEN
      tmp := obj.type.dsc;
      IF obj.type.base # NIL THEN
        internalName(obj.type.base, baseName)
      END
    END ;
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESPECIAL, baseName,
                              "<init>", "()V", 0);
    initializeScope(tmp, 1);
    ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx);
    ClassFormat.setMaxVars(topCtx.m, topCtx.numTmpVars);
    closeContext
  END Constructor;

  PROCEDURE emitTypedReturn(type: OJB.Type);
  BEGIN
    IF (type = OJB.intType) OR (type = OJB.boolType) OR
       (type = OJB.charType) OR (type = OJB.setType) OR
       (type = OJB.byteType) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.IRETURN)
    ELSIF type = OJB.realType THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FRETURN)
    ELSE
      ClassFormat.putInsn(topCtx.m, Opcodes.ARETURN)
    END
  END emitTypedReturn;

  PROCEDURE InvokeMethod(signature: ARRAY OF CHAR; procType: OJB.Type;
                         qualifierModName, procName: ARRAY OF CHAR;
                         impl: BOOLEAN);
    VAR access, i: INTEGER; par: OJB.Object;
      tmp: Item;
  BEGIN
    access := Opcodes.ACCxPUBLIC;
    IF ~impl THEN INC(access, Opcodes.ACCxABSTRACT) END ;
    topCtx.m := ClassFormat.NewMI(topCtx.c, access, "invoke", signature);
    clearCtx(topCtx);
    IF impl THEN
      par := procType.dsc;
      FOR i := 1 TO procType.nofpar DO
        IF par.class = OJB.Par THEN
          (* it's a VAR parameter, force pushTypedLocal() to generate an
             ALOAD *)
          tmp.type := OJB.nilType
        ELSE
          tmp.type := par.type
        END ;
        tmp.a := i;
        pushTypedLocal(tmp);
        par := par.next
      END ;
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                qualifierModName, procName, signature,
                                procType.nofpar);
      IF procType.base.form # OJB.NoTyp THEN
        emitTypedReturn(procType.base)
      ELSE ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx)
      END ;
      ClassFormat.setMaxVars(topCtx.m, procType.nofpar+1) (* +1 is this *)
    END ;
    closeContext
  END InvokeMethod;

  PROCEDURE genClassFilePath(name: ARRAY OF CHAR; VAR path: ARRAY OF CHAR);
    VAR i: INTEGER;
  BEGIN
    i := OJB.getOutputFolder(path);
    i := Strings.Write(Files.SEPARATOR, path, i);
    i := Strings.Write(name, path, i);
    i := Strings.Write(".class", path, i);
    IF i = -1 THEN OJS.Mark("Maximum file path length reached") END
  END genClassFilePath;

  PROCEDURE MakeProcType*(type: OJB.Type);
    VAR typeName: ClassFormat.Descriptor;
      path: ARRAY OJS.maxPath OF CHAR;
      newCtx: ClassContext;
  BEGIN
    internalName(type, typeName);
    genClassFilePath(typeName, path);
    IF ~Files.Exists(path) THEN
      NEW(newCtx);
      Strings.Copy(typeName, newCtx.className);
      newCtx.next := topCtx;
      topCtx := newCtx;
      newCtx.c := ClassFormat.NewCF(Opcodes.ACCxPUBLIC + Opcodes.ACCxABSTRACT,
                                    topCtx.className, "java/lang/Object");
      Constructor(NIL);
      InvokeMethod(type.signature, NIL, "", "", FALSE);
      genClassFilePath(topCtx.className, path);
      ClassFormat.toFile(topCtx.c, path);
      topCtx := topCtx.next
    END
  END MakeProcType;

  PROCEDURE joinNames(VAR s0, s1: ARRAY OF CHAR; VAR out: ARRAY OF CHAR);
    VAR i: INTEGER;
  BEGIN
    i := Strings.Write(s0, out, 0);
    i := Strings.WriteChar("_", out, i);
    i := Strings.Write(s1, out, i);
    IF i = -1 THEN OJS.Mark("Maximum descriptor length reached") END
  END joinNames;

  PROCEDURE makeRefDesc(VAR s, desc: ARRAY OF CHAR);
    VAR i: INTEGER;
  BEGIN
    i := Strings.WriteChar("L", desc, 0);
    i := Strings.Write(s, desc, i);
    i := Strings.WriteChar(";", desc, i);
    IF i = -1 THEN OJS.Mark("Maximum descriptor length reached") END
  END makeRefDesc;

  PROCEDURE ConstructorProcInstance(signature: OJB.Type);
    VAR iname: ClassFormat.Descriptor;
  BEGIN
    topCtx.m := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxPRIVATE, "<init>",
                                  "()V");
    clearCtx(topCtx);
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
    internalName(signature, iname);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESPECIAL, iname, "<init>",
                              "()V", 0);
    ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx);
    ClassFormat.setMaxVars(topCtx.m, 1);
    closeContext
  END ConstructorProcInstance;

  PROCEDURE InitProcInstance(VAR typeName: ARRAY OF CHAR);
    VAR desc: ClassFormat.Descriptor;
  BEGIN
    topCtx.m := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxSTATIC, "<clinit>",
                                  "()V");
    clearCtx(topCtx);
    genNew(typeName);
    makeRefDesc(typeName, desc);
    ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTSTATIC, typeName, "INSTANCE",
                             desc);
    ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx);
    ClassFormat.setMaxVars(topCtx.m, topCtx.numTmpVars);
    closeContext
  END InitProcInstance;

  PROCEDURE MakeProcInstance(VAR x: Item);
    VAR
      path: ARRAY OJS.maxPath OF CHAR;
      iname, instanceName:ClassFormat.Descriptor;
      procType: OJB.Type;
      newCtx: ClassContext;
      access: INTEGER;
  BEGIN
    procType := x.type;
    MakeProcType(procType);
    joinNames(x.modName, x.name, instanceName);
    genClassFilePath(instanceName, path);
    IF ~Files.Exists(path) THEN
      NEW(newCtx);
      access := Opcodes.ACCxPUBLIC + Opcodes.ACCxSUPER + Opcodes.ACCxFINAL;
      joinNames(x.modName, x.name, newCtx.className);
      newCtx.next := topCtx;
      topCtx := newCtx;
      internalName(procType, iname);
      newCtx.c := ClassFormat.NewCF(access, topCtx.className, iname);
      makeRefDesc(topCtx.className, iname);
      ClassFormat.addField(topCtx.c, Opcodes.ACCxPUBLIC + Opcodes.ACCxSTATIC,
                           "INSTANCE", iname);
      ConstructorProcInstance(procType);
      InvokeMethod(procType.signature, procType, x.modName, x.name, TRUE);
      InitProcInstance(topCtx.className);
      genClassFilePath(topCtx.className, path);
      ClassFormat.toFile(topCtx.c, path);
      topCtx := topCtx.next
    END
  END MakeProcInstance;

  PROCEDURE load(VAR x: Item);
    VAR
      L0, L1: INTEGER;
      xt: OJB.Type;
      desc, procType: ClassFormat.Descriptor;
      s: ARRAY OJS.stringBufSize OF CHAR;
  BEGIN
    IF x.mode # Stack THEN
      IF x.oldType # NIL THEN
        xt := x.oldType
      ELSE
        xt := x.type
      END ;
      IF (x.mode = OJB.Var) OR (x.mode = OJB.ParStruct) THEN
        IF x.r > 0 THEN
          (*local*)
          pushTypedLocal(x)
        ELSE
          Descriptor(xt, desc);
          ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETSTATIC, x.modName,
                                   x.name, desc)
        END
      ELSIF x.mode = OJB.Par THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
        pushConst(0);
        pushTypedArray(xt)
      ELSIF  x.mode = OJB.Const THEN
        IF xt = OJB.realType THEN
          pushRealConst(x.a)
        ELSIF xt = OJB.nilType THEN
          ClassFormat.putInsn(topCtx.m, Opcodes.ACONSTNULL);
        ELSIF xt = OJB.strType THEN
          OJS.ExtractStr(x.a, x.b, s);
          ClassFormat.putLdcInsnStr(topCtx.m, s, TRUE)
        ELSIF xt.form = OJB.Proc THEN
          MakeProcInstance(x);
          joinNames(x.modName, x.name, procType);
          makeRefDesc(procType, desc);
          ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETSTATIC, procType,
                                   "INSTANCE", desc)
        ELSE
          pushConst(x.a)
        END
      ELSIF x.mode = RegI THEN
        pushTypedArray(xt)
      ELSIF x.mode = Field THEN
        Descriptor(xt, desc);
        ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETFIELD, x.recordName,
                                 x.name, desc)
      ELSIF x.mode = Cond THEN
        L0 := topCtx.m.i;
        ClassFormat.putJumpInsn(topCtx.m, negated(x.r), 0);
        ClassFormat.FixLink(topCtx.m, x.b);
        ClassFormat.putInsn(topCtx.m, Opcodes.ICONST1);
        L1 := topCtx.m.i;
        ClassFormat.putGotoInsn(topCtx.m, 0, -1);
        ClassFormat.FixLink(topCtx.m, x.a);
        Fixup(L0);
        ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0);
        Fixup(L1)
      END ;
      x.mode := Stack;
      IF x.oldType # NIL THEN
        x.oldType := NIL;
        internalName(x.type, desc);
        ClassFormat.putTypeInsn(topCtx.m, Opcodes.CHECKCAST, desc)
      END ;
      IF x.type = OJB.byteType THEN
        pushConst(255);
        ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
      END
    END
  END load;

  PROCEDURE TypeTest*(VAR x: Item; T: OJB.Type; isguard: BOOLEAN);
    VAR iname: ClassFormat.Descriptor;
  BEGIN
    IF isguard THEN
      IF x.oldType = NIL THEN x.oldType := x.type END
    ELSE
      load(x);
      internalName(T, iname);
      ClassFormat.putTypeInsn(topCtx.m, Opcodes.INSTANCEOF, iname)
    END
  END TypeTest;

  PROCEDURE loadCond(VAR x: Item);
  BEGIN
   IF x.type.form = OJB.Bool THEN
     load(x); x.r := Opcodes.IFNE;
     x.mode := Cond; x.a := 0; x.b := 0;
   ELSE OJS.Mark("not Boolean") END
  END loadCond;

  PROCEDURE getPC*(): INTEGER;
    RETURN topCtx.m.i
  END getPC;

  PROCEDURE CFJump*(VAR x: Item);  (*conditional forward jump*)
    VAR L0: INTEGER;
  BEGIN
    IF x.mode # Cond THEN loadCond(x) END ;
    L0 := topCtx.m.i;
    ClassFormat.putJumpInsn(topCtx.m, negated(x.r), x.a);
    ClassFormat.FixLink(topCtx.m, x.b); x.a := L0
  END CFJump;

  PROCEDURE FJump*(L: INTEGER): INTEGER;   (*unconditional forward jump*)
    VAR L0: INTEGER;
  BEGIN
    L0 := topCtx.m.i;
    ClassFormat.putGotoInsn(topCtx.m, L, 0);
    RETURN L0
  END FJump;

  PROCEDURE CBJump*(VAR x: Item; L: INTEGER);   (*conditional backward jump*)
  BEGIN
    IF x.mode # Cond THEN loadCond(x) END ;
    ClassFormat.putJumpInsn(topCtx.m, negated(x.r), L-topCtx.m.i);
    ClassFormat.FixLink(topCtx.m, x.b);
    ClassFormat.FixLinkWith(topCtx.m, x.a, L)
  END CBJump;

  PROCEDURE BJump*(L: INTEGER);   (*unconditional backward jump*)
  BEGIN
    ClassFormat.putGotoInsn(topCtx.m, L-topCtx.m.i, 0)
  END BJump;

  PROCEDURE genSignature*(type: OJB.Type);
  BEGIN DescriptorProc(type, type.nofpar, type.signature)
  END genSignature;

  PROCEDURE MakeConstItem*(VAR x: Item; typ: OJB.Type; val: INTEGER);
  BEGIN
    x.mode := OJB.Const; x.type := typ; x.a := val; x.rdo := TRUE
  END MakeConstItem;

  PROCEDURE MakeRealItem*(VAR x: Item; val: REAL);
  BEGIN
    x.mode := OJB.Const; x.type := OJB.realType;
    x.a := SYSTEM.VAL(INTEGER, val); x.rdo := TRUE
  END MakeRealItem;

  PROCEDURE MakeStringItem*(VAR x: Item);
  BEGIN
    x.mode := OJB.Const; x.type := OJB.strType;
    x.a := OJS.strpos; x.b := OJS.slen; x.rdo := TRUE;
  END MakeStringItem;

  PROCEDURE MakeItem*(VAR x: Item; y: OJB.Object);
  BEGIN
    x.mode := y.class; x.type := y.type; x.a := y.val;  x.rdo := y.rdo;
    Strings.Copy(y.name, x.name);
    IF y.nestedId > 0 THEN
      Strings.AppendInt(y.nestedId, 0, x.name)
    END ;
    x.oldType := y.caseOrgType;
    IF (y.class = OJB.Const) & (y.type.form = OJB.String) THEN
      x.b := y.len
    END ;
    x.r := y.lev
  END MakeItem;

  PROCEDURE Pop(x: Item);
  BEGIN
    IF x.mode IN {Stack, Field} THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.POP)
    ELSIF x.mode = RegI THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.POP2)
    END
  END Pop;

  PROCEDURE ConstTypeTest*(VAR x: Item);
  BEGIN
    IF x.mode IN {OJB.Var, OJB.Par, OJB.ParStruct, Field, Stack, RegI} THEN
      Pop(x);
      MakeConstItem(x, OJB.boolType, 1)
    ELSE OJS.Mark("Unexpected internal mode")
    END
  END ConstTypeTest;

  PROCEDURE FieldAccess*(VAR x: Item; y: OJB.Object);   (* x := x.y *)
  BEGIN
    load(x);
    x.mode := Field;
    internalName(x.type, x.recordName);
    Strings.Copy(y.name, x.name)
  END FieldAccess;

  PROCEDURE Index0*(VAR x: Item);
  BEGIN
    load(x)
  END Index0;

  PROCEDURE Index*(VAR x, y: Item);   (* x := x[y] *)
  BEGIN
    IF (y.mode = OJB.Const) & (x.type.len >= 0) THEN
      IF (y.a < 0) OR (y.a >= x.type.len) THEN OJS.Mark("bad index") END
    END ;
    load(y);
    x.mode := RegI
  END Index;

  PROCEDURE Not*(VAR x: Item);   (* x := ~x *)
    VAR t: INTEGER;
  BEGIN
    IF x.mode # Cond THEN loadCond(x) END ;
    x.r := negated(x.r); t := x.a; x.a := x.b; x.b := t
  END Not;

  PROCEDURE And1*(VAR x: Item);   (* x := x & *)
    VAR L0: INTEGER;
  BEGIN
    IF x.mode # Cond THEN loadCond(x) END ;
    L0 := topCtx.m.i;
    ClassFormat.putJumpInsn(topCtx.m, negated(x.r), x.a);
    x.a := L0; ClassFormat.FixLink(topCtx.m, x.b);
    x.b := 0
  END And1;

  PROCEDURE And2*(VAR x, y: Item);
  BEGIN
    IF y.mode # Cond THEN loadCond(y) END ;
    x.a := ClassFormat.merged(topCtx.m, y.a, x.a); x.b := y.b; x.r := y.r
  END And2;

  PROCEDURE Or1*(VAR x: Item);   (* x := x OR *)
    VAR L0: INTEGER;
  BEGIN
    IF x.mode # Cond THEN loadCond(x) END ;
    L0 := topCtx.m.i;
    ClassFormat.putJumpInsn(topCtx.m, x.r, x.b);
    x.b := L0; ClassFormat.FixLink(topCtx.m, x.a);
    x.a := 0
  END Or1;

  PROCEDURE Or2*(VAR x, y: Item);
  BEGIN
    IF y.mode # Cond THEN loadCond(y) END ;
    x.a := y.a; x.b := ClassFormat.merged(topCtx.m, y.b, x.b); x.r := y.r
  END Or2;

  PROCEDURE loadAndMaybeSwap(VAR x, y: Item);
  BEGIN
    IF (x.mode = OJB.Const) & (y.mode # OJB.Const) THEN
      (*x loading has been delayed, so fully load y and restore the order
        (SWAP)*)
      load(y);
      load(x);
      ClassFormat.putInsn(topCtx.m, Opcodes.SWAP)
    ELSE load(x); load(y)
    END
  END loadAndMaybeSwap;

  PROCEDURE Neg*(VAR x: Item);   (* x := -x *)
  BEGIN
    IF x.type.form = OJB.Int THEN
      IF x.mode = OJB.Const THEN x.a := -x.a
      ELSE load(x); ClassFormat.putInsn(topCtx.m, Opcodes.INEG)
      END
    ELSIF x.type.form = OJB.Real THEN
      IF x.mode = OJB.Const THEN
        x.a := SYSTEM.VAL(INTEGER, -SYSTEM.VAL(REAL, x.a));
      ELSE load(x); ClassFormat.putInsn(topCtx.m, Opcodes.FNEG)
      END
    ELSE (*form := Set*)
      (* The sign of a two’s complement number is reversed in a process called
         taking the two’s complement:
          Ex. 8 := 00001000 -> -8 := oneComplement(8) + 1
                := 11110111 + 1 := 11111000
        So if I only need the oneComplement I subtract 1 to the two's
        complement:
          Ex.  -8 := oneComplement(8) + 1 ->  -8 - 1 := oneComplement(8)
      *)
      IF x.mode = OJB.Const THEN x.a := -x.a-1
      ELSE  (* there is no Not instruction in JVM *)
        load(x);
        pushConst(-1);
        ClassFormat.putInsn(topCtx.m, Opcodes.IXOR)
      END
    END
  END Neg;

  PROCEDURE AddOp*(op: INTEGER; VAR x, y: Item);   (* x := x +- y *)
  BEGIN
    IF op = OJS.plus THEN
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN x.a := x.a + y.a
      ELSIF x.mode = OJB.Const THEN
        load(y);
        IF x.a # 0 THEN
          load(x);
          ClassFormat.putInsn(topCtx.m, Opcodes.IADD)
        ELSE x.mode := Stack
        END
      ELSIF y.mode = OJB.Const THEN
        load(x);
        IF y.a # 0 THEN
          load(y); ClassFormat.putInsn(topCtx.m, Opcodes.IADD)
        END ;
      ELSE load(x); load(y); ClassFormat.putInsn(topCtx.m, Opcodes.IADD)
      END
    ELSE (*op = ORS.minus*)
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN x.a := x.a - y.a
      ELSIF y.mode = OJB.Const THEN
        load(x);
        IF y.a # 0 THEN load(y); ClassFormat.putInsn(topCtx.m, Opcodes.ISUB) END
      ELSE loadAndMaybeSwap(x, y); ClassFormat.putInsn(topCtx.m, Opcodes.ISUB)
      END
    END
  END AddOp;

  (*
    The log2(x) is the number of bits needed to represent the value of a
    positive x. As The result is a real number
    we actually compute the ceiling(log2(x)).
    Ex. log2(5) = 2.32 -> ceiling(2.32) = 3 in fact 5 is 101 in binary
    This means that to implement log2(x) we count how many bits we have until
    we reach the Most Significant Bit.
    So we basically shift x by one in a loop until x = 1 i.e. the
    MSB ( while(x # 1) { x = x >> 1; res++;} ceiling(res);)

    Here however, we are interested in the log2(x) where x is a power of 2.
    ex. 2^3 = 1000 = 1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0
    All the power of 2 have the MSB set to 1 and the rest of the bits set to 0.
    So we can stop the loop when we find the first 1 and check at the call site
    (log2(x) = 1) to know if x is indeed a power of 2.
    Also remember that log2(2^k) = k = ceiling(k) as there is no fractional
    part.
  *)

  PROCEDURE log2(m: INTEGER; VAR e: INTEGER): INTEGER;
  BEGIN e := 0;
    WHILE ~ODD(m) DO m := m DIV 2; INC(e) END ;
    RETURN m
  END log2;

  PROCEDURE MulOp*(VAR x, y: Item);   (* x := x * y *)
    VAR e: INTEGER;
  BEGIN
    IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN  x.a := x.a * y.a
    ELSIF (y.mode = OJB.Const) & (y.a >= 2) & (log2(y.a, e) = 1) THEN
      load(x); pushConst(e);
      ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
    ELSIF x.mode = OJB.Const THEN
      load(y);
      IF (x.a >= 2) & (log2(x.a, e) = 1) THEN
        pushConst(e);
        ClassFormat.putInsn(topCtx.m, Opcodes.ISHL);
      ELSIF x.a # 0 THEN
        load(x);
        ClassFormat.putInsn(topCtx.m, Opcodes.IMUL)
      END ;
      x.mode := Stack
    ELSE load(x); load(y); ClassFormat.putInsn(topCtx.m, Opcodes.IMUL)
    END
  END MulOp;

  (*
    Delayed code generation is used to implement constant folding
    Given VAR i,j : INTEGER; a : ARRAY 10 OF INTEGER;
      i := 2 + a[1+3];
    will be compiled as i := a[4] + 2:
      GETSTATIC A.a : [I
      ICONST_4
      IALOAD
      ICONST_2
      IADD
      PUTSTATIC A.i : I
    We delay the code generation for the first operands of both additions.
    In the first addition 2 will be emitted after the evaluation of a[1+3] as
    at that point is clear that no constant folding is possible between 2 and a.
    In the second addition we delay the emission of 1 as it could be folded as
    indeed happens as the second operand is the constant 3.
    The statement
      i := 2 - a[j+3];
    Will be compiled as i = 2 - a[j + 3];
      GETSTATIC A.a : [I
      GETSTATIC A.j : I
      ICONST_3
      IADD
      IALOAD
      ICONST_2
      SWAP
      ISUB
      PUTSTATIC A.i : I
    Notice the presence of an extra SWAP. Because 2 is emitted after evaluating
    a[j+3] and because - is not commutative we need to swap the argument of the
    ISUB instruction.
  *)

  PROCEDURE loadOp*(VAR x: Item);
  BEGIN
    IF x.mode # OJB.Const THEN
      load(x)
    END
  END loadOp;

  PROCEDURE SetLineNumber*(line: INTEGER);
  BEGIN
    ClassFormat.addLineNumber(topCtx.m, line)
  END SetLineNumber;

  PROCEDURE Trap(msg: ARRAY OF CHAR);
  BEGIN
    ClassFormat.putTypeInsn(topCtx.m, Opcodes.NEW,
                            "java/lang/RuntimeException");
    ClassFormat.putInsn(topCtx.m, Opcodes.DUP);
    ClassFormat.putLdcInsnStr(topCtx.m, msg, FALSE);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESPECIAL,
                              "java/lang/RuntimeException", "<init>",
                              "(Ljava/lang/String;)V", 1);
    ClassFormat.putInsn(topCtx.m, Opcodes.ATHROW)
  END Trap;

  PROCEDURE TrapWithCond(cond: INTEGER; msg: ARRAY OF CHAR);
    VAR L: INTEGER;
  BEGIN
    ClassFormat.putInsn(topCtx.m, Opcodes.DUP);
    L := topCtx.m.i;
    ClassFormat.putJumpInsn(topCtx.m, negated(cond), 0);
    Trap(msg);
    Fixup(L)
  END TrapWithCond;

  PROCEDURE DivOp*(op: INTEGER; VAR x, y: Item);   (* x := x op y *)
    VAR e: INTEGER; skip: BOOLEAN;
  BEGIN
    skip := FALSE;
    IF op = OJS.div THEN
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
        (* Euclidean division, use y.a > 0 for Floored division *)
        IF y.a # 0 THEN
          x.a := x.a DIV y.a
        ELSE
          OJS.Mark("bad divisor")
        END
      ELSIF (y.mode = OJB.Const) & (y.a >= 2) & (log2(y.a, e) = 1) THEN
        load(x); pushConst(e);
        ClassFormat.putInsn(topCtx.m, Opcodes.ISHR)
      ELSE
        (* Euclidean division, use y.a <= 0 for Floored division *)
        IF y.mode = OJB.Const THEN
          IF y.a = 0 THEN OJS.Mark("bad divisor")
          ELSIF y.a > 0 THEN skip := TRUE END
        END ;
        loadAndMaybeSwap(x, y);
        (* Euclidean division, use Opcodes.IFLE for Floored division *)
        IF check & ~skip THEN TrapWithCond(Opcodes.IFEQ, "bad divisor") END ;
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                  "OberonRuntime", "DIV", "(II)I", 2)
      END
    ELSE (*op := ORS.mod*)
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
        (* Euclidean division, use y.a > 0 for Floored division *)
        IF y.a # 0 THEN
          x.a := x.a MOD y.a
        ELSE
          OJS.Mark("bad modulus")
        END
      ELSIF (y.mode = OJB.Const) & (y.a >= 2) & (log2(y.a, e) = 1) THEN
        load(x); pushConst(y.a-1);
        ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
      ELSE
        (* Euclidean division, use y.a <= 0 for Floored division *)
        IF (y.mode = OJB.Const) & (y.a = 0) THEN OJS.Mark("bad modulus")
        ELSE skip := TRUE END ;
        loadAndMaybeSwap(x, y);
        (* Euclidean division, use Opcodes.IFLE for Floored division *)
        IF check & ~skip THEN TrapWithCond(Opcodes.IFEQ, "bad modulus") END ;
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                  "OberonRuntime", "MOD", "(II)I", 2)
      END
    END
  END DivOp;

  (* Code generation for REAL operators *)

  PROCEDURE RealOp*(op: INTEGER; VAR x, y: Item);   (* x := x op y *)
  BEGIN
    IF op = OJS.plus THEN
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
        x.a := SYSTEM.VAL(INTEGER, SYSTEM.VAL(REAL, x.a) +SYSTEM.VAL(REAL, y.a))
      ELSE
        loadAndMaybeSwap(x, y); ClassFormat.putInsn(topCtx.m, Opcodes.FADD)
      END ;
    ELSIF op = OJS.minus THEN
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
        x.a := SYSTEM.VAL(INTEGER, SYSTEM.VAL(REAL, x.a) -SYSTEM.VAL(REAL, y.a))
      ELSE
        loadAndMaybeSwap(x, y); ClassFormat.putInsn(topCtx.m, Opcodes.FSUB)
      END ;
    ELSIF op = OJS.times THEN
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
        x.a := SYSTEM.VAL(INTEGER, SYSTEM.VAL(REAL, x.a) *SYSTEM.VAL(REAL, y.a))
      ELSE
        loadAndMaybeSwap(x, y); ClassFormat.putInsn(topCtx.m, Opcodes.FMUL)
      END ;
    ELSE (* op = ORS.rdiv *)
      IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
        x.a := SYSTEM.VAL(INTEGER, SYSTEM.VAL(REAL, x.a) /SYSTEM.VAL(REAL, y.a))
      ELSE
        loadAndMaybeSwap(x, y); ClassFormat.putInsn(topCtx.m, Opcodes.FDIV)
      END ;
    END
  END RealOp;

  (* Code generation for set operators *)

  PROCEDURE Singleton*(VAR x: Item);  (* x := {x} *)
  BEGIN
    IF x.mode = OJB.Const THEN x.a := LSL(1, x.a)
    ELSIF x.mode # OJB.Var THEN
      load(x);
      pushConst(1);
      ClassFormat.putInsn(topCtx.m, Opcodes.SWAP);
      ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
    ELSE pushConst(1); load(x); ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
    END
  END Singleton;

  (*
     Example s := {3..5}

      -1 = 11111111111111111111111111111111
      -2 = 11111111111111111111111111111110
      LSL(-1, 3) = 11111111111111111111111111111000
      LSL(-2, 5) = 11111111111111111111111111000000
      LSL(-1, 3) - LSL(-2, 5)
        = 11111111111111111111111111111000 - 11111111111111111111111111000000
        = 11111111111111111111111111111000 + 00000000000000000000000000111111
          + 1 (two's complement)
        = 11111111111111111111111111111001 + 00000000000000000000000000111111
        = 00000000000000000000000000111000
        = 56
      LSL(2, 5) - LSL(1, 3)
        = 00000000000000000000000001000000 - 00000000000000000000000000001000
        = 00000000000000000000000001000000 + 11111111111111111111111111110111
          + 1 (two's complement)
        = 00000000000000000000000001000001 + 11111111111111111111111111110111
        = 00000000000000000000000000111000
        = 56


        = LSL(-1, 3) &  ~LSL(-2, 5)
        = 11111111111111111111111111111000 & 00000000000000000000000000111111

        = (LSL(-2, 5) xor -1) & LSL(-1, 3)    // where LSL(-1, 3) is computed
                                              // at compile time
        = ~LSL(-2, 5) & LSL(-1, 3)  // there is no Not institution in RISC


   *)

  PROCEDURE Set0*(VAR x: Item);   (* x := {x .. y} *)
  BEGIN
    (* delay generation if x is constant, handle it in Set1 *)
    IF x.mode # OJB.Const THEN
      IF x.mode # OJB.Var THEN
        load(x);
        pushConst(-1);
        ClassFormat.putInsn(topCtx.m, Opcodes.SWAP);
        ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
      ELSE pushConst(-1); load(x); ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
      END
    END
  END Set0;

  PROCEDURE Set1*(VAR x, y: Item);   (* x := {x .. y} *)
  BEGIN
    IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
      IF x.a <= y.a THEN  x.a := LSL(-1, x.a) - LSL(-2, y.a) ELSE x.a := 0 END
    ELSE
      IF y.mode = OJB.Const THEN
        pushConst(LSL(-2, y.a)); y.mode := Stack
      ELSIF y.mode # OJB.Var THEN
        load(y);
        pushConst(-2);
        ClassFormat.putInsn(topCtx.m, Opcodes.SWAP);
        ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
      ELSE pushConst(-2); load(y); ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
      END ;
      IF x.mode = OJB.Const THEN
        pushConst(LSL(-1, x.a)); x.mode := Stack;
        ClassFormat.putInsn(topCtx.m, Opcodes.SWAP)
      END ;
      ClassFormat.putInsn(topCtx.m, Opcodes.ISUB);
    END
  END Set1;

  (*
     Example

     s := 4 in {3..5}

     {3..5}  = 56 =  00000000000000000000000000111000
     4 = 100
     We add 1 as our set is 0 based
     4 + 1 = 5 = 101

     ROR(56, 5) = 11000000000000000000000000000001
                -> as the most significant bit is set to 1 this number is
                   negative so it means that 4 is indeed in {3..5}

     Alternately as in the JVM ROR will be implemented with too many
     instructions (i.e. (x >>> n) | (x << -n) )

     LSL(1, 4) =          00000000000000000000000000010000
     LSL(1, 4) & {3..5} = 00000000000000000000000000010000 &
                          00000000000000000000000000111000
                        = 00000000000000000000000000010000
                        -> the result is not 0 so it means that 4 is
                           indeed in {3..5}

     x IN y
     If x is not in the implementation defined SET range {0..MaxSetElement-1}
     the IN operator is undefined
  *)

  PROCEDURE In0*(VAR x: Item);   (* x := x IN y *)
  BEGIN
    IF x.mode = OJB.Const THEN
      x.a := LSL(1, x.a);
      load(x)
    ELSE
      load(x);
      pushConst(1);
      ClassFormat.putInsn(topCtx.m, Opcodes.SWAP);
      ClassFormat.putInsn(topCtx.m, Opcodes.ISHL)
    END ;
    SetCC(x, Opcodes.IFNE)
  END In0;

  PROCEDURE In1*(VAR x: Item);   (* x := x IN y *)
  BEGIN
    load(x);
    ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
  END In1;

  (* See "SET: A neglected data type, and its compilation for the ARM" *)
  PROCEDURE SetOp*(op: INTEGER; VAR x, y: Item);   (* x := x op y *)
    VAR xset, yset: SET;   (*x.type.form = Set*)
  BEGIN
    IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
      xset := SYSTEM.VAL(SET, x.a); yset := SYSTEM.VAL(SET, y.a);
      IF op = OJS.plus THEN xset := xset + yset
      ELSIF op = OJS.minus THEN xset := xset - yset
      ELSIF op = OJS.times THEN xset := xset * yset
      ELSIF op = OJS.rdiv THEN xset := xset / yset
      END ;
      x.a := SYSTEM.VAL(INTEGER, xset)
    ELSE
      loadAndMaybeSwap(x, y);
      IF op = OJS.plus THEN ClassFormat.putInsn(topCtx.m, Opcodes.IOR)
      ELSIF op = OJS.minus THEN
        (*ANN*)
        pushConst(-1);
        ClassFormat.putInsn(topCtx.m, Opcodes.IXOR);
        ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
      ELSIF op = OJS.times THEN ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
      ELSIF op = OJS.rdiv THEN ClassFormat.putInsn(topCtx.m, Opcodes.IXOR)
      END
    END
  END SetOp;

  (* Code generation for relations *)

  PROCEDURE IntRelation*(op: INTEGER; VAR x, y: Item);    (* x := x < y *)
  BEGIN
    IF (y.mode = OJB.Const) & (y.type.form # OJB.Proc) & (y.a = 0) THEN
      load(x);
      IF y.type.form = OJB.NilTyp THEN
        SetCC(x, relmapNil[op - OJS.eql])
      ELSE
        SetCC(x, relmap0[op - OJS.eql])
      END
    ELSE
      loadAndMaybeSwap(x, y);
      IF (x.type.form = OJB.Pointer) OR (x.type.form = OJB.Proc) OR
         (x.type.form = OJB.NilTyp) THEN
        SetCC(x, relmapAdr[op - OJS.eql])
      ELSE
        SetCC(x, relmap[op - OJS.eql])
      END
    END
  END IntRelation;

  PROCEDURE RealRelation*(op: INTEGER; VAR x, y: Item);    (* x := x < y *)
  BEGIN
    loadAndMaybeSwap(x, y);
    IF (op = OJS.lss) OR (op = OJS.leq) THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.FCMPG)
    ELSE
      ClassFormat.putInsn(topCtx.m, Opcodes.FCMPL)
    END ;
    SetCC(x, relmap0[op - OJS.eql])
  END RealRelation;

  PROCEDURE StrToChar*(VAR x: Item);
  BEGIN
    x.type := OJB.charType; x.a := ORD(OJS.ExtractChar(x.a))
  END StrToChar;

  PROCEDURE CharToStr*(VAR x: Item);
    VAR c: CHAR;
  BEGIN
    c := CHR(x.a);
    x.mode := OJB.Const; x.type := OJB.strType; x.a := OJS.InsertChar(c);
    x.b := 2; (* 1 char + 0X *) x.rdo := TRUE
  END CharToStr;

  PROCEDURE StringRelation*(op: INTEGER; VAR x, y: Item);    (* x := x < y *)
  BEGIN
    (*x, y are char arrays or strings*)
    IF (x.type.form = OJB.String) & (x.b = 2) & (y.type.form = OJB.String) &
       (y.b = 2) THEN
      StrToChar(x); StrToChar(y);
      IntRelation(op, x, y)
    ELSIF (x.type.form # OJB.String) & (y.type.form = OJB.String) &
          (y.b = 1) THEN (* x := x < "" *)
      load(x);
      pushConst(0);
      pushTypedArray(x.type.base);
      SetCC(x, relmap0[op - OJS.eql])
    ELSIF (x.type.form = OJB.String) & (y.type.form # OJB.String) THEN
      load(y);
      load(x);
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL,
                                "java/lang/String", "toCharArray", "()[C", 0);
      x.mode := Stack;
      ClassFormat.putInsn(topCtx.m, Opcodes.SWAP);
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                "OberonRuntime", "StrCmp", "([C[C)I", 2);
      SetCC(x, relmap0[op - OJS.eql])
    ELSE
      load(x);
      IF x.type.form = OJB.String THEN
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL,
                                  "java/lang/String", "toCharArray", "()[C", 0);
        x.mode := Stack
      END ;
      load(y);
      IF y.type.form = OJB.String THEN
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL,
                                  "java/lang/String", "toCharArray", "()[C", 0);
        x.mode := Stack
      END ;
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                "OberonRuntime", "StrCmp", "([C[C)I", 2);
      SetCC(x, relmap0[op - OJS.eql])
    END
  END StringRelation;

  PROCEDURE makeCopyDesc(VAR s, desc: ARRAY OF CHAR);
    VAR i: INTEGER;
  BEGIN
    i := Strings.Write("(L", desc, 0);
    i := Strings.Write(s, desc, i);
    i := Strings.Write(";)V", desc, i);
    IF i = -1 THEN OJS.Mark("Maximum descriptor length reached") END
  END makeCopyDesc;


  (* Code generation of Assignments *)

  PROCEDURE Store*(VAR x, y: Item; storeStruct: BOOLEAN);   (* x := y *)
    VAR  desc, typeName :ClassFormat.Descriptor;
  BEGIN
    load(y);
    IF storeStruct THEN
      internalName(x.type, typeName);
      makeCopyDesc(typeName, desc);
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL, typeName,
                                "copyFrom", desc, 1)
    ELSE
      IF (x.mode = OJB.Var) OR (x.mode = Stack) OR (x.mode = OJB.ParStruct) THEN
        IF x.r > 0 THEN (*local*)
          storeTypedLocal(x)
        ELSE
          Descriptor(x.type, desc);
          ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTSTATIC, x.modName,
                                   x.name, desc)
        END
      ELSIF x.mode = RegI THEN
        storeTypedArray(x.type)
      ELSIF x.mode = Field THEN
        Descriptor(x.type, desc);
        ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTFIELD, x.recordName,
                                 x.name, desc)
      ELSIF x.mode = OJB.Par THEN
        storeTypedArray(x.type)
      ELSE OJS.Mark("illegal assignment")
      END
    END
  END Store;

  PROCEDURE storeArrayR(i: INTEGER; type: OJB.Type; VAR x, y: Item);
    VAR desc, iname: ClassFormat.Descriptor;
      cond, end, lastTmp, index: INTEGER;
  BEGIN
    IF type.form = OJB.Array THEN
      IF type.len < 0  THEN
        (*
          only monodimensional open arrays are supported.
          ex.
            PROCEDURE P(VAR b : ARRAY OF ARRAY OF INTEGER);
              VAR c: ARRAY 10, 10 OF INTEGER;
            BEGIN
              c := b; (* illegal see Type Rule E.6, B.2, B.3 *)
            END P;
         *)
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, y.a);
        ClassFormat.putInsn(topCtx.m, Opcodes.ARRAYLENGTH);
        pushConst(1);
        ClassFormat.putInsn(topCtx.m, Opcodes.ISUB)
      ELSE
        pushConst(type.len - 1)
      END ;
      (* topCtx.numTmpVars is the first free local to use *)
      index := i + topCtx.numTmpVars;
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ISTORE, index);
      cond := topCtx.m.i;
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, index);
      end := topCtx.m.i;
      ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFLT, 0);
      storeArrayR(i + 1, type.base, x, y);
      ClassFormat.putIincInsn(topCtx.m, index, -1);
      ClassFormat.putGotoInsn(topCtx.m, cond-topCtx.m.i, 0);
      Fixup(end)
    ELSE
      (* as we incremented index(= last used local) we have to
         update topCtx.numTmpVars as it is still pointing to the
         first used local *)
      IF ~isPrimitiveType(type) & (type.form # OJB.Pointer) &
        (type.form # OJB.Proc) THEN
        topCtx.numTmpVars := topCtx.numTmpVars + i + 1;
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
        lastTmp := pushIndexes2(i+1);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, y.a);
        lastTmp := pushIndexes2(i+1);
        internalName(type, iname);
        makeCopyDesc(iname, desc);
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL, iname,
                                  "copyFrom", desc, 1);
      ELSE
        topCtx.numTmpVars := topCtx.numTmpVars + i;
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
        lastTmp := pushIndexes2(i);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, lastTmp);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, y.a);
        lastTmp := pushIndexes2(i);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, lastTmp);
        pushTypedArray(type);
        storeTypedArray(type)
      END
    END
  END storeArrayR;

  PROCEDURE storeTempLocal(VAR x: Item);
  BEGIN
    load(x);
    x.a := topCtx.numTmpVars;
    storeTypedLocal(x);
    INC(topCtx.numTmpVars)
  END storeTempLocal;

  PROCEDURE storeArray*(VAR x, y: Item);
    VAR end: INTEGER;
  BEGIN
    load(y);
    y.a := topCtx.numTmpVars;
    storeTypedLocal(y);
    INC(topCtx.numTmpVars);
    (* OJP guarantees x and y are regular arrays (not open) with equal element
       types and length or x is a regular array, y is an open array with equal
       base type *)
    IF (check) & (y.type.len < 0) THEN (*open array len*)
      pushConst(x.type.len);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, y.a);
      ClassFormat.putInsn(topCtx.m, Opcodes.ARRAYLENGTH);
      end := topCtx.m.i;
      ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFICMPGE, 0);
      Trap("array copy overflow");
      Fixup(end)
    END ;
    IF isPrimitiveType(x.type.base) THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, y.a);
      pushConst(0);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
      pushConst(0);
      IF y.type.len < 0 THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, y.a);
        ClassFormat.putInsn(topCtx.m, Opcodes.ARRAYLENGTH)
      ELSE
        pushConst(y.type.len)
      END ;
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                "java/lang/System", "arraycopy",
                                "(Ljava/lang/Object;ILjava/lang/Object;II)V", 5)
    ELSE
      storeArrayR(0, y.type, x, y)
    END
  END storeArray;

  PROCEDURE StoreProc*(VAR x, y: Item);   (* x := y *)
  BEGIN
    IF y.mode = OJB.Const THEN
      load(y)
    END ;
    Store(x, y, FALSE)
  END StoreProc;

  PROCEDURE ValueParam*(VAR x: Item);
  BEGIN
    load(x)
  END ValueParam;

  PROCEDURE StringParam*(VAR x: Item);
  BEGIN
    load(x);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL,
                              "java/lang/String", "toCharArray", "()[C", 0)
  END StringParam;

  PROCEDURE CopyString*(VAR x, y: Item);   (* x := y *)
    VAR len, end: INTEGER;
  BEGIN
    len := x.type.len;
    IF len >= 0 THEN
      IF len < y.b THEN OJS.Mark("string too long") END
    ELSIF check THEN (*open array len*)
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
      ClassFormat.putInsn(topCtx.m, Opcodes.ARRAYLENGTH);
      pushConst(y.b);
      end := topCtx.m.i;
      ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFICMPGE, 0);
      Trap("string too long");
      Fixup(end)
    END ;
    IF y.b = 1 THEN (* x := "" *)
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
      pushConst(0);
      pushConst(0);
      ClassFormat.putInsn(topCtx.m, Opcodes.CASTORE)
    ELSE
      StringParam(y);
      pushConst(0);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
      pushConst(0);
      pushConst(y.b);
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                "java/lang/System", "arraycopy",
                                "(Ljava/lang/Object;ILjava/lang/Object;II)V", 5)
    END
  END CopyString;

  PROCEDURE loadPar*(VAR x: Item);
  BEGIN
    IF x.mode = OJB.Par THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
      ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0)
    END
  END loadPar;

  PROCEDURE VarParam*(VAR x: Item);
    VAR desc:  ClassFormat.Descriptor;
      newStoreStmt: StoreStmt;
      type, newType: OJB.Type;
  BEGIN
    IF (x.mode = OJB.Par) & (x.oldType = NIL) THEN
      (* x is already a var parameter with no type guard, so pass it by value *)
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a)
    ELSE
      IF x.oldType # NIL THEN
        newType := x.oldType
      ELSE
        newType := x.type
      END ;
      NEW(newStoreStmt);
      IF x.mode = Field THEN
        newStoreStmt.x.a := topCtx.numTmpVars;
        newStoreStmt.x.mode := x.mode;
        Strings.Copy(x.recordName, newStoreStmt.x.recordName);
        Strings.Copy(x.modName, newStoreStmt.x.modName);
        Strings.Copy(x.name, newStoreStmt.x.name);
        newStoreStmt.x.type := newType;
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ASTORE, newStoreStmt.x.a);
        INC(topCtx.numTmpVars)
      ELSIF x.mode = RegI THEN
        newStoreStmt.x.a := topCtx.numTmpVars;
        INC(topCtx.numTmpVars);
        newStoreStmt.x.b := topCtx.numTmpVars;
        INC(topCtx.numTmpVars);
        newStoreStmt.x.mode := x.mode;
        Strings.Copy(x.name, newStoreStmt.x.name);
        Strings.Copy(x.modName, newStoreStmt.x.modName);
        newStoreStmt.x.type := newType;
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ISTORE, newStoreStmt.x.b);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ASTORE, newStoreStmt.x.a)
      ELSIF (x.mode = OJB.Var) OR (x.oldType # NIL) THEN
        newStoreStmt.x.a := x.a;
        newStoreStmt.x.r := x.r;
        newStoreStmt.x.mode := x.mode;
        Strings.Copy(x.name, newStoreStmt.x.name);
        Strings.Copy(x.modName, newStoreStmt.x.modName);
        newStoreStmt.x.type := newType
      ELSE OJS.Mark("Only variables allowed")
      END ;
      ClassFormat.putInsn(topCtx.m, Opcodes.ICONST1);
      type := x.type;
      IF isPrimitiveType(type) THEN
        emitPrimitiveNewArray(type)
      ELSIF type.form = OJB.Array THEN
        Descriptor(type, desc);
        ClassFormat.putTypeInsn(topCtx.m, Opcodes.ANEWARRAY, desc)
      ELSE
        internalName(type, desc);
        ClassFormat.putTypeInsn(topCtx.m, Opcodes.ANEWARRAY, desc)
      END ;
      ClassFormat.putInsn(topCtx.m, Opcodes.DUP);
      ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0);
      IF x.mode = Field THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, newStoreStmt.x.a)
      ELSIF x.mode = RegI THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, newStoreStmt.x.a);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, newStoreStmt.x.b)
      END ;
      load(x);
      storeTypedArray(x.type);
      newStoreStmt.y.a := topCtx.numTmpVars;
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ASTORE, topCtx.numTmpVars);
      INC(topCtx.numTmpVars);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, newStoreStmt.y.a);
      newStoreStmt.y.mode := RegI;
      newStoreStmt.y.type := type;
      newStoreStmt.next := topCtx.storeStmt;
      topCtx.storeStmt := newStoreStmt
    END
  END VarParam;

  PROCEDURE storeVarPar*;
    VAR h: StoreStmt;
  BEGIN
    h := topCtx.storeStmt;
    WHILE h # NIL DO
      IF h.x.mode = Field THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, h.x.a)
      ELSIF h.x.mode = RegI THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, h.x.a);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, h.x.b)
      ELSIF h.x.mode = OJB.Par THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, h.x.a);
        ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0)
      END ;
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, h.y.a);
      ClassFormat.putInsn(topCtx.m, Opcodes.ICONST0);
      Store(h.x, h.y, FALSE);
      h := h.next;
    END ;
    topCtx.storeStmt := NIL
  END storeVarPar;

  PROCEDURE PrepAssign*(VAR x: Item);
  BEGIN
    IF x.type.form = OJB.Array THEN
      storeTempLocal(x)
    ELSIF x.type.form = OJB.Record THEN
      load(x)
    ELSE
      loadPar(x)
    END
  END PrepAssign;

  PROCEDURE For0*(VAR x, y: Item);
  BEGIN
    Store(x, y, FALSE)
  END For0;

  PROCEDURE For1*(VAR x, z, w: Item): INTEGER;
    VAR L, oldMode: INTEGER;
  BEGIN
    load(z);
    oldMode := x.mode;
    load(x);
    x.mode := oldMode;
    L := topCtx.m.i;
    IF w.a < 0 THEN ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFICMPGT, 0)
    ELSIF w.a > 0 THEN ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFICMPLT, 0)
    ELSE OJS.Mark("zero increment")
    END
    RETURN L
  END For1;

  PROCEDURE For2*(VAR x, w: Item);
  BEGIN
    IF (x.mode = OJB.Var) & (x.r > 0) THEN
      ClassFormat.putIincInsn(topCtx.m, x.a, w.a)
    ELSIF x.mode = OJB.Par THEN
      loadPar(x);
      AddOp(OJS.plus, x, w);
      w.mode := Stack;
      x.mode := OJB.Par;
      Store(x, w, FALSE)
    ELSE
      AddOp(OJS.plus, x, w);
      w.mode := Stack;
      Store(x, w, FALSE)
    END
  END For2;

  PROCEDURE CaseIn*(VAR x: Item; L: INTEGER): INTEGER;
  BEGIN
    storeTempLocal(x)
    RETURN FJump(L)
  END CaseIn;

  PROCEDURE CaseDefault*(): INTEGER;
    VAR dflt: INTEGER;
  BEGIN
    dflt := topCtx.m.i;
    Trap("Invalid case in CASE statement")
    RETURN dflt
  END CaseDefault;

  PROCEDURE CaseOut*(VAR caseExpr: Item; end, L0, n, dflt: INTEGER;
                     tab: ARRAY OF LabelRange);
    VAR max, lastLow, nLables, i, j: INTEGER;
      all: ARRAY NofCases OF INTEGER;
  BEGIN
    IF n > 0 THEN
      max := tab[n-1].high;
      lastLow := tab[0].low;
      nLables := ABS(max - lastLow + 1);
    ELSE
      max := 0; lastLow := 0; nLables := 0;
    END ;
    IF nLables <= NofCases THEN
      ClassFormat.FixLink(topCtx.m, L0);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, caseExpr.a);
      dflt := dflt-topCtx.m.i;
      i := 0; j := 0;
      WHILE j < n DO
        WHILE lastLow < tab[j].low DO
          all[i] := dflt;
          INC(i);
          INC(lastLow)
        END ;
        WHILE lastLow <= tab[j].high DO
          all[i] := tab[j].L-topCtx.m.i;
          INC(i);
          INC(lastLow)
        END ;
        INC(j)
      END ;
      ClassFormat.putTableSwitchInsn(topCtx.m, tab[0].low, max,
                                     dflt, nLables, all);
      ClassFormat.FixLink(topCtx.m, end)
    ELSE OJS.Mark("too many cases or no case in case statement")
    END
  END CaseOut;

  PROCEDURE PrepCall*(VAR x: Item);
  BEGIN
    (*x.type.form = OJB.Proc*)
    IF x.mode # OJB.Const THEN
      (*call on procedure variable*)
      load(x)
    END
  END PrepCall;

  PROCEDURE Call*(VAR x: Item);
    VAR iname: ClassFormat.Descriptor;
  BEGIN
    (*x.type.form = OJB.Proc*)
    IF x.mode = OJB.Const THEN
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, x.modName,
                                x.name, x.type.signature, x.type.nofpar)
    ELSE
      internalName(x.type, iname);
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL, iname,
                                "invoke", x.type.signature, x.type.nofpar)
    END ;
    IF x.type.base.form # OJB.NoTyp THEN (*function*)
      x.mode := Stack
    END
  END Call;

  PROCEDURE Enter*(proc: OJB.Object; locblksize: INTEGER);
    VAR acc: INTEGER;
      nestedName: ARRAY OJS.IdLen + ClassFormat.nestedIdLen OF CHAR;
  BEGIN
    acc := Opcodes.ACCxSTATIC + Opcodes.ACCxPUBLIC + Opcodes.ACCxFINAL;
    IF proc.nestedId > 0 THEN
      Strings.Copy(proc.name, nestedName);
      Strings.AppendInt(proc.nestedId, 0, nestedName);
      topCtx.m := ClassFormat.NewMI(topCtx.c, acc, nestedName,
                                    proc.type.signature)
    ELSE
      topCtx.m := ClassFormat.NewMI(topCtx.c, acc, proc.name,
                                    proc.type.signature)
    END;
    clearCtx(topCtx);
    initializeScope(getLocalVars(proc.type.dsc, proc.type.nofpar), locblksize)
  END Enter;

  PROCEDURE Return*(type: OJB.Type; VAR x: Item);
  BEGIN
    IF (type # NIL) & (type.form # OJB.NoTyp) THEN
      load(x);
      IF type = OJB.byteType THEN
        pushConst(255);
        ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
      END ;
      emitTypedReturn(x.type)
    ELSE ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx)
    END ;
    ClassFormat.setMaxVars(topCtx.m, topCtx.numTmpVars);
    closeContext
  END Return;

  PROCEDURE Increment1*(upordown: INTEGER; VAR x, y, z: Item);
    VAR op: INTEGER;
  BEGIN
    IF upordown = 0 THEN op := Opcodes.IADD ELSE op := Opcodes.ISUB END ;
    IF y.type.form = OJB.NoTyp THEN y.mode := OJB.Const; y.a := 1 END ;
    IF (x.mode = OJB.Var) & (x.r > 0) & (y.mode = OJB.Const) &
       (y.a >= -128) & (y.a <= 127) THEN
      IF op = Opcodes.ISUB THEN y.a := -y.a END ;
      ClassFormat.putIincInsn(topCtx.m, x.a, y.a)
    ELSE
      IF (x.mode = OJB.Var) & (x.r > 0) THEN
        z := x;
        IF y.mode IN {OJB.Const, OJB.Var} THEN load(x); load(y)
        ELSE
          load(y);
          load(x);
          ClassFormat.putInsn(topCtx.m, Opcodes.SWAP)
        END
      ELSE
        load(y)
      END ;
      ClassFormat.putInsn(topCtx.m, op);
      Store(z, x, FALSE)
    END
  END Increment1;

  PROCEDURE Increment0*(VAR x, z: Item);
  BEGIN
    IF (x.mode # OJB.Var) OR (x.r = 0) THEN
      z := x;
      IF x.mode = RegI THEN
        ClassFormat.putInsn(topCtx.m, Opcodes.DUP2)
      ELSIF x.mode = Field THEN
        ClassFormat.putInsn(topCtx.m, Opcodes.DUP)
      END ;
      IF x.mode = OJB.Par THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
        pushConst(0);
        ClassFormat.putInsn(topCtx.m, Opcodes.DUP2);
        pushTypedArray(x.type);
        x.mode := Stack
      ELSE
        load(x)
      END ;
    END
  END Increment0;

  PROCEDURE Include0*(VAR x, z: Item);
  BEGIN
    z := x;
    IF x.mode = RegI THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.DUP2)
    ELSIF x.mode = Field THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.DUP)
    END ;
    IF x.mode = OJB.Par THEN
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, x.a);
      pushConst(0);
      ClassFormat.putInsn(topCtx.m, Opcodes.DUP2);
      pushTypedArray(x.type);
      x.mode := Stack
    ELSE
      load(x)
    END ;
  END Include0;

  PROCEDURE Include1*(inorex: INTEGER; VAR y, z: Item);
  BEGIN
    Singleton(y);
    load(y);
    IF inorex = 0 THEN
      ClassFormat.putInsn(topCtx.m, Opcodes.IOR)
    ELSE
      pushConst(-1);
      ClassFormat.putInsn(topCtx.m, Opcodes.IXOR);
      ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
    END ;
    Store(z, y, FALSE)
  END Include1;

  PROCEDURE Assert*(VAR x: Item);
    VAR L0: INTEGER;
  BEGIN
    IF x.mode # Cond THEN loadCond(x) END ;
    L0 := topCtx.m.i;
    ClassFormat.putJumpInsn(topCtx.m, x.r, x.b);
    ClassFormat.FixLink(topCtx.m, x.a); x.b := L0;
    Trap("assertion violated");
    ClassFormat.FixLink(topCtx.m, x.b)
  END Assert;

  PROCEDURE New*(VAR x: Item);
    VAR iname: ClassFormat.Descriptor;
      dummy: Item;
  BEGIN
    loadPar(x);
    dummy.mode := Stack;
    internalName(x.type, iname);
    genNew(iname);
    Store(x, dummy, FALSE)
  END New;

  PROCEDURE ReadInt*(VAR x: Item);
  BEGIN
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "ReadInt", "()I", 0);
    x.mode := Stack
  END ReadInt;

  PROCEDURE eot*(VAR x: Item);
  BEGIN
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "eot", "()Z", 0);
    x.mode := Stack
  END eot;

  PROCEDURE WriteInt*(VAR x: Item);
  BEGIN
    load(x);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "WriteInt", "(I)V", 1)
  END WriteInt;

  PROCEDURE WriteReal*(VAR x: Item);
  BEGIN
    load(x);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "WriteReal", "(F)V", 1)
  END WriteReal;

  PROCEDURE WriteChar*(VAR x: Item);
  BEGIN
    load(x);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "WriteChar", "(I)V", 1)
  END WriteChar;

  PROCEDURE WriteLn*;
  BEGIN
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "WriteLn", "()V", 0)
  END WriteLn;

  PROCEDURE Args0*;
  BEGIN
    ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETSTATIC, topCtx.className,
                             "args", "[Ljava/lang/String;");
  END Args0;

  PROCEDURE Args1*(VAR x: Item);
  BEGIN
    load(x)
  END Args1;

  PROCEDURE Args2*(VAR y: Item);
  BEGIN
    load(y);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                              "ARGS", "([Ljava/lang/String;I[C)V", 3)
  END Args2;

  PROCEDURE Abs*(VAR x: Item);
    VAR f: REAL; end: INTEGER;
  BEGIN
    IF x.mode = OJB.Const THEN
      IF x.type.form = OJB.Real THEN
        f := ABS(SYSTEM.VAL(REAL, x.a));
        x.a := SYSTEM.VAL(INTEGER, f)
      ELSE x.a := ABS(x.a)
      END
    ELSE load(x);
      IF x.type.form = OJB.Real THEN
        ClassFormat.putInsn(topCtx.m, Opcodes.DUP);
        ClassFormat.putInsn(topCtx.m, Opcodes.FCONST0);
        ClassFormat.putInsn(topCtx.m, Opcodes.FCMPG);
        end := topCtx.m.i;
        ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFGT, 0);
        ClassFormat.putInsn(topCtx.m, Opcodes.FCONST0);
        ClassFormat.putInsn(topCtx.m, Opcodes.SWAP);
        ClassFormat.putInsn(topCtx.m, Opcodes.FSUB);
        Fixup(end)
      ELSE
        ClassFormat.putInsn(topCtx.m, Opcodes.DUP);
        end := topCtx.m.i;
        ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFGE, 0);
        ClassFormat.putInsn(topCtx.m, Opcodes.INEG);
        Fixup(end)
      END
    END
  END Abs;

  PROCEDURE Odd*(VAR x: Item);
  BEGIN
    IF x.mode = OJB.Const THEN
      x.a := ORD(ODD(x.a))
    ELSE
      load(x);
      pushConst(1);
      ClassFormat.putInsn(topCtx.m, Opcodes.IAND);
      SetCC(x, Opcodes.IFNE)
    END
  END Odd;

  PROCEDURE Floor*(VAR x: Item);
    VAR f: REAL;
  BEGIN
    IF x.mode = OJB.Const THEN
       f := SYSTEM.VAL(REAL, x.a);
       x.a := FLOOR(f)
    ELSE
      load(x);
      ClassFormat.putInsn(topCtx.m, Opcodes.F2D);
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "java/lang/Math",
                                "floor", "(D)D", 1);
      ClassFormat.putInsn(topCtx.m, Opcodes.D2I)
    END
  END Floor;

  PROCEDURE IntToReal*(VAR x: Item);
  BEGIN
    load(x);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "java/lang/Float",
                              "intBitsToFloat", "(I)F", 1)
  END IntToReal;

  PROCEDURE RealToInt*(VAR x: Item);
  BEGIN
    load(x);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "java/lang/Float",
                              "floatToRawIntBits", "(F)I", 1)
  END RealToInt;

  PROCEDURE Float*(VAR x: Item);
  BEGIN
    IF x.mode = OJB.Const THEN
       x.a := SYSTEM.VAL(INTEGER, FLT(x.a))
    ELSE
      load(x);
      ClassFormat.putInsn(topCtx.m, Opcodes.I2F)
    END
  END Float;

  PROCEDURE Ord*(VAR x: Item);
  BEGIN
    (* allow constant folding: ORD("A") + 1 *)
    IF x.mode # OJB.Const THEN
      load(x)
    END
  END Ord;

  PROCEDURE Len*(VAR x: Item);
  BEGIN
    IF x.type.len >= 0 THEN
      Pop(x);
      x.mode := OJB.Const; x.a := x.type.len
    ELSE (*open array*)
      load(x);
      ClassFormat.putInsn(topCtx.m, Opcodes.ARRAYLENGTH);
      x.mode := Stack
    END
  END Len;

  PROCEDURE Argnum*(VAR x: Item);
  BEGIN
    ClassFormat.putFieldInsn(topCtx.m, Opcodes.GETSTATIC, topCtx.className,
                             "args", "[Ljava/lang/String;");
    ClassFormat.putInsn(topCtx.m, Opcodes.ARRAYLENGTH);
    x.mode := Stack
  END Argnum;

  PROCEDURE Shift*(fct: INTEGER; VAR x, y: Item);
    CONST Ror = 3;
    VAR op: INTEGER;
  BEGIN
    IF fct = 0 THEN
      op := Opcodes.ISHL
    ELSIF fct = 1 THEN
      op := Opcodes.ISHR
    ELSE op := Ror
    END ;
    IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
      IF fct = 0 THEN
        x.a := LSL(x.a, y.a)
      ELSIF fct = 1 THEN
        x.a := ASR(x.a, y.a)
      ELSE
        x.a := ROR(x.a, y.a)
      END
    ELSE
      loadAndMaybeSwap(x, y);
      IF op = Ror THEN
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC, "OberonRuntime",
                                  "ROR", "(II)I", 2)
      ELSE
        ClassFormat.putInsn(topCtx.m, op)
      END
    END
  END Shift;

  PROCEDURE Band*(VAR x, y: Item);
  BEGIN
    IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
       x.a := AND(x.a, y.a)
    ELSE
      loadAndMaybeSwap(x, y);
      ClassFormat.putInsn(topCtx.m, Opcodes.IAND)
    END
  END Band;

  PROCEDURE Bor*(VAR x, y: Item);
  BEGIN
    IF (x.mode = OJB.Const) & (y.mode = OJB.Const) THEN
       x.a := BOR(x.a, y.a)
    ELSE
      loadAndMaybeSwap(x, y);
      ClassFormat.putInsn(topCtx.m, Opcodes.IOR)
    END
  END Bor;

  PROCEDURE Bnot*(VAR x: Item);
  BEGIN
    IF x.mode = OJB.Const THEN
       x.a := NOT(x.a)
    ELSE
      load(x);
      pushConst(-1);
      ClassFormat.putInsn(topCtx.m, Opcodes.IXOR)
    END
  END Bnot;

  PROCEDURE Open*(modid: ARRAY OF CHAR);
    VAR ctx: ClassContext;
  BEGIN
    ClassFormat.Init;
    check := TRUE;
    NEW(ctx);
    Strings.Copy(modid, ctx.className);
    topCtx := ctx;
    topCtx.c := ClassFormat.NewCF(Opcodes.ACCxPUBLIC + Opcodes.ACCxFINAL,
                                  topCtx.className, "java/lang/Object");
    dummyMethod := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxPUBLIC, "DUMMY",
                                     "()V");
    topCtx.m := dummyMethod
  END Open;

  PROCEDURE Header*(topScope: OJB.Object);
    VAR desc: ClassFormat.Descriptor;
      tmp: OJB.Object;
  BEGIN
    ClassFormat.addField(topCtx.c, Opcodes.ACCxPUBLIC + Opcodes.ACCxSTATIC,
                         "args", "[Ljava/lang/String;");
    tmp := topScope;
    WHILE tmp # NIL DO
      IF tmp.expo & (tmp.class = OJB.Const) & (tmp.type.form # OJB.Proc) &
         (tmp.type.form # OJB.String) THEN
        Descriptor(tmp.type, desc);
        ClassFormat.addConstField(topCtx.c, tmp.name, desc, tmp.val)
      ELSIF tmp.class = OJB.Var THEN
        Descriptor(tmp.type, desc);
        ClassFormat.addField(topCtx.c, Opcodes.ACCxPUBLIC + Opcodes.ACCxSTATIC,
                             tmp.name, desc)
      END ;
      tmp := tmp.next
    END
  END Header;

  PROCEDURE copyRecord(x: OJB.Object);
    VAR desc, iname: ClassFormat.Descriptor;
  BEGIN
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
    loadRef(x);
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 1);
    loadRef(x);
    internalName(x.type, iname);
    makeCopyDesc(iname, desc);
    ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL, iname,"copyFrom",
                              desc, 1)
  END copyRecord;

  PROCEDURE copyPrimitiveField(x: OJB.Object);
  BEGIN
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 1);
    loadRef(x);
    storeRef(x)
  END copyPrimitiveField;

  PROCEDURE copyArrayR(i: INTEGER; x: OJB.Object; type: OJB.Type; tmpVars,
                       currTmpVar: OJB.Object);
    VAR desc, iname: ClassFormat.Descriptor;
      lastTmp: OJB.Object;
      cond, end: INTEGER;
  BEGIN
    IF type.form = OJB.Array THEN
      pushConst(type.len - 1);
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ISTORE, currTmpVar.val);
      cond := topCtx.m.i;
      ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, currTmpVar.val);
      end := topCtx.m.i;
      ClassFormat.putJumpInsn(topCtx.m, Opcodes.IFLT, 0);
      copyArrayR(i + 1, x, type.base, tmpVars, currTmpVar.next);
      ClassFormat.putIincInsn(topCtx.m, currTmpVar.val, -1);
      ClassFormat.putGotoInsn(topCtx.m, cond-topCtx.m.i, 0);
      Fixup(end)
    ELSE
      IF ~isPrimitiveType(type) & (type.form # OJB.Pointer) &
        (type.form # OJB.Proc) THEN
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
        loadRef(x);
        lastTmp := pushIndexes(i+1, tmpVars);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 1);
        loadRef(x);
        lastTmp := pushIndexes(i+1, tmpVars);
        internalName(type, iname);
        makeCopyDesc(iname, desc);
        ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKEVIRTUAL, iname,
                                  "copyFrom", desc, 1)
      ELSE
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
        loadRef(x);
        lastTmp := pushIndexes(i, tmpVars);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, lastTmp.val);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 1);
        loadRef(x);
        lastTmp := pushIndexes(i, tmpVars);
        ClassFormat.putVarInsn(topCtx.m, Opcodes.ILOAD, lastTmp.val);
        pushTypedArray(type);
        storeTypedArray(type)
      END
    END
  END copyArrayR;

  PROCEDURE copyArray(x, tmpVars: OJB.Object);
  BEGIN
    copyArrayR(0, x, x.type, tmpVars, tmpVars)
  END copyArray;

  PROCEDURE copyState(x: OJB.Object);
    VAR type: OJB.Type; tempVars: OJB.Object;
       num: INTEGER;
  BEGIN
    (* 2 locals so far: this + copyFrom's parameter *)
    num := 2;
    tempVars := createArraysTempVars(x, num, FALSE);
    WHILE x # NIL DO
      IF (x.class = OJB.Var) OR (x.class = OJB.Fld) THEN
        type := x.type;
        IF type.form = OJB.Record THEN
          copyRecord(x)
        ELSIF type.form = OJB.Array THEN
          copyArray(x, tempVars)
        ELSE
          copyPrimitiveField(x)
        END
      END ;
      x := x.next;
    END ;
    topCtx.numTmpVars := num
  END copyState;

  PROCEDURE CopyMethod(obj: OJB.Object);
    VAR desc,typeName: ClassFormat.Descriptor;
  BEGIN
    internalName(obj.type, typeName);
    makeCopyDesc(typeName, desc);
    topCtx.m := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxPUBLIC +
                                  Opcodes.ACCxFINAL, "copyFrom", desc);
    clearCtx(topCtx);
    copyState(obj.type.dsc);
    ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx);
    ClassFormat.setMaxVars(topCtx.m, topCtx.numTmpVars);
    closeContext
  END CopyMethod;

  PROCEDURE MainProc*(hasMain: BOOLEAN);
  BEGIN
    topCtx.m := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxPUBLIC +
                                  Opcodes.ACCxSTATIC, "main",
                                  "([Ljava/lang/String;)V");
    clearCtx(topCtx);
    ClassFormat.putVarInsn(topCtx.m, Opcodes.ALOAD, 0);
    ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTSTATIC, topCtx.className,
                             "args", "[Ljava/lang/String;");
    IF hasMain THEN
      ClassFormat.putMethodInsn(topCtx.m, Opcodes.INVOKESTATIC,
                                topCtx.className, "Main", "()V", 0)
    END ;
    ClassFormat.putInsn(topCtx.m, Opcodes.RETURNx);
    ClassFormat.setMaxVars(topCtx.m, 1);
    closeContext
  END MainProc;

  PROCEDURE addCmdLineArgs;
  BEGIN
    pushConst(0);
    ClassFormat.putTypeInsn(topCtx.m, Opcodes.ANEWARRAY, "java/lang/String");
    ClassFormat.putFieldInsn(topCtx.m, Opcodes.PUTSTATIC, topCtx.className,
                             "args", "[Ljava/lang/String;")
  END addCmdLineArgs;

  PROCEDURE ModuleBody*(topScope: OJB.Object);
  BEGIN
    topCtx.m := ClassFormat.NewMI(topCtx.c, Opcodes.ACCxSTATIC, "<clinit>",
                                  "()V");
    clearCtx(topCtx);
    addCmdLineArgs;
    initializeScope(topScope, 0)
  END ModuleBody;

  PROCEDURE Close*;
    VAR path: ARRAY OJS.maxPath OF CHAR;
  BEGIN
    genClassFilePath(topCtx.className, path);
    ClassFormat.toFile(topCtx.c, path)
  END Close;

  PROCEDURE deleteModule*;
    VAR path: ARRAY OJS.maxPath OF CHAR;
      r: INTEGER;
  BEGIN
    genClassFilePath(topCtx.className, path);
    r := Files.Delete(path)
  END deleteModule;

  PROCEDURE MakeRecordType*(obj: OJB.Object);
    VAR desc, name, baseName: ClassFormat.Descriptor;
      path: ARRAY OJS.maxPath OF CHAR;
      newCtx: ClassContext;
      access: INTEGER;
      tmp: OJB.Object;
      recordType: OJB.Type;
  BEGIN
    NEW(newCtx);
    internalName(obj.type, name);
    access := Opcodes.ACCxPUBLIC;
    Strings.Append("java/lang/Object", baseName);
    IF obj.type.base # NIL THEN
      internalName(obj.type.base, baseName);
      INC(access, Opcodes.ACCxSUPER)
    END ;
    Strings.Copy(topCtx.className, newCtx.className);
    newCtx.next := topCtx;
    topCtx := newCtx;
    newCtx.c := ClassFormat.NewCF(access, name, baseName);
    tmp := obj.type.dsc;
    recordType := obj.type;
    WHILE (tmp # NIL) & (tmp.class = OJB.Fld) & (recordType = tmp.recordType) DO
      Descriptor(tmp.type, desc);
      ClassFormat.addField(topCtx.c, Opcodes.ACCxPUBLIC, tmp.name, desc);
      tmp := tmp.next
    END ;
    Constructor(obj);
    CopyMethod(obj);
    genClassFilePath(name, path);
    ClassFormat.toFile(topCtx.c, path);
    topCtx := topCtx.next
  END MakeRecordType;

BEGIN
  relmap[0] := Opcodes.IFICMPEQ; relmap[1] := Opcodes.IFICMPNE;
  relmap[2] := Opcodes.IFICMPLT; relmap[3] := Opcodes.IFICMPLE;
  relmap[4] := Opcodes.IFICMPGT; relmap[5] := Opcodes.IFICMPGE;
  relmap0[0] := Opcodes.IFEQ; relmap0[1] := Opcodes.IFNE;
  relmap0[2] := Opcodes.IFLT; relmap0[3] := Opcodes.IFLE;
  relmap0[4] := Opcodes.IFGT; relmap0[5] := Opcodes.IFGE;
  relmapNil[0] := Opcodes.IFNULL; relmapNil[1] := Opcodes.IFNONNULL;
  relmapAdr[0] := Opcodes.IFACMPEQ;relmapAdr[1] := Opcodes.IFACMPNE;
END OJG.


================================================
FILE: src/OJP.Mod
================================================
(*
  Copyright 2017 Luca Boasso.
  Copyright (C)2013 Niklaus Wirth (NW), Juerg Gutknecht (JG),
  Paul Reed (PR/PDR).
  Use of this source code is governed by a MIT
  license that can be found in the LICENSE file.
*)

(*
  Parser of Oberon-JVM compiler. Uses Scanner OJS to obtain symbols (tokens),
  OJB for definition of data structures and for handling import and export, and
  OJG to produce bytecode. OJP performs type checking and data allocation.
  Parser is target-independent, except for part of the handling of allocations.
*)

MODULE OJP; (*Oberon compiler for JVM in Oberon-07*)
  IMPORT OJS, OJB, OJG, Strings;

  TYPE PtrBase = POINTER TO PtrBaseDesc;
    PtrBaseDesc = RECORD  (*list of names of pointer base types*)
      name: OJS.Ident; type: OJB.Type; next: PtrBase
    END ;

  VAR sym: INTEGER;   (*last symbol read*)
    level: INTEGER;
    nestedIdCounter: INTEGER; (*unique id for each nested procedure/type*)
    newSF, isDefinition: BOOLEAN;
    expression: PROCEDURE (VAR x: OJG.Item);  (*to avoid forward reference*)
    Type: PROCEDURE (typedef: BOOLEAN): OJB.Type;
    FormalType: PROCEDURE (): OJB.Type;
    EqualSignatures: PROCEDURE (t0, t1: OJB.Type): BOOLEAN;
    modid: OJS.Ident;
    pbsList: PtrBase;   (*list of names of pointer base types*)
    dummy: OJB.Object;
    hasMain: BOOLEAN;

  PROCEDURE Check(s: INTEGER; msg: ARRAY OF CHAR);
  BEGIN
    IF sym = s THEN OJS.Get(sym) ELSE OJS.Mark(msg) END
  END Check;

  PROCEDURE qualident(VAR modName: ARRAY OF CHAR): OJB.Object;
    VAR obj, modObj: OJB.Object;
  BEGIN
    obj := OJB.thisObj(OJS.id); OJS.Get(sym);
    IF obj = NIL THEN OJS.Mark("undef"); obj := dummy END ;
    IF (sym = OJS.period) & (obj.class = OJB.Mod) THEN
      modObj := OJB.GetModFrom(obj);
      Strings.Copy(modObj.name, modName);
      OJS.Get(sym);
      IF sym = OJS.ident THEN obj := OJB.thisimport(obj, OJS.id); OJS.Get(sym);
        IF obj = NIL THEN OJS.Mark("undef"); obj := dummy END
      ELSE OJS.Mark("identifier expected"); obj := dummy
      END
    ELSE Strings.Copy(modid, modName)
    END
    RETURN obj
  END qualident;

  PROCEDURE CheckBool(VAR x: OJG.Item);
  BEGIN
    IF x.type.form # OJB.Bool THEN
      OJS.Mark("not Boolean"); x.type := OJB.boolType
    END
  END CheckBool;

  PROCEDURE CheckInt(VAR x: OJG.Item);
  BEGIN
    IF x.type.form # OJB.Int THEN
      OJS.Mark("not Integer"); x.type := OJB.intType
    END
  END CheckInt;

  PROCEDURE CheckByteRange(VAR x: OJG.Item);
  BEGIN
    IF (x.mode = OJB.Const) & ((x.a < 0) OR (x.a > 255)) THEN
      OJS.Mark("not a valid BYTE value")
    END
  END CheckByteRange;

  PROCEDURE CheckReal(VAR x: OJG.Item);
  BEGIN
    IF x.type.form # OJB.Real THEN
      OJS.Mark("not Real"); x.type := OJB.realType
    END
  END CheckReal;

  PROCEDURE CheckSet(VAR x: OJG.Item);
  BEGIN
    IF x.type.form # OJB.Set THEN OJS.Mark("not Set"); x.type := OJB.setType END
  END CheckSet;

  PROCEDURE CheckSetVal(VAR x: OJG.Item);
  BEGIN
    IF x.type.form # OJB.Int THEN OJS.Mark("not Int"); x.type := OJB.setType
    ELSIF x.mode = OJB.Const THEN
      IF (x.a < 0) OR (x.a >= OJG.MaxSetElement) THEN
        OJS.Mark("invalid set")
      END
    END
  END CheckSetVal;

  PROCEDURE CheckConst(VAR x: OJG.Item);
  BEGIN
    IF x.mode # OJB.Const THEN
      OJS.Mark("not a constant"); x.mode := OJB.Const
    END
  END CheckConst;

  PROCEDURE CheckReadOnly(VAR x: OJG.Item);
  BEGIN
    IF x.rdo THEN OJS.Mark("read-only") END
  END CheckReadOnly;

  PROCEDURE CheckExport(): BOOLEAN;
    VAR expo: BOOLEAN;
  BEGIN
    IF sym = OJS.times THEN
      expo := TRUE; OJS.Get(sym);
      IF level # 0 THEN OJS.Mark("remove asterisk") END
    ELSE expo := isDefinition
    END
    RETURN expo
  END CheckExport;

  PROCEDURE OrigFormalParamType(param: OJB.Object): OJB.Type;
    VAR partype: OJB.Type;
  BEGIN
    IF param.caseOrgType # NIL THEN partype := param.caseOrgType
    ELSE partype := param.type
    END
    RETURN partype
  END OrigFormalParamType;

  PROCEDURE FindObjFrom(modid: ARRAY OF CHAR; VAR x: OJG.Item): OJB.Object;
    VAR obj: OJB.Object;
  BEGIN
    IF (x.mode = OJB.Var) OR (x.mode = OJB.Par) OR (x.mode = OJB.ParStruct) THEN
      obj := OJB.FindObj(modid, x.modName, x.name)
    ELSE
      obj := NIL
    END
    RETURN obj
  END FindObjFrom;

  PROCEDURE IsExtension(t0, t1: OJB.Type): BOOLEAN;
  BEGIN (*t1 is an extension of t0*)
    RETURN (t0 = t1) OR (t1 # NIL) & IsExtension(t0, t1.base)
  END IsExtension;

  (* expressions *)

  PROCEDURE TypeTest(VAR x: OJG.Item; T: OJB.Type; guard: BOOLEAN);
    VAR xt: OJB.Type;
  BEGIN xt := x.type;
    IF (T.form = xt.form) &
       ((T.form = OJB.Pointer) OR
        (T.form = OJB.Record) & (x.mode = OJB.ParStruct)) THEN
      WHILE (xt # T) & (xt # NIL) DO xt := xt.base END ;
      IF xt # T THEN xt := x.type;
        IF xt.form = OJB.Pointer THEN
          IF IsExtension(xt.base, T.base) THEN
            OJG.TypeTest(x, T.base, guard); x.type := T
          ELSE
            OJS.Mark("not an extension")
          END
        ELSIF (xt.form = OJB.Record) & (x.mode = OJB.ParStruct) THEN
          IF IsExtension(xt, T) THEN  OJG.TypeTest(x, T, guard); x.type := T
          ELSE OJS.Mark("not an extension")
          END
        ELSE OJS.Mark("incompatible types")
        END
      ELSIF ~guard THEN OJG.ConstTypeTest(x)
      END
    ELSE OJS.Mark("type mismatch")
    END ;
    IF ~guard THEN x.type := OJB.boolType END
  END TypeTest;

  PROCEDURE selector(VAR x: OJG.Item);
    VAR y: OJG.Item; obj: OJB.Object;
      modName: OJS.Ident;
  BEGIN
    WHILE (sym = OJS.lbrak) OR (sym = OJS.period) OR (sym = OJS.arrow)
        OR (sym = OJS.lparen) & (x.type.form IN {OJB.Record, OJB.Pointer}) DO
      IF sym = OJS.lbrak THEN
        REPEAT
          OJG.Index0(x);
          OJS.Get(sym); expression(y);
          IF x.type.form = OJB.Array THEN
            CheckInt(y); OJG.Index(x, y); x.type := x.type.base
          ELSE OJS.Mark("not an array")
          END
        UNTIL sym # OJS.comma;
        Check(OJS.rbrak, "no ]")
      ELSIF sym = OJS.period THEN OJS.Get(sym);
        IF sym = OJS.ident THEN
          IF x.type.form = OJB.Pointer THEN x.type := x.type.base END ;
          IF x.type.form = OJB.Record THEN
            obj := OJB.thisfield(x.type); OJS.Get(sym);
            IF obj # NIL THEN OJG.FieldAccess(x, obj); x.type := obj.type
            ELSE OJS.Mark("undef")
            END
          ELSE OJS.Mark("not a record")
          END
        ELSE OJS.Mark("ident?")
        END
      ELSIF sym = OJS.arrow THEN
        OJS.Get(sym);
        IF x.type.form = OJB.Pointer THEN x.type := x.type.base
        ELSE OJS.Mark("not a pointer")
        END
      ELSE (*type guard*)
        OJS.Get(sym);
        IF sym = OJS.ident THEN
          obj := qualident(modName);
          IF obj.class = OJB.Typ THEN
            TypeTest(x, obj.type, TRUE)
          ELSE
            OJS.Mark("guard type expected")
          END
        ELSE OJS.Mark("not an identifier")
        END ;
        Check(OJS.rparen, " ) missing")
      END
    END
  END selector;

  (* Type Rule A, B *)
  PROCEDURE EqualTypes(t0, t1: OJB.Type): BOOLEAN;
  BEGIN
    RETURN (t0 = t1)
           OR (t0.form = OJB.Array) & (t1.form = OJB.Array) &
              (t0.len = t1.len) & EqualTypes(t0.base, t1.base)
           OR (t0.form = OJB.Proc) & (t1.form = OJB.Proc) &
              EqualSignatures(t0, t1)
           OR (t0.form = OJB.Pointer) & (t1.form = OJB.Pointer) &
              EqualTypes(t0.base, t1.base)
  END EqualTypes;


  (* Type Rule F.1, F.2 *)
  PROCEDURE CompatibleOpenArrays(t0, t1: OJB.Type): BOOLEAN;
  BEGIN
    RETURN EqualTypes(t0, t1)
           OR (t0.form = OJB.Array) & (t1.form = OJB.Array) & (t1.len < 0) &
              CompatibleOpenArrays(t0.base, t1.base)
  END CompatibleOpenArrays;

  PROCEDURE EqualSignatures0(t0, t1: OJB.Type): BOOLEAN;
    VAR p0, p1: OJB.Object; com: BOOLEAN;
  BEGIN com := TRUE;
    (* Type Rule C.1, C.2 *)
    IF (t0.nofpar = t1.nofpar) & EqualTypes(t0.base, t1.base) THEN
      p0 := t0.dsc; p1 := t1.dsc;
      WHILE p0 # NIL DO
        (* Type Rule C.3, C.4 *)
        IF (p0.class = p1.class) & (p0.rdo = p1.rdo) &
           EqualTypes(OrigFormalParamType(p0), OrigFormalParamType(p1))
        THEN p0 := p0.next; p1 := p1.next
        ELSE p0 := NIL; com := FALSE
        END
      END
    ELSE com := FALSE
    END ;
    RETURN com
  END EqualSignatures0;

  PROCEDURE CompTypes(t0, t1: OJB.Type; varpar: BOOLEAN): BOOLEAN;
  BEGIN (*check for assignment compatibility*)
    (* Type Rule E.1, E.2, E.3, E.4 *)
    RETURN ~((t0.form = OJB.Array) & (t1.form = OJB.Array) & (t0.len < 0) &
             (t1.len < 0)) & EqualTypes(t0, t1)
      OR (t0.form = OJB.Record) & (t1.form = OJB.Record) & IsExtension(t0, t1)
      OR ~varpar &
        ((t0.form = OJB.Pointer) & (t1.form = OJB.Pointer) &
          IsExtension(t0.base, t1.base)
        OR (t0.form IN {OJB.Pointer, OJB.Proc}) & (t1.form = OJB.NilTyp))
  END CompTypes;

  PROCEDURE Parameter(par: OJB.Object);
    VAR x: OJG.Item; varpar: BOOLEAN; partype: OJB.Type;
  BEGIN
    expression(x);
    IF par # NIL THEN
      partype := OrigFormalParamType(par);
      varpar := (par.class = OJB.Par) OR (par.class = OJB.ParStruct);
      IF CompTypes(partype, x.type, varpar) OR    (* Type Rule G.1, G.2, G.3 *)
         CompatibleOpenArrays(x.type, partype) THEN (* Type Rule F.1, F.2 *)
        IF varpar & x.rdo THEN OJS.Mark("read only actual parameter")
        ELSIF ~varpar OR (par.class = OJB.ParStruct) THEN
          IF x.mode = OJB.Typ THEN OJS.Mark("illegal value parameter") END ;
          OJG.ValueParam(x)
        ELSE (*par.class = Par*)
          IF ~par.rdo THEN CheckReadOnly(x) END ;
          OJG.VarParam(x)
        END
      (* Type Rule F.3  *)
      ELSIF (x.type.form = OJB.String) & par.rdo & (partype.form = OJB.Array) &
            (partype.base.form = OJB.Char) & (partype.len < 0) THEN
        OJG.StringParam(x)
      (* Type Rule F.3, J *)
      ELSIF (x.type.form = OJB.Char) & x.rdo & par.rdo &
            (partype.form = OJB.Array) & (partype.base.form = OJB.Char) &
            (partype.len < 0) THEN
        OJG.CharToStr(x);
        OJG.StringParam(x)
      (*
        ~varpar below to prevent this:
           PROCEDURE P(VAR n: INTEGER);  ...  n := ...    uses a STR instruction
           P(b)     STR overwrites bytes around byte variable b
      *)
      (* Type Rule I, G.2 *) (*BYTE*)
      ELSIF ~varpar & (partype.form = OJB.Int) & (x.type.form = OJB.Int) THEN
        IF partype = OJB.byteType THEN CheckByteRange(x) END;
        OJG.ValueParam(x)
      (* Type Rule J, G.2 *)
      ELSIF (x.type.form = OJB.String) & (x.b = 2) & (par.class = OJB.Var) &
            (partype.form = OJB.Char) THEN
        OJG.StrToChar(x);
        OJG.ValueParam(x)
      ELSE OJS.Mark("incompatible parameters")
      END
    END
  END Parameter;

  PROCEDURE ParamList(VAR x: OJG.Item);
    VAR n: INTEGER; par: OJB.Object;
  BEGIN par := x.type.dsc; n := 0;
    IF sym # OJS.rparen THEN
      Parameter(par); n := 1;
      WHILE sym <= OJS.comma DO
        Check(OJS.comma, "comma?");
        IF par # NIL THEN par := par.next END ;
        INC(n); Parameter(par)
      END ;
      Check(OJS.rparen, ") missing")
    ELSE OJS.Get(sym);
    END ;
    IF n < x.type.nofpar THEN OJS.Mark("too few params")
    ELSIF n > x.type.nofpar THEN OJS.Mark("too many params")
    END
  END ParamList;

  PROCEDURE StandFunc(VAR x: OJG.Item; fct: INTEGER; restyp: OJB.Type);
    VAR y: OJG.Item; n, npar: INTEGER;
  BEGIN
    Check(OJS.lparen, "no (");
    npar := fct MOD 10; fct := fct DIV 10;
    n := 0;
    IF npar # 0 THEN expression(x); n := 1 END ;
    WHILE sym = OJS.comma DO
      OJS.Get(sym);
      IF (x.mode # OJB.Typ) & (* x.mode = ORB.Typ for SYSTEM.VAL(x, y) *)
         (x.mode # OJB.Const) (* allow constant folding in OJG.Shift/Band/Bor *)
         THEN
        OJG.ValueParam(x)
      END ;
      expression(y);
      INC(n)
    END ;
    Check(OJS.rparen, "no )");
    IF n = npar THEN
      IF fct = 0 THEN (*ABS*)
        IF x.type.form IN {OJB.Int, OJB.Real} THEN
          OJG.Abs(x); restyp := x.type
        ELSE
          OJS.Mark("bad type")
        END
      ELSIF fct = 1 THEN (*ODD*) CheckInt(x); OJG.Odd(x)
      ELSIF fct = 2 THEN (*FLOOR*) CheckReal(x); OJG.Floor(x)
      ELSIF fct = 3 THEN (*FLT*) CheckInt(x); OJG.Float(x)
      ELSIF fct = 4 THEN (*ORD*)
        IF x.type.form IN {OJB.Char, OJB.Bool, OJB.Set} THEN OJG.Ord(x)
        ELSIF (x.type.form = OJB.String) & (x.b = 2) THEN OJG.StrToChar(x)
        ELSE OJS.Mark("bad type")
        END
      ELSIF fct = 5 THEN (*CHR*) CheckInt(x); OJG.Ord(x)
      ELSIF fct = 6 THEN (*LEN*)
          IF x.type.form = OJB.Array THEN
            OJG.Len(x)
          ELSE
            OJS.Mark("not an array")
          END
      ELSIF fct IN {7, 8, 9} THEN (*LSL, ASR, ROR*)
        IF x.type.form IN {OJB.Int, OJB.Set} THEN
          CheckInt(x); CheckInt(y);
          OJG.Shift(fct-7, x, y); restyp := x.type
        ELSE
          OJS.Mark("bad type")
        END
      ELSIF fct = 11 THEN (*AND*) CheckInt(x); CheckInt(y); OJG.Band(x, y)
      ELSIF fct = 12 THEN (*BOR*) CheckInt(x); CheckInt(y); OJG.Bor(x, y)
      ELSIF fct = 15 THEN (*NOT*) CheckInt(x); OJG.Bnot(x)
      ELSIF fct = 16 THEN (*VAL*)
        IF x.mode = OJB.Typ THEN
          IF ((x.type = OJB.byteType) OR (x.type = OJB.intType)) &
            ((y.type = OJB.byteType) OR (y.type = OJB.intType)) THEN
            (*
              BYTE and INTEGER are compatible, this is a no-op.
              The resulting type must match the one of the expression to be
              converted, as the actual conversion happens elsewhere.
            *)
            restyp := y.type;
          ELSIF ((x.type = OJB.intType) OR (x.type.form = OJB.Set)) &
            ((y.type = OJB.intType) OR (y.type.form = OJB.Set)) THEN
            (* A SET is represented as an INTEGER, this is a no-op. *)
            restyp := x.type
          ELSIF (x.type.form = OJB.Real) & (y.type.form = OJB.Int) THEN
            OJG.IntToReal(y);
            restyp := x.type
          ELSIF (x.type.form = OJB.Int) & (y.type.form = OJB.Real) THEN
            OJG.RealToInt(y);
            restyp := x.type
          ELSIF (x.type.form = OJB.Pointer) & (y.type.form = OJB.Pointer)
          THEN
            TypeTest(y, x.type, TRUE);
            restyp := x.type
          ELSE OJS.Mark("casting not allowed")
          END ;
          x := y
        ELSE OJS.Mark("casting not allowed")
        END
      ELSIF fct = 21 THEN (*eot*) OJG.eot(x)
      ELSIF fct = 22 THEN (*ReadInt*) OJG.ReadInt(x)
      ELSIF fct = 23 THEN (*ARGNUM*) OJG.Argnum(x)
      END ;
      x.type := restyp
    ELSE OJS.Mark("wrong nof params")
    END
  END StandFunc;

  PROCEDURE element(VAR x: OJG.Item);
    VAR y: OJG.Item;
  BEGIN
    expression(x); CheckSetVal(x);
    IF sym = OJS.upto THEN
      OJS.Get(sym);
      OJG.Set0(x);
      expression(y);
      CheckSetVal(y);
      OJG.Set1(x, y)
    ELSE OJG.Singleton(x)
    END ;
    x.type := OJB.setType
  END element;

  PROCEDURE set(VAR x: OJG.Item);
    VAR y: OJG.Item;
  BEGIN
    IF sym >= OJS.if THEN
      IF sym # OJS.rbrace THEN OJS.Mark(" } missing") END ;
      OJG.MakeConstItem(x, OJB.setType, 0) (*empty set*)
    ELSE element(x);
      WHILE sym < OJS.rparen DO
        IF sym = OJS.comma THEN OJS.Get(sym)
        ELSE OJS.Mark("missing comma")
        END ;
        element(y); OJG.SetOp(OJS.plus, x, y)
      END
    END
  END set;

  PROCEDURE factor(VAR x: OJG.Item);
    VAR obj: OJB.Object;
      modName: OJS.Ident;
  BEGIN (*sync*)
    IF (sym < OJS.char) OR (sym > OJS.ident) THEN
      OJS.Mark("expression expected");
      REPEAT OJS.Get(sym)
      UNTIL (sym >= OJS.char) & (sym <= OJS.for) OR (sym >= OJS.then)
    END ;
    IF sym = OJS.ident THEN
      obj := qualident(modName);
      IF obj.class = OJB.SFunc THEN StandFunc(x, obj.val, obj.type)
      ELSE
        Strings.Copy(modName, x.modName);
        OJG.MakeItem(x, obj); selector(x);
        IF sym = OJS.lparen THEN
          OJS.Get(sym);
          IF (x.type.form = OJB.Proc) & (x.type.base.form # OJB.NoTyp) THEN
            OJG.PrepCall(x); ParamList(x); OJG.Call(x);
            OJG.storeVarPar;
            x.type := x.type.base
          ELSE OJS.Mark("not a function"); ParamList(x)
          END
        END
      END
    ELSIF sym = OJS.int THEN
      OJG.MakeConstItem(x, OJB.intType, OJS.ival); OJS.Get(sym)
    ELSIF sym = OJS.real THEN
      OJG.MakeRealItem(x, OJS.rval); OJS.Get(sym)
    ELSIF sym = OJS.char THEN
      OJG.MakeConstItem(x, OJB.charType, OJS.ival); OJS.Get(sym)
    ELSIF sym = OJS.nil THEN
       OJS.Get(sym); OJG.MakeConstItem(x, OJB.nilType, 0)
    ELSIF sym = OJS.string THEN
       OJG.MakeStringItem(x); OJS.Get(sym)
    ELSIF sym = OJS.lparen THEN
       OJS.Get(sym); expression(x); Check(OJS.rparen, "no )")
    ELSIF sym = OJS.lbrace THEN
       OJS.Get(sym); set(x); Check(OJS.rbrace, "no }")
    ELSIF sym = OJS.not THEN
      OJS.Get(sym); factor(x); CheckBool(x); OJG.Not(x)
    ELSIF sym = OJS.false THEN
      OJS.Get(sym); OJG.MakeConstItem(x, OJB.boolType, 0)
    ELSIF sym = OJS.true THEN
      OJS.Get(sym); OJG.MakeConstItem(x, OJB.boolType, 1)
    ELSE
      OJS.Mark("not a factor"); OJG.MakeConstItem(x, OJB.intType, 0)
    END
  END factor;

  PROCEDURE term(VAR x: OJG.Item);
    VAR y: OJG.Item; op, f: INTEGER;
  BEGIN
    factor(x); f := x.type.form;
    WHILE (sym >= OJS.times) & (sym <= OJS.and) DO
      op := sym; OJS.Get(sym);
      IF op = OJS.times THEN
        OJG.loadOp(x);
        IF f = OJB.Int THEN factor(y); CheckInt(y); OJG.MulOp(x, y)
        ELSIF f = OJB.Real THEN factor(y); CheckReal(y); OJG.RealOp(op, x, y)
        ELSIF f = OJB.Set THEN factor(y); CheckSet(y); OJG.SetOp(op, x, y)
        ELSE OJS.Mark("bad type")
        END
      ELSIF (op = OJS.div) OR (op = OJS.mod) THEN
        OJG.loadOp(x);
        CheckInt(x); factor(y); CheckInt(y);
        OJG.DivOp(op, x, y)
      ELSIF op = OJS.rdiv THEN
        OJG.loadOp(x);
        IF f = OJB.Real THEN factor(y); CheckReal(y); OJG.RealOp(op, x, y)
        ELSIF f = OJB.Set THEN factor(y); CheckSet(y); OJG.SetOp(op, x, y)
        ELSE OJS.Mark("bad type")
        END
      ELSE (*op = and*)
        CheckBool(x); OJG.And1(x); factor(y); CheckBool(y); OJG.And2(x, y)
      END
    END
  END term;

  PROCEDURE SimpleExpression(VAR x: OJG.Item);
    VAR y: OJG.Item; op: INTEGER;
  BEGIN
    IF sym = OJS.minus THEN OJS.Get(sym); term(x);
      IF x.type.form IN {OJB.Int, OJB.Real, OJB.Set} THEN
        OJG.Neg(x)
      ELSE
        CheckInt(x)
      END
    ELSIF sym = OJS.plus THEN OJS.Get(sym); term(x);
    ELSE term(x)
    END ;
    WHILE (sym >= OJS.plus) & (sym <= OJS.or) DO
      op := sym; OJS.Get(sym);
      IF op = OJS.or THEN
         OJG.Or1(x); CheckBool(x); term(y); CheckBool(y); OJG.Or2(x, y)
      ELSIF x.type.form = OJB.Int THEN
         OJG.loadOp(x); term(y); CheckInt(y); OJG.AddOp(op, x, y)
      ELSIF x.type.form = OJB.Real THEN
         OJG.loadOp(x); term(y); CheckReal(y); OJG.RealOp(op, x, y)
      ELSE
        CheckSet(x); OJG.loadOp(x); term(y); CheckSet(y); OJG.SetOp(op, x, y)
      END
    END
  END SimpleExpression;

  PROCEDURE expression0(VAR x: OJG.Item);
    VAR y: OJG.Item; obj: OJB.Object; rel, xf, yf: INTEGER;
      modName: OJS.Ident;
  BEGIN
    SimpleExpression(x);
    IF (sym >= OJS.eql) & (sym <= OJS.geq) THEN
      rel := sym; OJS.Get(sym);
      OJG.loadOp(x);
      SimpleExpression(y); xf := x.type.form; yf := y.type.form;
      IF x.type = y.type THEN
        IF (xf IN {OJB.Char, OJB.Int}) THEN OJG.IntRelation(rel, x, y)
        ELSIF xf = OJB.Real THEN OJG.RealRelation(rel, x, y)
        ELSIF xf IN {OJB.Set, OJB.Pointer, OJB.Proc, OJB.NilTyp, OJB.Bool} THEN
          IF rel <= OJS.neq THEN
            OJG.IntRelation(rel, x, y)
          ELSE
            OJS.Mark("only = or #")
          END
        ELSIF (xf = OJB.Array) &
              (x.type.base.form = OJB.Char) OR (xf = OJB.String) THEN
          OJG.StringRelation(rel, x, y)
        ELSE OJS.Mark("illegal comparison")
        END
      ELSIF (xf IN {OJB.Pointer, OJB.Proc}) & (yf = OJB.NilTyp)
          OR (yf IN {OJB.Pointer, OJB.Proc}) & (xf = OJB.NilTyp) THEN
        IF rel <= OJS.neq THEN
           OJG.IntRelation(rel, x,  y)
        ELSE
          OJS.Mark("only = or #")
        END
      ELSIF (xf = OJB.Pointer) & (yf = OJB.Pointer) &
            (IsExtension(x.type.base, y.type.base) OR
             IsExtension(y.type.base, x.type.base)) OR
            (xf = OJB.Proc) & (yf = OJB.Proc) &
             EqualSignatures(x.type, y.type) THEN
        IF rel <= OJS.neq THEN
          OJG.IntRelation(rel,  x, y)
        ELSE
          OJS.Mark("only = or #")
        END
      ELSIF (xf = OJB.Array) & (x.type.base.form = OJB.Char) &
            ((yf = OJB.String) OR (yf = OJB.Array) &
             (y.type.base.form = OJB.Char)) OR
            (yf = OJB.Array) & (y.type.base.form = OJB.Char) &
            (xf = OJB.String) THEN
        OJG.StringRelation(rel, x, y)
      ELSIF (xf = OJB.Char) & (yf = OJB.String) & (y.b = 2) THEN
        OJG.StrToChar(y); OJG.IntRelation(rel, x, y)
      ELSIF (yf = OJB.Char) & (xf = OJB.String) & (x.b = 2) THEN
        OJG.StrToChar(x); OJG.IntRelation(rel, x, y)
      (*BYTE*)
      ELSIF (xf = OJB.Int) & (yf = OJB.Int) THEN OJG.IntRelation(rel,  x, y)
      ELSE OJS.Mark("illegal comparison")
      END ;
      x.type := OJB.boolType
    ELSIF sym = OJS.in THEN
      OJG.In0(x);
      OJS.Get(sym);
      CheckInt(x); SimpleExpression(y);
      CheckSet(y); OJG.In1(y);
      x.type := OJB.boolType
    ELSIF sym = OJS.is THEN
      OJS.Get(sym);
      obj := qualident(modName);
      Strings.Copy(modName, x.modName);
      TypeTest(x, obj.type, FALSE);
      x.type := OJB.boolType
    END
  END expression0;

  (* statements *)

  PROCEDURE StandProc(pno: INTEGER);
    VAR npar: INTEGER; (*nof formal parameters*)
      x, y, z: OJG.Item;
  BEGIN
    npar := pno MOD 10; pno := pno DIV 10;
    IF (npar = 0) & (pno = 17) THEN
      OJG.WriteLn
    ELSIF (npar = 2) & (pno = 24) THEN (*ARGS*)
      OJG.Args0();
      Check(OJS.lparen, "no (");
      expression(x); CheckInt(x); OJG.Args1(x);
      Check(OJS.comma, "no ,");
      expression(y);
      Check(OJS.rparen, "no )");
      IF (y.type.form # OJB.Array) OR (y.type.base # OJB.charType) THEN
        OJS.Mark("not ARRAY OF CHAR")
      END ;
      OJG.Args2(y)
    ELSIF (npar = 2) & ((pno = 2) OR (pno = 3)) THEN (*INCL, EXCL*)
      Check(OJS.lparen, "no (");
      expression(x); CheckSet(x); CheckReadOnly(x);
      OJG.Include0(x, z);
      Check(OJS.comma, "no ,");
      expression(y);
      Check(OJS.rparen, "no )");
      CheckInt(y); OJG.Include1(pno-2, y, z)
    ELSIF (pno = 0) OR (pno = 1) THEN (*INC, DEC*)
      Check(OJS.lparen, "no (");
      expression(x); CheckInt(x); CheckReadOnly(x);
      OJG.Increment0(x, z);
      IF sym = OJS.comma THEN
        OJS.Get(sym); expression(y); CheckInt(y)
      ELSE
        y.type := OJB.noType
      END ;
      Check(OJS.rparen, "no )");
      OJG.Increment1(pno, x, y, z)
    ELSE
      Check(OJS.lparen, "no (");
      expression(x);
      Check(OJS.rparen, "no )");
      IF pno = 4 THEN CheckBool(x); OJG.Assert(x)
      ELSIF pno = 5 THEN(*NEW*) CheckReadOnly(x);
         IF (x.type.form = OJB.Pointer) & (x.type.base.form = OJB.Record) THEN
           OJG.New(x)
         ELSE
           OJS.Mark("not a pointer to record")
         END
      ELSIF pno = 15 THEN
        IF (x.type.form = OJB.String) & (x.b = 2) THEN
          OJG.StrToChar(x)
        ELSIF x.type.form # OJB.Char THEN
          OJS.Mark("not Char"); x.type := OJB.charType
        END ;
        OJG.WriteChar(x)
      ELSIF pno = 16 THEN CheckInt(x); OJG.WriteInt(x)
      ELSIF pno = 18 THEN CheckReal(x); OJG.WriteReal(x)
      END
    END
  END StandProc;

  PROCEDURE StatSequence;
    VAR obj: OJB.Object;
      orgtype: OJB.Type; (*original type of case var*)
      x, y, z, w: OJG.Item;
      L0, L1: INTEGER;
      modName: OJS.Ident;

    PROCEDURE TypeCase(obj: OJB.Object; VAR x: OJG.Item);
      VAR typobj: OJB.Object;
        modName: OJS.Ident;
    BEGIN
      IF sym = OJS.ident THEN
        typobj := qualident(modName);
        Strings.Copy(modName, x.modName);
        OJG.MakeItem(x, obj);
        x.oldType := NIL;
        IF typobj.class # OJB.Typ THEN OJS.Mark("not a type") END ;
        TypeTest(x, typobj.type, FALSE); obj.caseOrgType := obj.type;
        obj.type := typobj.type;
        OJG.CFJump(x); Check(OJS.colon, ": expected"); StatSequence
      ELSE OJS.Mark("type id expected"); OJG.CFJump(x)
      END
    END TypeCase;

    PROCEDURE LabelRange(labelForm: INTEGER; VAR x, y: OJG.Item; VAR n: INTEGER;
                         VAR tab: ARRAY OF OJG.LabelRange; stmts: INTEGER);
      VAR i: INTEGER; done: BOOLEAN;
    BEGIN
      expression(x);
      CheckConst(x);
      IF (x.type.form = OJB.String) & (x.b = 2) THEN OJG.StrToChar(x) END ;
      IF x.type.form # labelForm THEN
        OJS.Mark("wrong type of case label")
      END ;
      IF sym = OJS.upto THEN
        OJS.Get(sym);
        expression(y);
        CheckConst(y);
        IF (y.type.form = OJB.String) & (y.b = 2) THEN OJG.StrToChar(y) END ;
        IF y.type.form # x.type.form THEN
           OJS.Mark("wrong type of case label")
        END ;
        IF y.a < x.a THEN OJS.Mark("illegal value of constant"); y.a := x.a END;
      ELSE y := x
      END ;
      (*enter label range into ordered table*)
      i := n;
      done := FALSE;
      IF i < OJG.NofCases THEN
        WHILE (i # 0) & ~done DO
          IF tab[i-1].low <= y.a THEN
            IF tab[i-1].high >= x.a THEN
              OJS.Mark("case label defined more than once")
            END ;
            done := TRUE
          ELSE
            tab[i] := tab[i-1];
            DEC(i)
          END
        END ;
        tab[i].low := x.a; tab[i].high := y.a;
        tab[i].L := stmts;
        INC(n)
      ELSE OJS.Mark("too many cases or no case in case statement")
      END
    END LabelRange;

    PROCEDURE Case(labelForm: INTEGER; VAR n: INTEGER;
                   VAR tab: ARRAY OF OJG.LabelRange);
      VAR x, y: OJG.Item;
        stmts: INTEGER;
    BEGIN
      IF sym IN {OJS.int, OJS.char, OJS.minus, OJS.plus, OJS.string, OJS.ident}
         THEN
        stmts := OJG.getPC();
        LabelRange(labelForm, x, y, n, tab, stmts);
        WHILE sym = OJS.comma DO
          OJS.Get(sym);
          LabelRange(labelForm, x, y, n, tab, stmts)
        END ;
        IF sym IN {OJS.int, OJS.char, OJS.minus, OJS.plus, OJS.string,OJS.ident}
           THEN
          OJS.Mark("',' missing")
        END ;
        Check(OJS.colon, ": expected");
        StatSequence;
      ELSE OJS.Mark("integer or character expected")
      END
    END Case;

    PROCEDURE CasePart(VAR caseExpr: OJG.Item);
      VAR n, L0, dflt, end: INTEGER;
        tab: ARRAY OJG.NofCases OF OJG.LabelRange;
    BEGIN
      L0 := 0;
      end := 0;
      n := 0;
      L0 := OJG.CaseIn(caseExpr, L0);
      Case(caseExpr.type.form, n, tab);
      end := OJG.FJump(end);
      WHILE sym = OJS.bar DO
        OJS.Get(sym); Case(caseExpr.type.form, n, tab);
        end := OJG.FJump(end)
      END ;
      dflt := OJG.CaseDefault();
      OJG.CaseOut(caseExpr, end, L0, n, dflt, tab)
    END CasePart;

    PROCEDURE SkipCase;
    BEGIN
      WHILE sym # OJS.colon DO OJS.Get(sym) END ;
      OJS.Get(sym); StatSequence
    END SkipCase;

  BEGIN
    (* StatSequence *)
    REPEAT (*sync*) obj := NIL;
      IF ~((sym >= OJS.ident) & (sym <= OJS.for) OR (sym >= OJS.semicolon)) THEN
        OJS.Mark("statement expected");
        REPEAT OJS.Get(sym) UNTIL sym >= OJS.ident
      END ;
      OJG.SetLineNumber(OJS.GetLine());
      IF sym = OJS.ident THEN
        obj := qualident(modName);
        Strings.Copy(modName, x.modName);
        OJG.MakeItem(x, obj);
        IF x.mode = OJB.SProc THEN StandProc(obj.val)
        ELSE selector(x);
          IF sym = OJS.becomes THEN (*assignment*)
            OJS.Get(sym); CheckReadOnly(x);
            OJG.PrepAssign(x);
            expression(y);
            IF y.mode = OJB.Typ THEN
              OJS.Mark("illegal assignment")
            (* Type Rule E.1, E.2, E.3, E.4 *)
            ELSIF CompTypes(x.type, y.type, FALSE) THEN
              IF x.type.form <= OJB.Pointer THEN OJG.Store(x, y, FALSE)
              ELSIF x.type.form = OJB.Proc THEN OJG.StoreProc(x, y)
              ELSIF y.type.form = OJB.Record THEN
                  OJG.Store(x, y, TRUE)
              ELSIF y.type.form = OJB.Array THEN
                OJG.storeArray(x, y)
              END
            (* Type Rule E.6 *)
            ELSIF (x.type.form = OJB.Array) & (y.type.form = OJB.Array) &
                  (x.type.len > 0) & (y.type.len < 0) &
                  EqualTypes(x.type.base, y.type.base) THEN
              OJG.storeArray(x, y)
            (* Type Rule E.5 *)
            ELSIF (x.type.form = OJB.Array) & (x.type.base.form = OJB.Char) &
                  (y.type.form = OJB.String) THEN
              OJG.CopyString(x, y)
            (* Type Rule E.5, J *)
            ELSIF (x.type.form = OJB.Array) & (x.type.base.form = OJB.Char) &
                  y.rdo & (y.type.form = OJB.Char) THEN
              OJG.CharToStr(y);
              OJG.CopyString(x, y);
            (* Type Rule I *)  (*BYTE*)
            ELSIF (x.type.form = OJB.Int) & (y.type.form = OJB.Int) THEN
              IF x.type = OJB.byteType THEN CheckByteRange(y) END;
              OJG.Store(x, y, FALSE)
            (* Type Rule J *)
            ELSIF (x.type.form = OJB.Char) & (y.type.form = OJB.String) &
                  (y.b = 2) THEN
              OJG.StrToChar(y); OJG.Store(x, y, FALSE)
            ELSE OJS.Mark("illegal assignment")
            END
          ELSIF sym = OJS.eql THEN
            OJS.Mark("should be :="); OJS.Get(sym); expression(y)
          ELSIF sym = OJS.lparen THEN (*procedure call*)
            OJS.Get(sym);
            IF (x.type.form = OJB.Proc) & (x.type.base.form = OJB.NoTyp) THEN
              OJG.PrepCall(x); ParamList(x); OJG.Call(x);
              OJG.storeVarPar
            ELSE OJS.Mark("not a procedure"); ParamList(x)
            END
          (*procedure call without parameters*)
          ELSIF x.type.form = OJB.Proc THEN
            IF x.type.nofpar > 0 THEN OJS.Mark("missing parameters") END ;
            IF x.type.base.form = OJB.NoTyp THEN
              OJG.PrepCall(x);
              OJG.Call(x)
            ELSE
              OJS.Mark("not a procedure")
            END
          ELSIF x.mode = OJB.Typ THEN OJS.Mark("illegal assignment")
          ELSE OJS.Mark("not a procedure")
          END
        END
      ELSIF sym = OJS.if THEN
        OJS.Get(sym); expression(x); CheckBool(x); OJG.CFJump(x);
        Check(OJS.then, "no THEN");
        StatSequence; L0 := 0;
        WHILE sym = OJS.elsif DO
          OJS.Get(sym); L0 := OJG.FJump(L0); OJG.Fixup(x.a); expression(x);
          CheckBool(x); OJG.CFJump(x); Check(OJS.then, "no THEN"); StatSequence
        END ;
        IF sym = OJS.else THEN
          OJS.Get(sym); L0 := OJG.FJump(L0); OJG.Fixup(x.a); StatSequence
        ELSE
          OJG.Fixup(x.a)
        END ;
        OJG.Fixup(L0); Check(OJS.end, "no END")
      ELSIF sym = OJS.while THEN
        OJS.Get(sym); L0 := OJG.getPC(); expression(x); CheckBool(x);
        OJG.CFJump(x); Check(OJS.do, "no DO"); StatSequence; OJG.BJump(L0);
        WHILE sym = OJS.elsif DO
          OJS.Get(sym); OJG.Fixup(x.a); expression(x); CheckBool(x);
          OJG.CFJump(x); Check(OJS.do, "no DO"); StatSequence; OJG.BJump(L0)
        END ;
        OJG.Fixup(x.a); Check(OJS.end, "no END")
      ELSIF sym = OJS.repeat THEN
        OJS.Get(sym); L0 := OJG.getPC(); StatSequence;
        IF sym = OJS.until THEN
          OJS.Get(sym); expression(x); CheckBool(x); OJG.CBJump(x, L0)
        ELSE OJS.Mark("missing UNTIL")
        END
      ELSIF sym = OJS.for THEN
        OJS.Get(sym);
        IF sym = OJS.ident THEN
          obj := qualident(modName);
          Strings.Copy(modName, x.modName);
          OJG.MakeItem(x, obj); CheckInt(x); CheckReadOnly(x);
          IF sym = OJS.becomes THEN
            OJG.loadPar(x); OJS.Get(sym); expression(y); CheckInt(y);
            OJG.For0(x, y); L0 := OJG.getPC();
            Check(OJS.to, "no TO"); expression(z); CheckInt(z); obj.rdo := TRUE;
            IF sym = OJS.by THEN
              OJS.Get(sym); expression(w); CheckConst(w); CheckInt(w)
            ELSE
              OJG.MakeConstItem(w, OJB.intType, 1)
            END ;
            Check(OJS.do, "no DO"); L1 := OJG.For1(x, z, w);
            StatSequence; Check(OJS.end, "no END");
            OJG.For2(x, w); OJG.BJump(L0); OJG.Fixup(L1); obj.rdo := FALSE
          ELSE OJS.Mark(":= expected")
          END
        ELSE OJS.Mark("identifier expected")
        END
      ELSIF sym = OJS.case THEN
        OJS.Get(sym);
        expression(x);
        Check(OJS.of, "OF expected");
        obj := FindObjFrom(modid, x);
        IF (obj # NIL) & ((obj.type.form = OJB.Pointer) OR
                            (obj.type.form = OJB.Record) &
                            (obj.class = OJB.ParStruct)) THEN
          orgtype := obj.type;
          TypeCase(obj, x); L0 := 0;
          WHILE sym = OJS.bar DO
            OJS.Get(sym); L0 := OJG.FJump(L0); OJG.Fixup(x.a);
            obj.type := orgtype; obj.caseOrgType := NIL;
            TypeCase(obj, x)
          END ;
          OJG.Fixup(x.a); OJG.Fixup(L0); obj.type := orgtype;
          obj.caseOrgType := NIL
        ELSIF (obj = NIL) & ((x.type.form = OJB.Pointer) OR
                             (x.type.form = OJB.Record)) THEN
          OJS.Mark("identifier expected")
        ELSIF (x.type.form = OJB.Int) OR (x.type.form = OJB.Char) THEN
          CasePart(x)
        ELSE OJS.Mark("inadmissible type");
          SkipCase;
          WHILE sym = OJS.bar DO SkipCase END
        END ;
        Check(OJS.end, "no END")
      END ;
      IF sym = OJS.semicolon THEN OJS.Get(sym)
      ELSIF sym < OJS.semicolon THEN OJS.Mark("missing semicolon?")
      END ;
      IF (OJG.curStack() # 0) & (OJS.errcnt = 0) THEN
        OJS.Mark("Reg Stack")
      END
    UNTIL sym > OJS.semicolon
  END StatSequence;

  (* Types and declarations *)

  PROCEDURE IdentList(class: INTEGER): OJB.Object;
    VAR obj, first: OJB.Object;
  BEGIN
    IF sym = OJS.ident THEN
      first := OJB.InsertObj(OJS.id, class); OJS.Get(sym);
      first.expo := CheckExport();
      WHILE sym = OJS.comma DO
        OJS.Get(sym);
        IF sym = OJS.ident THEN
          obj := OJB.InsertObj(OJS.id, class); OJS.Get(sym);
          obj.expo := CheckExport()
        ELSE OJS.Mark("ident?")
        END
      END;
      IF sym = OJS.colon THEN OJS.Get(sym) ELSE OJS.Mark(":?") END
    ELSE first := NIL
    END
    RETURN first
  END IdentList;

  PROC
Download .txt
gitextract_k_kcp7o4/

├── LICENSE.txt
├── Makefile
├── README.md
├── bin/
│   ├── ClassFormat.smb
│   ├── CpCache.smb
│   ├── Files.smb
│   ├── In.smb
│   ├── Math.smb
│   ├── OJB.smb
│   ├── OJG.smb
│   ├── OJP.smb
│   ├── OJS.smb
│   ├── Opcodes.smb
│   ├── Os.smb
│   ├── Out.smb
│   ├── Strings.smb
│   └── oberonc.smb
├── doc/
│   ├── TypeRules.md
│   └── oberon07.g
├── examples/
│   ├── GuessNumber.Mod
│   ├── Hello.Mod
│   ├── Powers.Mod
│   ├── PrimeNumbers.Mod
│   ├── Util.Mod
│   └── fern/
│       ├── IFS.Mod
│       ├── RandomNumbers.Mod
│       ├── XYplane.Mod
│       └── java/
│           └── XYplane.java
├── make.bat
├── src/
│   ├── ClassFormat.Mod
│   ├── CpCache.Mod
│   ├── Files.Mod
│   ├── In.Mod
│   ├── Math.Mod
│   ├── OJB.Mod
│   ├── OJG.Mod
│   ├── OJP.Mod
│   ├── OJS.Mod
│   ├── Opcodes.Mod
│   ├── Os.Mod
│   ├── Out.Mod
│   ├── Strings.Mod
│   ├── java/
│   │   ├── Files.java
│   │   ├── Files_FileDesc.java
│   │   ├── In.java
│   │   ├── Math.java
│   │   ├── OberonRuntime.java
│   │   ├── Os.java
│   │   └── Out.java
│   └── oberonc.Mod
└── tests/
    ├── TestRunner.java
    └── base/
        ├── ArrayAssignment.Mod
        ├── ArrayAssignment.txt
        ├── ArrayConstantSize.Mod
        ├── Arrays2.Mod
        ├── Arrays2.txt
        ├── Arrays3.Mod
        ├── Arrays3.txt
        ├── BitFunc.Mod
        ├── BitFunc.txt
        ├── CaseChar0.Mod
        ├── CaseChar0.txt
        ├── CaseNum0.Mod
        ├── CaseNum0.txt
        ├── CaseNum1.Mod
        ├── CaseNum1.txt
        ├── CaseRecord0.Mod
        ├── CaseRecord0.txt
        ├── CaseRecord1.Mod
        ├── CaseRecord1.txt
        ├── CaseRecord2.Mod
        ├── CaseRecord2.txt
        ├── CaseRecord3.Mod
        ├── CaseRecord3.txt
        ├── CaseRecord4.Mod
        ├── CaseRecord4.txt
        ├── CommutativeSwap.Mod
        ├── CommutativeSwap.txt
        ├── ConstantFoldingAndLoadOp.Mod
        ├── ConstantFoldingAndLoadOp.txt
        ├── DivMul.Mod
        ├── DivMul.txt
        ├── EmptyArrayAndRecord.Mod
        ├── EmptyArrayAndRecord.txt
        ├── ExtTypes.Mod
        ├── ForwardPointerRef.Mod
        ├── ForwardPointerRef.txt
        ├── Fractions.Mod
        ├── Fractions.txt
        ├── FragileBaseClass.Mod
        ├── FragileBaseClass.txt
        ├── MagicSquares.Mod
        ├── MagicSquares.txt
        ├── OpenArrays.Mod
        ├── OpenArrays.txt
        ├── OpenArrays2.Mod
        ├── OpenArrays2.txt
        ├── OpenArrays3.Mod
        ├── OpenArrays3.txt
        ├── Out0.Mod
        ├── Out0.txt
        ├── Out1.Mod
        ├── Out1.txt
        ├── Out2.Mod
        ├── Out2.txt
        ├── Out3.Mod
        ├── Out3.txt
        ├── Out4.Mod
        ├── Out4.txt
        ├── Out5.Mod
        ├── Out5.txt
        ├── Out6.Mod
        ├── Out6.txt
        ├── OutTest.Mod
        ├── OutTest.txt
        ├── Pattern1.Mod
        ├── Pattern1.txt
        ├── Pattern2a.Mod
        ├── Pattern2a.txt
        ├── Pattern2b.Mod
        ├── Pattern2b.txt
        ├── Pattern2c.Mod
        ├── Pattern2c.txt
        ├── Permutations.Mod
        ├── Permutations.txt
        ├── Powers.Mod
        ├── Powers.txt
        ├── PrimeNumbers.Mod
        ├── PrimeNumbers.txt
        ├── ProcComparisons.Mod
        ├── ProcComparisons.txt
        ├── ProcType.Mod
        ├── ProcType.txt
        ├── ProcVariables0.Mod
        ├── ProcVariables0.txt
        ├── ProcVariables1.Mod
        ├── ProcVariables1.txt
        ├── ProcVariables2.Mod
        ├── ProcVariables2.txt
        ├── ProcVariables3.Mod
        ├── ProcVariables3.txt
        ├── ProcVariables4.Mod
        ├── ProcVariables4.txt
        ├── ProcVariables5.Mod
        ├── ProcVariables5.txt
        ├── ProcVariables6.Mod
        ├── ProcVariables6.txt
        ├── ProcVariables7.Mod
        ├── ProcVariables7.txt
        ├── RealExpressions.Mod
        ├── RealExpressions.txt
        ├── RecordAndTypeExtension.Mod
        ├── RecordAndTypeExtension.txt
        ├── RecordAssignment.Mod
        ├── RecordAssignment.txt
        ├── RecordAssignment2.Mod
        ├── RecordAssignment2.txt
        ├── RecordParam.Mod
        ├── RecordParam.txt
        ├── Samples0.Mod
        ├── Samples0.txt
        ├── Samples1.Mod
        ├── Samples1.txt
        ├── Samples2.Mod
        ├── Samples2.txt
        ├── SetTest.Mod
        ├── SetTest.txt
        ├── Strings0.Mod
        ├── Strings0.txt
        ├── Strings1.Mod
        ├── Strings1.txt
        ├── Strings2.Mod
        ├── Strings2.txt
        ├── TestABS.Mod
        ├── TestABS.txt
        ├── TestAnonymousName.Mod
        ├── TestAnonymousName.txt
        ├── TestAssert.Mod
        ├── TestAssert.txt
        ├── TestAssignmentMix.Mod
        ├── TestAssignmentMix.txt
        ├── TestByteType.Mod
        ├── TestByteType.txt
        ├── TestCPS.Mod
        ├── TestCPS.txt
        ├── TestCmdLineArgs.Mod
        ├── TestCmdLineArgs.txt
        ├── TestConstFunc.Mod
        ├── TestConstFunc.txt
        ├── TestCyclicImport00A.Mod
        ├── TestCyclicImport00B.Mod
        ├── TestCyclicImport01A.Mod
        ├── TestCyclicImport01B.Mod
        ├── TestCyclicImport10A.Mod
        ├── TestCyclicImport10B.Mod
        ├── TestCyclicImport11.Mod
        ├── TestCyclicImport12.Mod
        ├── TestEqualSignature00.Mod
        ├── TestEqualSignature00.txt
        ├── TestExprVarPar.Mod
        ├── TestFor.Mod
        ├── TestFor.txt
        ├── TestFor1.Mod
        ├── TestFor1.txt
        ├── TestFunction0.Mod
        ├── TestFunction0.txt
        ├── TestINC0.Mod
        ├── TestINC0.txt
        ├── TestINC1.Mod
        ├── TestINC1.txt
        ├── TestINCLAndEXCL.Mod
        ├── TestINCLAndEXCL.txt
        ├── TestImport00.Mod
        ├── TestImport00.txt
        ├── TestImport01.Mod
        ├── TestImport01.txt
        ├── TestImport10.Mod
        ├── TestImport10.txt
        ├── TestImport100.Mod
        ├── TestImport100.txt
        ├── TestImport11.Mod
        ├── TestImport11.txt
        ├── TestImport110.Mod
        ├── TestImport110.txt
        ├── TestImport111.Mod
        ├── TestImport112.Mod
        ├── TestImport112.txt
        ├── TestImport120.Mod
        ├── TestImport120.txt
        ├── TestImport121.Mod
        ├── TestImport121.txt
        ├── TestImport122.Mod
        ├── TestImport122.txt
        ├── TestImport130.Mod
        ├── TestImport130.txt
        ├── TestImport131.Mod
        ├── TestImport131.txt
        ├── TestImport140.Mod
        ├── TestImport140.txt
        ├── TestImport141.Mod
        ├── TestImport141.txt
        ├── TestImport142.Mod
        ├── TestImport142.txt
        ├── TestImport150.Mod
        ├── TestImport150.txt
        ├── TestImport151.Mod
        ├── TestImport151.txt
        ├── TestImport20.Mod
        ├── TestImport20.txt
        ├── TestImport21.Mod
        ├── TestImport21.txt
        ├── TestImport22.Mod
        ├── TestImport22.txt
        ├── TestImport30.Mod
        ├── TestImport30.txt
        ├── TestImport31.Mod
        ├── TestImport31.txt
        ├── TestImport40.Mod
        ├── TestImport40.txt
        ├── TestImport41.Mod
        ├── TestImport41.txt
        ├── TestImport42.Mod
        ├── TestImport42.txt
        ├── TestImport50.Mod
        ├── TestImport50.txt
        ├── TestImport51.Mod
        ├── TestImport51.txt
        ├── TestImport52.Mod
        ├── TestImport52.txt
        ├── TestImport53.Mod
        ├── TestImport53.txt
        ├── TestImport60.Mod
        ├── TestImport60.txt
        ├── TestImport61.Mod
        ├── TestImport61.txt
        ├── TestImport62.Mod
        ├── TestImport62.txt
        ├── TestImport70.Mod
        ├── TestImport70.txt
        ├── TestImport71.Mod
        ├── TestImport71.txt
        ├── TestImport80.Mod
        ├── TestImport80.txt
        ├── TestImport81.Mod
        ├── TestImport81.txt
        ├── TestImport82.Mod
        ├── TestImport82.txt
        ├── TestImport90.Mod
        ├── TestImport90.txt
        ├── TestImport91.Mod
        ├── TestImport91.txt
        ├── TestMath.Mod
        ├── TestMath.txt
        ├── TestNestedProcs.Mod
        ├── TestNestedProcs.txt
        ├── TestODD.Mod
        ├── TestODD.txt
        ├── TestOOP.Mod
        ├── TestOOP.txt
        ├── TestReadOnlyPar.Mod
        ├── TestReturn0.Mod
        ├── TestReturn0.txt
        ├── TestShift.Mod
        ├── TestShift.txt
        ├── TestStringsMod.Mod
        ├── TestStringsMod.txt
        ├── TestSystemVal.Mod
        ├── TestSystemVal.txt
        ├── TestTypeConvFun.Mod
        ├── TestTypeConvFun.txt
        ├── TestTypeGuardExt.Mod
        ├── TestTypeGuardExt.txt
        ├── TestTypeTest.Mod
        ├── TestTypeTest.txt
        ├── UTF8String.Mod
        ├── UTF8String.txt
        ├── UniqueTypeAndProcNames.Mod
        ├── UniqueTypeAndProcNames.txt
        ├── VarInit.Mod
        ├── VarInit.txt
        ├── VarParGuard.Mod
        └── VarParGuard.txt
Download .txt
SYMBOL INDEX (113 symbols across 9 files)

FILE: examples/fern/java/XYplane.java
  class XYplane (line 10) | public final class XYplane {
    method XYplane (line 20) | private XYplane() {}
    method Open (line 22) | public static void Open() {
    method Dot (line 34) | public static void Dot(int x, int y, int mode) {
    method isDot (line 44) | public static boolean isDot(int x, int y) {
    method Key (line 49) | public static char Key() {
    method Clear (line 53) | public static void Clear() {
    class Viewer (line 57) | private static class Viewer extends JPanel {
      method Viewer (line 60) | public Viewer(int width, int height) {
      method getPreferredSize (line 67) | public Dimension getPreferredSize() {
      method paintComponent (line 71) | public void paintComponent(Graphics g) {
      method fillCanvas (line 77) | public void fillCanvas(int color) {

FILE: src/java/Files.java
  class Files (line 14) | public final class Files {
    method Files (line 22) | private Files() {}
    method Status (line 24) | public static int Status(Files_FileDesc file) {
    method Rename (line 28) | public static int Rename(char[] from, char[] to) {
    method Exists (line 39) | public static boolean Exists(char[] name) {
    method Delete (line 49) | public static int Delete(char[] name) {
    method Create (line 63) | public static Files_FileDesc Create(char[] name) {
    method open (line 67) | private static Files_FileDesc open(char[] name, String mode,
    method Open (line 85) | public static Files_FileDesc Open(char[] name) {
    method Close (line 89) | public static void Close(Files_FileDesc file) {
    method toStr (line 97) | private static String toStr(char[] name) {
    method WriteAsciiStr (line 109) | public static void WriteAsciiStr(Files_FileDesc file, char[] str) {
    method WriteStr (line 121) | public static void WriteStr(Files_FileDesc file, char[] str) {
    method Write (line 133) | public static void Write(Files_FileDesc file, byte b) {
    method WriteChar (line 143) | public static void WriteChar(Files_FileDesc file, char c) {
    method WriteBytes (line 159) | public static void WriteBytes(Files_FileDesc file, byte[] b) {
    method WriteNBytes (line 169) | public static void WriteNBytes(Files_FileDesc file, byte[] b, int len) {
    method WriteInt (line 180) | public static void WriteInt(Files_FileDesc file, int x) {
    method WriteNum (line 221) | public static void WriteNum(Files_FileDesc file, int x) {
    method ReadNum (line 306) | public static int ReadNum(Files_FileDesc file) {
    method Read (line 324) | public static byte Read(Files_FileDesc file) {
    method ReadChar (line 332) | public static char ReadChar(Files_FileDesc file) {
    method read (line 377) | private static int read(Files_FileDesc file) {
    method ReadBytes (line 393) | public static void ReadBytes(Files_FileDesc file, byte[] b, int[] n) {
    method ReadInt (line 407) | public static int ReadInt(Files_FileDesc file) {
    method ReadAsciiStr (line 414) | public static void ReadAsciiStr(Files_FileDesc file, char[] str) {
    method ReadStr (line 429) | public static int ReadStr(Files_FileDesc file, char[] str) {
    method Seek (line 446) | public static int Seek(Files_FileDesc file, int pos) {
    method Size (line 460) | public static int Size(Files_FileDesc file) {

FILE: src/java/Files_FileDesc.java
  class Files_FileDesc (line 9) | public class Files_FileDesc {
    method Files_FileDesc (line 12) | public Files_FileDesc() {}
    method copy (line 14) | public Files_FileDesc copy() {

FILE: src/java/In.java
  class In (line 9) | public final class In {
    method In (line 15) | private In() {}
    method Char (line 23) | static public void Char(char[] ch) {
    method String (line 37) | static public void String(char[] str) {
    method Real (line 59) | static public void Real(float[] x) {
    method Int (line 68) | static public void Int(int[] i) {

FILE: src/java/Math.java
  class Math (line 7) | public final class Math {
    method Math (line 12) | private Math() {}
    method sqrt (line 14) | public static float sqrt(float x) {
    method power (line 18) | public static float power(float x, float base) {
    method exp (line 22) | public static float exp(float x) {
    method ln (line 26) | public static float ln(float x) {
    method log (line 30) | public static float log(float x, float b) {
    method round (line 34) | public static float round(float x) {
    method sin (line 38) | public static float sin(float x) {
    method cos (line 42) | public static float cos(float x) {
    method tan (line 46) | public static float tan(float x) {
    method arcsin (line 50) | public static float arcsin(float x) {
    method arccos (line 54) | public static float arccos(float x) {
    method arctan (line 58) | public static float arctan(float x) {
    method arctan2 (line 62) | public static float arctan2(float x, float y) {
    method sinh (line 66) | public static float sinh(float x) {
    method cosh (line 70) | public static float cosh(float x) {
    method tanh (line 74) | public static float tanh(float x) {
    method arcsinh (line 78) | public static float arcsinh(float x) {
    method arccosh (line 83) | public static float arccosh(float x) {
    method arctanh (line 88) | public static float arctanh(float x) {

FILE: src/java/OberonRuntime.java
  class OberonRuntime (line 10) | public final class OberonRuntime {
    method OberonRuntime (line 13) | private OberonRuntime() {}
    method DIV (line 32) | public static int DIV(int x, int y) {
    method MOD (line 45) | public static int MOD(int x, int y) {
    method ASR (line 57) | public static int ASR(int x, int n) {
    method ROR (line 62) | public static int ROR(int x, int n) {
    method StrCmp (line 67) | public static int StrCmp(char[] s0, char[] s1) {
    method ARGS (line 85) | public static void ARGS(String[] args, int i, char[] out) {
    method ReadInt (line 100) | public static int ReadInt() {
    method WriteInt (line 121) | public static void WriteInt(int num) {
    method WriteReal (line 129) | public static void WriteReal(float num) {
    method WriteChar (line 133) | public static void WriteChar(int c) {
    method WriteLn (line 137) | public static void WriteLn() {
    method eot (line 141) | public static boolean eot() {

FILE: src/java/Os.java
  class Os (line 7) | public final class Os {
    method Os (line 12) | private Os() {}
    method toString (line 14) | private static String toString(char[] name) {
    method GetEnv (line 24) | public static void GetEnv(char[] out, char[] name) {
    method CurrentTime (line 42) | public static int CurrentTime() {
    method Exit (line 46) | public static void Exit(int status) {

FILE: src/java/Out.java
  class Out (line 10) | public final class Out {
    method Out (line 13) | private Out() {}
    method Char (line 24) | public static void Char(char ch) {
    method String (line 28) | public static void String(char[] str) {
    method Real (line 37) | public static void Real(float x, int n) {
    method Int (line 45) | public static void Int(int x, int n) {
    method Ln (line 53) | public static void Ln() {
    method Hex (line 57) | public static void Hex(int x) {

FILE: tests/TestRunner.java
  class TestRunner (line 8) | public class TestRunner {
    method main (line 12) | public static void main(String[] args) {
    method check (line 60) | private static void check(int[] tot, int[] successful, int[] failed,
    method testImports (line 70) | private static void testImports(int[] tot, int[] successful, int[] fai...
    method testCyclicImports (line 105) | private static void testCyclicImports(int[] tot, int[] successful,
    method testWithInputs (line 125) | private static void testWithInputs(int[] tot, int[] successful,
    method testTypeGuardExt (line 143) | private static void testTypeGuardExt(int[] tot, int[] successful,
    method assertEquals (line 149) | private static boolean assertEquals(String name, String expected, Stri...
    method compileAndRunArgs (line 160) | private static boolean compileAndRunArgs(String name, String input,
    method compile (line 196) | private static int compile(String name, boolean newSym) {
    method compileAndRunWithInput (line 202) | private static boolean compileAndRunWithInput(String name, String inpu...
    method compileAndRun (line 206) | private static boolean compileAndRun(String name) {
    method compileAndFail (line 210) | private static boolean compileAndFail(String name, int errors, String ...
    method getExpectedOutput (line 236) | private static String getExpectedOutput(String name) {
Condensed preview — 322 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (429K chars).
[
  {
    "path": "LICENSE.txt",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2017 Luca Boasso\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "Makefile",
    "chars": 1647,
    "preview": ".POSIX:\n.SUFFIXES:\n\nJAVA_SOURCES = src/java/Files_FileDesc.java src/java/Files.java \\\n               src/java/OberonRunt"
  },
  {
    "path": "README.md",
    "chars": 4744,
    "preview": "\n# Oberon-07 compiler\n\n`oberonc` is a single pass, self-hosting compiler for the\n[Oberon-07](https://en.wikipedia.org/wi"
  },
  {
    "path": "doc/TypeRules.md",
    "chars": 3827,
    "preview": "# Type rules\n\n## Same types [A]\nTwo variables *a* and *b* with types *Ta* and *Tb* are of the *same* type if\n1. *Ta* and"
  },
  {
    "path": "doc/oberon07.g",
    "chars": 3857,
    "preview": "// ANTLR v3 grammar\ngrammar oberon07;\n\n// LL(1) with few ambiguities resolved with the help of the symbol table\noptions "
  },
  {
    "path": "examples/GuessNumber.Mod",
    "chars": 1090,
    "preview": "MODULE GuessNumber;\n  IMPORT In, Out;\n  VAR\n    name: ARRAY 20 OF CHAR;\n    number, left, right, old: INTEGER;\n    choic"
  },
  {
    "path": "examples/Hello.Mod",
    "chars": 144,
    "preview": "MODULE Hello;\n  IMPORT Out; (* Import Out to print on the console *)\nBEGIN\n  Out.String(\"Hello 世界\");\n  Out.Ln (* print a"
  },
  {
    "path": "examples/Powers.Mod",
    "chars": 1433,
    "preview": "MODULE Powers;  (*Tabulate positive and negative powers of 2*)\n  IMPORT Out, Util;\n  CONST N = 32; M = 11;  (*M ~ N*log2"
  },
  {
    "path": "examples/PrimeNumbers.Mod",
    "chars": 1121,
    "preview": "MODULE PrimeNumbers;  (*Tabulate prime numbers*)\n  IMPORT Out, Util;\n\n  PROCEDURE Primes(n: INTEGER);\n    VAR i, k, m, x"
  },
  {
    "path": "examples/Util.Mod",
    "chars": 292,
    "preview": "MODULE Util;\n\n  PROCEDURE strToInt*(str: ARRAY OF CHAR): INTEGER;\n    VAR res, i, x: INTEGER;\n  BEGIN\n    res := 0;\n    "
  },
  {
    "path": "examples/fern/IFS.Mod",
    "chars": 1848,
    "preview": "MODULE  IFS;\n  IMPORT RandomNumbers, XYplane;\n\n  VAR\n    a1, b1, c1, d1, e1, f1, p1: REAL;   (* IFS parameters *)\n    a2"
  },
  {
    "path": "examples/fern/RandomNumbers.Mod",
    "chars": 521,
    "preview": "MODULE RandomNumbers;\n  IMPORT Math;\n\n  VAR Z: INTEGER;\n\n  PROCEDURE Uniform*(): REAL;\n    CONST\n      a = 16807;  m = 2"
  },
  {
    "path": "examples/fern/XYplane.Mod",
    "chars": 234,
    "preview": "DEFINITION XYplane;  \n  CONST erase = 0; draw = 1;\n  VAR X, Y, W, H: INTEGER;\n\n  PROCEDURE Open;\n  PROCEDURE Dot(x, y, m"
  },
  {
    "path": "examples/fern/java/XYplane.java",
    "chars": 2148,
    "preview": "import java.awt.Color;\nimport java.awt.Dimension;\nimport java.awt.Graphics;\nimport java.awt.Graphics2D;\nimport java.awt."
  },
  {
    "path": "make.bat",
    "chars": 1442,
    "preview": "@echo off\r\n\r\nset JAVA_SOURCES=src/java/Files_FileDesc.java src/java/Files.java src/java/OberonRuntime.java src/java/Os.j"
  },
  {
    "path": "src/ClassFormat.Mod",
    "chars": 23568,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/CpCache.Mod",
    "chars": 1664,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/Files.Mod",
    "chars": 1526,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/In.Mod",
    "chars": 804,
    "preview": "(*\n  Copyright 2019 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/Math.Mod",
    "chars": 2803,
    "preview": "(*\n  Copyright 2020 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/OJB.Mod",
    "chars": 22188,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/OJG.Mod",
    "chars": 84095,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/OJP.Mod",
    "chars": 52395,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Copyright (C)2013 Niklaus Wirth (NW), Juerg Gutknecht (JG),\n  Paul Reed (PR/PDR).\n  U"
  },
  {
    "path": "src/OJS.Mod",
    "chars": 14660,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Copyright (C)2013 Niklaus Wirth (NW), Juerg Gutknecht (JG),\n  Paul Reed (PR/PDR).\n  U"
  },
  {
    "path": "src/Opcodes.Mod",
    "chars": 2268,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/Os.Mod",
    "chars": 442,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/Out.Mod",
    "chars": 394,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/Strings.Mod",
    "chars": 9199,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/Files.java",
    "chars": 12689,
    "preview": "/*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/Files_FileDesc.java",
    "chars": 412,
    "preview": "/*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/In.java",
    "chars": 1402,
    "preview": "/*\n  Copyright 2019 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/Math.java",
    "chars": 2226,
    "preview": "/*\n  Copyright 2020 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/OberonRuntime.java",
    "chars": 3209,
    "preview": "/*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/Os.java",
    "chars": 1069,
    "preview": "/*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/java/Out.java",
    "chars": 1216,
    "preview": "/*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "src/oberonc.Mod",
    "chars": 1439,
    "preview": "(*\n  Copyright 2017 Luca Boasso.\n  Use of this source code is governed by a MIT\n  license that can be found in the LICEN"
  },
  {
    "path": "tests/TestRunner.java",
    "chars": 9818,
    "preview": "import java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.InputStre"
  },
  {
    "path": "tests/base/ArrayAssignment.Mod",
    "chars": 2009,
    "preview": "MODULE ArrayAssignment;\n  TYPE\n    arr2 = ARRAY 2 OF INTEGER;\n    multy = ARRAY 2, 4 OF INTEGER;\n    R = RECORD\n        "
  },
  {
    "path": "tests/base/ArrayAssignment.txt",
    "chars": 88,
    "preview": "   5   5   7   7   6   8   5   5  10  11  11  12  15  15  16  25  25  26  77  77  66  88"
  },
  {
    "path": "tests/base/ArrayConstantSize.Mod",
    "chars": 94,
    "preview": "MODULE ArrayConstantSize;\n  VAR N: INTEGER;\n      e: ARRAY 2*N OF CHAR;\nEND ArrayConstantSize."
  },
  {
    "path": "tests/base/Arrays2.Mod",
    "chars": 564,
    "preview": "MODULE Arrays2;\n  TYPE X = ARRAY 1 OF INTEGER;\n  VAR\n       a : X;\n       b : ARRAY 1 OF INTEGER;\n       (*i : ARRAY 3, "
  },
  {
    "path": "tests/base/Arrays2.txt",
    "chars": 20,
    "preview": "   2   8   9   3   3"
  },
  {
    "path": "tests/base/Arrays3.Mod",
    "chars": 1169,
    "preview": "MODULE Arrays3;\n  TYPE ARR = ARRAY 3 OF INTEGER;\n       ARR2 = ARRAY 3, 2 OF INTEGER;\n  VAR\n    x : ARRAY 3 OF INTEGER;\n"
  },
  {
    "path": "tests/base/Arrays3.txt",
    "chars": 20,
    "preview": "  88   1   8   8   9"
  },
  {
    "path": "tests/base/BitFunc.Mod",
    "chars": 1707,
    "preview": "MODULE BitFunc;\n  VAR x: ARRAY 18 OF BYTE;\n      i: INTEGER;\n      ch: CHAR;\n\n  PROCEDURE next(in: ARRAY OF BYTE; VAR i:"
  },
  {
    "path": "tests/base/BitFunc.txt",
    "chars": 43,
    "preview": "A  65\nД 1044\nа 1072\n€ 8364\n世 19990\n界 30028\n"
  },
  {
    "path": "tests/base/CaseChar0.Mod",
    "chars": 333,
    "preview": "MODULE CaseChar0;\n  CONST C = \"C\";\n  VAR i, a : CHAR;\nBEGIN\n  i := \"B\";\n  CASE i OF\n    \"A\"..\"C\", \"Z\":\n      a := \"1\"; \n"
  },
  {
    "path": "tests/base/CaseChar0.txt",
    "chars": 2,
    "preview": "15"
  },
  {
    "path": "tests/base/CaseNum0.Mod",
    "chars": 856,
    "preview": "MODULE CaseNum0;\n  VAR i, a : INTEGER;\n  \n  PROCEDURE P(i : INTEGER);\n     VAR a : INTEGER;\n  BEGIN\n    CASE i OF\n      "
  },
  {
    "path": "tests/base/CaseNum0.txt",
    "chars": 28,
    "preview": "   2   1   2   3  -1   8   9"
  },
  {
    "path": "tests/base/CaseNum1.Mod",
    "chars": 847,
    "preview": "MODULE CaseNum1;\n  TYPE ARR =  ARRAY 3 OF RECORD c: CHAR END;\n  VAR b: ARR;\n\n  PROCEDURE P(i: INTEGER);\n     VAR a: INTE"
  },
  {
    "path": "tests/base/CaseNum1.txt",
    "chars": 25,
    "preview": "   1   2   3   4   5  80P"
  },
  {
    "path": "tests/base/CaseRecord0.Mod",
    "chars": 820,
    "preview": "MODULE CaseRecord0;\n  TYPE\n    R = RECORD a: INTEGER END ;\n    R0 = RECORD (R) b: REAL END ;\n    R1 = RECORD (R) b: INTE"
  },
  {
    "path": "tests/base/CaseRecord0.txt",
    "chars": 16,
    "preview": "   1   8   9  10"
  },
  {
    "path": "tests/base/CaseRecord1.Mod",
    "chars": 423,
    "preview": "MODULE CaseRecord1;\n  TYPE \n    R = RECORD a: INTEGER END ;\n    R0 = RECORD (R) b: INTEGER END ;\n    R1 = RECORD (R) b: "
  },
  {
    "path": "tests/base/CaseRecord1.txt",
    "chars": 8,
    "preview": "   1  -1"
  },
  {
    "path": "tests/base/CaseRecord2.Mod",
    "chars": 838,
    "preview": "MODULE CaseRecord2;\n  TYPE \n    P = POINTER TO R;\n    P0 = POINTER TO R0;\n    P1 = POINTER TO R1;\n    P2 = POINTER TO R2"
  },
  {
    "path": "tests/base/CaseRecord2.txt",
    "chars": 8,
    "preview": "   3   1"
  },
  {
    "path": "tests/base/CaseRecord3.Mod",
    "chars": 725,
    "preview": "MODULE CaseRecord3;\n  TYPE\n    P = POINTER TO R;\n    P0 = POINTER TO R0;\n    P1 = POINTER TO R1;\n    P2 = POINTER TO R2;"
  },
  {
    "path": "tests/base/CaseRecord3.txt",
    "chars": 8,
    "preview": "  17   1"
  },
  {
    "path": "tests/base/CaseRecord4.Mod",
    "chars": 1021,
    "preview": "MODULE CaseRecord4;\n  TYPE\n    A = POINTER TO ADesc;\n    ADesc = RECORD\n      next: A\n    END;\n\n    B = POINTER TO RECOR"
  },
  {
    "path": "tests/base/CaseRecord4.txt",
    "chars": 2,
    "preview": "B\n"
  },
  {
    "path": "tests/base/CommutativeSwap.Mod",
    "chars": 194,
    "preview": "MODULE CommutativeSwap;\n  VAR i : INTEGER;       a : ARRAY 10 OF INTEGER;\nBEGIN\n  i := 5;\n  a[i+3] := 5;\n  WriteInt(1 - "
  },
  {
    "path": "tests/base/CommutativeSwap.txt",
    "chars": 8,
    "preview": "  11  -3"
  },
  {
    "path": "tests/base/ConstantFoldingAndLoadOp.Mod",
    "chars": 1147,
    "preview": "MODULE ConstantFoldingAndLoadOp;\n  CONST con = 100;\n  TYPE\n    V = RECORD\n          x : ARRAY 10 OF INTEGER;\n        END"
  },
  {
    "path": "tests/base/ConstantFoldingAndLoadOp.txt",
    "chars": 86,
    "preview": "  11   6   8 200   6   8 200\n   1   2  98   1   2  98 100 100\n   1   1   1   1   1   1"
  },
  {
    "path": "tests/base/DivMul.Mod",
    "chars": 1618,
    "preview": "MODULE DivMul;\n  CONST eight = 8;\n\n  PROCEDURE PrintDiv(a,b : INTEGER);\n  BEGIN\n    WriteInt(a);\n    WriteInt(b);\n    Wr"
  },
  {
    "path": "tests/base/DivMul.txt",
    "chars": 249,
    "preview": "   8   3   2   2\n   8  -3  -2   2\n  -8   3  -3   1\n  -8  -3   3   1\n   1   2   0   1\n   1  -2   0   1\n  -1   2  -1   1\n "
  },
  {
    "path": "tests/base/EmptyArrayAndRecord.Mod",
    "chars": 1258,
    "preview": "MODULE EmptyArrayAndRecord;\n  TYPE\n    EMPTY0 = RECORD  END;\n    EMPTY2 = RECORD e : RECORD END END;\n    EMPTY3 = RECORD"
  },
  {
    "path": "tests/base/EmptyArrayAndRecord.txt",
    "chars": 4,
    "preview": "  10"
  },
  {
    "path": "tests/base/ExtTypes.Mod",
    "chars": 158,
    "preview": "MODULE ExtTypes;\n  TYPE\n    pR0* = POINTER TO R0;\n    pR1* = POINTER TO R1;\n    R0 = RECORD x: INTEGER END;\n    R1 = REC"
  },
  {
    "path": "tests/base/ForwardPointerRef.Mod",
    "chars": 580,
    "preview": "MODULE ForwardPointerRef;\n  TYPE\n    P0 = POINTER TO R0;\n    R0 = RECORD i: INTEGER END;\n\n    R1 = RECORD\n           j: "
  },
  {
    "path": "tests/base/ForwardPointerRef.txt",
    "chars": 14,
    "preview": "   3   4   4C\n"
  },
  {
    "path": "tests/base/Fractions.Mod",
    "chars": 760,
    "preview": "MODULE Fractions;  (*NW  10.10.07;  Tabulate fractions 1/n*)\n  CONST Base = 10; N = 32;\n  \n  PROCEDURE Go;\n    VAR i, j,"
  },
  {
    "path": "tests/base/Fractions.txt",
    "chars": 246,
    "preview": "   2\t.5'0\n   3\t.3'3\n   4\t.25'0\n   5\t.2'0\n   6\t.1'6\n   7\t.1'428571\n   8\t.125'0\n   9\t.1'1\n  10\t.1'0\n  11\t.0'90\n  12\t.08'3\n"
  },
  {
    "path": "tests/base/FragileBaseClass.Mod",
    "chars": 3513,
    "preview": "MODULE FragileBaseClass;\n  TYPE\n    List = POINTER TO ListDesc;\n    Node = POINTER TO NodeDesc;\n    List2 = POINTER TO L"
  },
  {
    "path": "tests/base/FragileBaseClass.txt",
    "chars": 12,
    "preview": "   2   2   4"
  },
  {
    "path": "tests/base/MagicSquares.Mod",
    "chars": 664,
    "preview": "MODULE MagicSquares;   (*for Oberon-0 NW 25.1.2013*)\n\n  PROCEDURE Generate;  (*magic square of order 3, 5, 7, ... *)\n   "
  },
  {
    "path": "tests/base/MagicSquares.txt",
    "chars": 39,
    "preview": "   3   8   4\n   5   1   9\n   7   6   2\n"
  },
  {
    "path": "tests/base/OpenArrays.Mod",
    "chars": 785,
    "preview": "MODULE OpenArrays;\n  VAR  x : ARRAY 1 OF INTEGER;\n       y : ARRAY 2 OF INTEGER;\n\n  PROCEDURE P2(a : ARRAY OF INTEGER; V"
  },
  {
    "path": "tests/base/OpenArrays.txt",
    "chars": 12,
    "preview": "   1   2   2"
  },
  {
    "path": "tests/base/OpenArrays2.Mod",
    "chars": 340,
    "preview": "MODULE OpenArrays2;\n  TYPE ARR = ARRAY 3 OF INTEGER;\n  VAR\n    x : ARR;\n    i : INTEGER;\n  \n  PROCEDURE P1(VAR b : ARRAY"
  },
  {
    "path": "tests/base/OpenArrays2.txt",
    "chars": 16,
    "preview": "   8   8   8   8"
  },
  {
    "path": "tests/base/OpenArrays3.Mod",
    "chars": 977,
    "preview": "MODULE OpenArrays3;\n  TYPE ARR = ARRAY 3 OF INTEGER;\n       R = RECORD i : INTEGER END;\n  VAR\n    d : ARRAY 10 OF INTEGE"
  },
  {
    "path": "tests/base/OpenArrays3.txt",
    "chars": 25,
    "preview": "   1   2   3\n   4   5   6"
  },
  {
    "path": "tests/base/Out0.Mod",
    "chars": 1043,
    "preview": "MODULE Out0;\n  CONST con = 100;\n TYPE\n    V = RECORD  x : ARRAY 10 OF INTEGER END;\n    Hello = RECORD world : INTEGER; x"
  },
  {
    "path": "tests/base/Out0.txt",
    "chars": 48,
    "preview": "   8   9  10  11  12  13  14  15  16  17 200   1"
  },
  {
    "path": "tests/base/Out1.Mod",
    "chars": 454,
    "preview": "MODULE Out1;\n  CONST con = 100;\n  VAR           \n      a : ARRAY 4 OF INTEGER;\n      b : ARRAY 3 OF ARRAY 5 OF INTEGER;\n"
  },
  {
    "path": "tests/base/Out1.txt",
    "chars": 28,
    "preview": "   1   2   3   4   5   6   7"
  },
  {
    "path": "tests/base/Out2.Mod",
    "chars": 822,
    "preview": "MODULE Out2;\n  CONST con = 100;\n  TYPE \n       R0 = RECORD x, y : INTEGER END; \n       R1 = RECORD u : INTEGER; \n       "
  },
  {
    "path": "tests/base/Out2.txt",
    "chars": 36,
    "preview": "  10  11  12  13  14  15  16  17  18"
  },
  {
    "path": "tests/base/Out3.Mod",
    "chars": 963,
    "preview": "MODULE Out3;\n  CONST con = 100;\n  VAR           \n    i, j, k : INTEGER;\n    o, x : BOOLEAN;\n  PROCEDURE gdc(m, n : INTEG"
  },
  {
    "path": "tests/base/Out3.txt",
    "chars": 61,
    "preview": "   2   3   4   5   6   7   8\n   9  10  11  12  13  14  15   4"
  },
  {
    "path": "tests/base/Out4.Mod",
    "chars": 1461,
    "preview": "MODULE Out4;\n  TYPE\n    R0 = RECORD x, y : INTEGER END;\n    R1 = RECORD r0 : R0 END;\n    R8 = RECORD x : ARRAY 2 OF INTE"
  },
  {
    "path": "tests/base/Out4.txt",
    "chars": 52,
    "preview": "   1   4 100  10  11  12  13  14  20  21  22  23  24"
  },
  {
    "path": "tests/base/Out5.Mod",
    "chars": 412,
    "preview": "MODULE Out5;\n  TYPE A = RECORD x : INTEGER END;\n  VAR i: INTEGER;\n      v : ARRAY 20 OF INTEGER;\n      a : A;\nBEGIN \n  i"
  },
  {
    "path": "tests/base/Out5.txt",
    "chars": 32,
    "preview": "  10  11  12  20  21  22  23  24"
  },
  {
    "path": "tests/base/Out6.Mod",
    "chars": 498,
    "preview": "MODULE Out6;\n  TYPE \n    R0 = RECORD x : INTEGER END; \n    R1 = RECORD\n           r0 : RECORD x : INTEGER END \n         "
  },
  {
    "path": "tests/base/Out6.txt",
    "chars": 12,
    "preview": "  20  22  24"
  },
  {
    "path": "tests/base/OutTest.Mod",
    "chars": 447,
    "preview": "MODULE OutTest;\n  IMPORT Out;\n  VAR str: ARRAY 3 OF CHAR;\nBEGIN\n  Out.Int(10, 0); Out.Ln;\n  Out.String(\"Hello\");\n  Out.L"
  },
  {
    "path": "tests/base/OutTest.txt",
    "chars": 18,
    "preview": "10\nHello\nABC\nAB\nA\n"
  },
  {
    "path": "tests/base/Pattern1.Mod",
    "chars": 764,
    "preview": "MODULE Pattern1;\nIMPORT SYSTEM;\n  VAR ch: CHAR;\n      k,j: INTEGER;\n      x: REAL;\n      s: SET;\nBEGIN\n  ch := \"0\";\n  Wr"
  },
  {
    "path": "tests/base/Pattern1.txt",
    "chars": 82,
    "preview": "  48  10 -65536 -65537 65535 65536 65537 1.000000 273  56  56 458752  56 65528  56"
  },
  {
    "path": "tests/base/Pattern2a.Mod",
    "chars": 1559,
    "preview": "MODULE Pattern2a;\n  VAR i, j, k, n: INTEGER;\n      x, y: REAL;\nBEGIN\n  i := 1+7;\n  WriteInt(i);\n  j := 7;\n  i := 1+j;\n  "
  },
  {
    "path": "tests/base/Pattern2a.txt",
    "chars": 174,
    "preview": "   8   8   8   8   7   7  -6  -6   6   7  -7   6  21  21  21 112 112  21   2   2   2   2  -2  -2  -2  -2   2   2   0   0"
  },
  {
    "path": "tests/base/Pattern2b.Mod",
    "chars": 567,
    "preview": "MODULE Pattern2b;\nIMPORT SYSTEM;\n  VAR i, j: INTEGER;\n      x, y: REAL;\n      s, t: SET;\nBEGIN \n  x := 1.0+7.0;\n  WriteR"
  },
  {
    "path": "tests/base/Pattern2b.txt",
    "chars": 81,
    "preview": " 8.000000 8.000000 8.000000 8.000000 8.000000  -8  -8 -8.000000 -8.000000   5   5"
  },
  {
    "path": "tests/base/Pattern2c.Mod",
    "chars": 959,
    "preview": "MODULE Pattern2c;\nIMPORT SYSTEM;\n  VAR s, t, u: SET;\nBEGIN \n  s := {1, 5} + {2, 7, 4};\n  WriteInt(SYSTEM.VAL(INTEGER, s)"
  },
  {
    "path": "tests/base/Pattern2c.txt",
    "chars": 48,
    "preview": " 182   2  32 160 182   2  32 160 182   2  32 160"
  },
  {
    "path": "tests/base/Permutations.Mod",
    "chars": 585,
    "preview": "MODULE Permutations;  (*NW 22.1.2013 for Oberon-0*)\n  VAR m, n: INTEGER;\n    a: ARRAY 10 OF INTEGER;\n\n  PROCEDURE perm(k"
  },
  {
    "path": "tests/base/Permutations.txt",
    "chars": 78,
    "preview": "   3   7  11\n   7   3  11\n  11   7   3\n   7  11   3\n   3  11   7\n  11   3   7\n"
  },
  {
    "path": "tests/base/Powers.Mod",
    "chars": 1143,
    "preview": "MODULE Powers;  (*NW  25.1.2013 fo Oberon-07; Tabulate positive and negative powers of 2*)\n  CONST N = 32; M = 11;  (*M "
  },
  {
    "path": "tests/base/Powers.txt",
    "chars": 1085,
    "preview": "          2   1\t0.5\n          4   2\t0.25\n          8   3\t0.125\n         16   4\t0.0625\n         32   5\t0.03125\n         6"
  },
  {
    "path": "tests/base/PrimeNumbers.Mod",
    "chars": 926,
    "preview": "MODULE PrimeNumbers;  (*NW 6.9.07; Tabulate prime numbers; for Oberon-07  NW 25.1.2013*)\n  \n  VAR n: INTEGER;\n    p: ARR"
  },
  {
    "path": "tests/base/PrimeNumbers.txt",
    "chars": 79,
    "preview": "  20\n   5   7  11  13  17  19  23  29  31  37  41\n  43  47  53  59  61  67  71\n"
  },
  {
    "path": "tests/base/ProcComparisons.Mod",
    "chars": 428,
    "preview": "MODULE ProcComparisons;\n  VAR x: BOOLEAN;\n   \n  PROCEDURE P2(i : INTEGER): INTEGER;\n  BEGIN \n    RETURN 2\n  END P2;\n  \n "
  },
  {
    "path": "tests/base/ProcComparisons.txt",
    "chars": 12,
    "preview": "   1   2   3"
  },
  {
    "path": "tests/base/ProcType.Mod",
    "chars": 322,
    "preview": "MODULE ProcType;\n  TYPE\n    P = PROCEDURE(x: INTEGER);\n  VAR p : P;\n  \n  PROCEDURE I(x: INTEGER);\n  BEGIN WriteInt(x)\n  "
  },
  {
    "path": "tests/base/ProcType.txt",
    "chars": 8,
    "preview": "   8   9"
  },
  {
    "path": "tests/base/ProcVariables0.Mod",
    "chars": 1237,
    "preview": "MODULE ProcVariables0;\n  TYPE \n    MyFun = PROCEDURE() : INTEGER;\n    R = RECORD i : INTEGER END;\n    P = POINTER TO R;\n"
  },
  {
    "path": "tests/base/ProcVariables0.txt",
    "chars": 41,
    "preview": "   8   7   1   2   2\n   8   7   1   2   2"
  },
  {
    "path": "tests/base/ProcVariables1.Mod",
    "chars": 599,
    "preview": "MODULE ProcVariables1;\n  TYPE \n    R = RECORD i : INTEGER END;\n    MyFun = PROCEDURE(r : R) : INTEGER;\n  VAR\n    a : ARR"
  },
  {
    "path": "tests/base/ProcVariables1.txt",
    "chars": 17,
    "preview": "   2   3\n   2   3"
  },
  {
    "path": "tests/base/ProcVariables2.Mod",
    "chars": 1081,
    "preview": "MODULE ProcVariables2;\n  TYPE \n    MyFun = PROCEDURE(x : INTEGER) : INTEGER;\n    R = RECORD \n          i : INTEGER; \n   "
  },
  {
    "path": "tests/base/ProcVariables2.txt",
    "chars": 35,
    "preview": "   2   3\n   2   3\n   2   3\n   2   3"
  },
  {
    "path": "tests/base/ProcVariables3.Mod",
    "chars": 449,
    "preview": "MODULE ProcVariables3;\n  TYPE PT = PROCEDURE (m, n: INTEGER);\n       Object = POINTER TO RECORD w: INTEGER; p: PT END ;\n"
  },
  {
    "path": "tests/base/ProcVariables3.txt",
    "chars": 27,
    "preview": "   1   8\n   2   8\n   3   8\n"
  },
  {
    "path": "tests/base/ProcVariables4.Mod",
    "chars": 1074,
    "preview": "MODULE ProcVariables4;\n  TYPE P0 = PROCEDURE(VAR x : REAL) : INTEGER;\n       P1 = PROCEDURE(VAR x : REAL; VAR y : P0) : "
  },
  {
    "path": "tests/base/ProcVariables4.txt",
    "chars": 12,
    "preview": "   8   9  10"
  },
  {
    "path": "tests/base/ProcVariables5.Mod",
    "chars": 665,
    "preview": "MODULE ProcVariables5;\n  TYPE\n    T = PROCEDURE (x, y: INTEGER; z: BYTE);\n  VAR\n    v: T;\n    i,j: INTEGER;\n\n  PROCEDURE"
  },
  {
    "path": "tests/base/ProcVariables5.txt",
    "chars": 16,
    "preview": "   6   7   6   7"
  },
  {
    "path": "tests/base/ProcVariables6.Mod",
    "chars": 260,
    "preview": "MODULE ProcVariables6;\n  TYPE\n    Q = PROCEDURE (c: CHAR);\n  VAR\n    q: Q;\n\n  PROCEDURE P0(VAR j: Q);\n    PROCEDURE P1(c"
  },
  {
    "path": "tests/base/ProcVariables6.txt",
    "chars": 2,
    "preview": "a\n"
  },
  {
    "path": "tests/base/ProcVariables7.Mod",
    "chars": 375,
    "preview": "MODULE ProcVariables7;\n  TYPE\n    ProcA = PROCEDURE;\n    ProcB = PROCEDURE;\n\n  VAR\n    a: ProcA;\n\n  PROCEDURE A;\n  BEGIN"
  },
  {
    "path": "tests/base/ProcVariables7.txt",
    "chars": 3,
    "preview": "ABC"
  },
  {
    "path": "tests/base/RealExpressions.Mod",
    "chars": 956,
    "preview": "MODULE RealExpressions;\n  CONST NaN* = 0.0/0.0;\n  PROCEDURE One() : REAL;\n    RETURN 1.0\n  END One;\n  PROCEDURE Arithmet"
  },
  {
    "path": "tests/base/RealExpressions.txt",
    "chars": 60,
    "preview": " 6.000000\n 1.000000\n 6.000000\n 1.500000\n 23.500000\n   4   5\n"
  },
  {
    "path": "tests/base/RecordAndTypeExtension.Mod",
    "chars": 3459,
    "preview": "MODULE RecordAndTypeExtension;\n  TYPE\n    A = RECORD\n          x : INTEGER;\n          next : POINTER TO A;\n          nex"
  },
  {
    "path": "tests/base/RecordAndTypeExtension.txt",
    "chars": 149,
    "preview": "   0  10  11  11  13  99  11\n   8   9  10  10   8   7  15  15  10  10   1\n   1\n   1\n   1\n   1\n   1\n   0\n   1\n   1\n   0\n "
  },
  {
    "path": "tests/base/RecordAssignment.Mod",
    "chars": 1720,
    "preview": "MODULE RecordAssignment;\n  TYPE A = RECORD \n             x : INTEGER;\n             y : ARRAY 1, 2 OF INTEGER;\n          "
  },
  {
    "path": "tests/base/RecordAssignment.txt",
    "chars": 139,
    "preview": "   1  11   3  33\n  79  80 99.000000 100.000000\n 4.000000 44.000000   1  11   3  33  79  80 99.000000 100.000000  71  75 "
  },
  {
    "path": "tests/base/RecordAssignment2.Mod",
    "chars": 1074,
    "preview": "MODULE RecordAssignment2;\n  TYPE RP = POINTER TO R;\n    R = RECORD\n          i: INTEGER;\n          a: ARRAY 10 OF RP;\n  "
  },
  {
    "path": "tests/base/RecordAssignment2.txt",
    "chars": 78,
    "preview": "   8   8   8\n   8   8   8\n   8   8   8\n   8   8   8\n   8   8   8\n   8   8   6\n"
  },
  {
    "path": "tests/base/RecordParam.Mod",
    "chars": 870,
    "preview": "MODULE RecordParam;\n  TYPE RP = POINTER TO R;\n    R = RECORD\n          i: INTEGER;\n          p: RP;\n        END;\n  VAR\n "
  },
  {
    "path": "tests/base/RecordParam.txt",
    "chars": 27,
    "preview": "   8   8\n   8   0\n   8   0\n"
  },
  {
    "path": "tests/base/Samples0.Mod",
    "chars": 1134,
    "preview": "MODULE Samples0;\n  VAR n: INTEGER;\n  PROCEDURE Multiply;\n    VAR x, y, z: INTEGER;\n  BEGIN x := ReadInt(); y := ReadInt("
  },
  {
    "path": "tests/base/Samples0.txt",
    "chars": 13,
    "preview": "   0  80  40\n"
  },
  {
    "path": "tests/base/Samples1.Mod",
    "chars": 1134,
    "preview": "MODULE Samples1;\n  VAR n: INTEGER;\n  PROCEDURE Multiply;\n    VAR x, y, z: INTEGER;\n  BEGIN x := ReadInt(); y := ReadInt("
  },
  {
    "path": "tests/base/Samples1.txt",
    "chars": 17,
    "preview": "  80   5  16   0\n"
  },
  {
    "path": "tests/base/Samples2.Mod",
    "chars": 1134,
    "preview": "MODULE Samples2;\n  VAR n: INTEGER;\n  PROCEDURE Multiply;\n    VAR x, y, z: INTEGER;\n  BEGIN x := ReadInt(); y := ReadInt("
  },
  {
    "path": "tests/base/Samples2.txt",
    "chars": 13,
    "preview": "   0   0   2\n"
  },
  {
    "path": "tests/base/SetTest.Mod",
    "chars": 4246,
    "preview": "MODULE SetTest;\nIMPORT SYSTEM;\n  CONST\n        eight = 8;\n        c0 = {6..eight};\n        c1 = {0..4, 10, 30..31};\n    "
  },
  {
    "path": "tests/base/SetTest.txt",
    "chars": 841,
    "preview": "   3   4   5\n   3   4   5  20\n   3   4   5  20\n   3   4   5  20\n   1\n   3   4   5   6   7   8  20\n   5   6   7   8   9  "
  },
  {
    "path": "tests/base/Strings0.Mod",
    "chars": 993,
    "preview": "MODULE Strings0;\n  CONST baobab =  \"BAOBAB\";\n        C =  \"C\";\n  TYPE Arr = ARRAY 10 OF CHAR;\n  VAR a : CHAR;\n      b : "
  },
  {
    "path": "tests/base/Strings0.txt",
    "chars": 25,
    "preview": "BAOBAB$---\nCC\nBAOBAB$---\n"
  },
  {
    "path": "tests/base/Strings1.Mod",
    "chars": 3801,
    "preview": "MODULE Strings1;\n  CONST y = \"A\";\n  VAR a: ARRAY 10 OF CHAR;\n      x : CHAR;\n      s0 : ARRAY 5 OF ARRAY 5 OF CHAR;\n    "
  },
  {
    "path": "tests/base/Strings1.txt",
    "chars": 480,
    "preview": "l\n   1   3   5   8   9  13  14  15  16\n  -3  -4  -6  -7 -10 -11\n  -1  -2  -9 -10 -11 -12\n  -3  -4  -5  -8  -9 -12\n  -3  "
  },
  {
    "path": "tests/base/Strings2.Mod",
    "chars": 948,
    "preview": "MODULE Strings2;\n\nCONST\n  a = \"a\";\n  abcd = \"abcd\";\n\nVAR\n  s4: ARRAY 4 OF CHAR;\n  s5: ARRAY 5 OF CHAR;\n  f: ARRAY 20 OF "
  },
  {
    "path": "tests/base/Strings2.txt",
    "chars": 59,
    "preview": "a$$$\na$$$\nab$$\na$\na$\nab$\nabcd$\nabcd$\n$bcd$\n   2   3   2   3"
  },
  {
    "path": "tests/base/TestABS.Mod",
    "chars": 367,
    "preview": "MODULE TestABS;\n  CONST r = -1.2; i = -2;\n  VAR x, y : INTEGER;\n      a, b : REAL;\n      z : BYTE;\n   \nBEGIN\n  b := r;\n "
  },
  {
    "path": "tests/base/TestABS.txt",
    "chars": 44,
    "preview": " 1.200000 1.200000   2   2\n 7.300000   8  10"
  },
  {
    "path": "tests/base/TestAnonymousName.Mod",
    "chars": 221,
    "preview": "MODULE TestAnonymousName;\n  TYPE Anonymous0 = RECORD i :CHAR END;\n  \n  VAR x : RECORD i :INTEGER END;\n      y : Anonymou"
  },
  {
    "path": "tests/base/TestAnonymousName.txt",
    "chars": 1,
    "preview": "A"
  },
  {
    "path": "tests/base/TestAssert.Mod",
    "chars": 170,
    "preview": "MODULE TestAssert;\n  VAR x : INTEGER;\n   \nBEGIN\n  x := 2;\n  ASSERT(TRUE);\n  ASSERT(x > 0);\n  ASSERT((x > 0) & (x > 5) OR"
  },
  {
    "path": "tests/base/TestAssert.txt",
    "chars": 4,
    "preview": "   1"
  },
  {
    "path": "tests/base/TestAssignmentMix.Mod",
    "chars": 607,
    "preview": "MODULE TestAssignmentMix;\n  TYPE A = RECORD i : INTEGER END;\n       B = RECORD(A) j : REAL END;\n       TA = ARRAY 1 OF A"
  },
  {
    "path": "tests/base/TestAssignmentMix.txt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/base/TestByteType.Mod",
    "chars": 1885,
    "preview": "MODULE TestByteType;\n  VAR x : INTEGER;\n      y : BYTE;\n      r : RECORD y : BYTE END;\n      ar : ARRAY 1 OF BYTE;\n\n  PR"
  },
  {
    "path": "tests/base/TestByteType.txt",
    "chars": 152,
    "preview": " 256   1\n 255 129 130 129\n 255 129 130 129\n 255 129 130 129   1\n 256   1\n 255 129 130 129\n 255 129 130 129\n 255 129 130 "
  },
  {
    "path": "tests/base/TestCPS.Mod",
    "chars": 272,
    "preview": "MODULE TestCPS;\n\nTYPE F = PROCEDURE(i: INTEGER);\n\nPROCEDURE log(i : INTEGER);\nBEGIN \n  WriteInt(i)\nEND log;\n\nPROCEDURE t"
  },
  {
    "path": "tests/base/TestCPS.txt",
    "chars": 4,
    "preview": " 720"
  },
  {
    "path": "tests/base/TestCmdLineArgs.Mod",
    "chars": 1020,
    "preview": "MODULE TestCmdLineArgs;\n  VAR x: INTEGER;\n      y: ARRAY 10 OF CHAR;\n   \n  PROCEDURE Print(x: ARRAY OF CHAR);\n    VAR i,"
  },
  {
    "path": "tests/base/TestCmdLineArgs.txt",
    "chars": 31,
    "preview": "   0\n\n\n\nW\nW\nHello\nHello\nWorld!\n"
  },
  {
    "path": "tests/base/TestConstFunc.Mod",
    "chars": 3523,
    "preview": "MODULE TestConstFunc;\n  IMPORT SYSTEM;\n  CONST\n    negReal = -2.5;\n    posReal = 3.2;\n    posInt = 2;\n    a = \"A\";\n    i"
  },
  {
    "path": "tests/base/TestConstFunc.txt",
    "chars": 340,
    "preview": " 2.500000 2.500000\n   0   0\n   3   3\n 2.000000 2.000000\n  65  65\nAA\n   5   5   3   3   1   1\n 260 260\n  16  16\n 10737418"
  },
  {
    "path": "tests/base/TestCyclicImport00A.Mod",
    "chars": 88,
    "preview": "MODULE TestCyclicImport00;\n  TYPE R0* = RECORD k*: INTEGER END;\nEND TestCyclicImport00.\n"
  },
  {
    "path": "tests/base/TestCyclicImport00B.Mod",
    "chars": 174,
    "preview": "MODULE TestCyclicImport00;\n  IMPORT TestCyclicImport01;\n\n  TYPE R0* = RECORD k*: INTEGER END;\n  VAR\n     r1: TestCyclicI"
  },
  {
    "path": "tests/base/TestCyclicImport01A.Mod",
    "chars": 174,
    "preview": "MODULE TestCyclicImport01;\n  IMPORT TestCyclicImport00;\n\n  TYPE R1* = RECORD x*: INTEGER END;\n  VAR\n     r0: TestCyclicI"
  },
  {
    "path": "tests/base/TestCyclicImport01B.Mod",
    "chars": 193,
    "preview": "MODULE TestCyclicImport01;\n  IMPORT TestCyclicImport00;\n\n  TYPE R1* = RECORD x*: INTEGER END;\n  VAR\n     r0*: TestCyclic"
  },
  {
    "path": "tests/base/TestCyclicImport10A.Mod",
    "chars": 89,
    "preview": "MODULE TestCyclicImport10;\n  TYPE R0* = RECORD k*: INTEGER END;\n\nEND TestCyclicImport10.\n"
  },
  {
    "path": "tests/base/TestCyclicImport10B.Mod",
    "chars": 175,
    "preview": "MODULE TestCyclicImport10;\n  IMPORT TestCyclicImport11;\n\n  TYPE R0* = RECORD k*: INTEGER END;\n  VAR\n     j: INTEGER;\nBEG"
  },
  {
    "path": "tests/base/TestCyclicImport11.Mod",
    "chars": 138,
    "preview": "MODULE TestCyclicImport11;\n  IMPORT TestCyclicImport12;\n  VAR\n     i*: INTEGER;\nBEGIN\n  i := TestCyclicImport12.w\nEND Te"
  },
  {
    "path": "tests/base/TestCyclicImport12.Mod",
    "chars": 164,
    "preview": "MODULE TestCyclicImport12;\n  IMPORT TestCyclicImport10;\n  VAR\n     r0: TestCyclicImport10.R0;\n     w*: INTEGER;\nBEGIN\n  "
  },
  {
    "path": "tests/base/TestEqualSignature00.Mod",
    "chars": 1309,
    "preview": "MODULE TestEqualSignature00;\n  TYPE\n    A = RECORD i : INTEGER END;\n    B = RECORD(A) k : CHAR END;\n    P0 = PROCEDURE(x"
  },
  {
    "path": "tests/base/TestEqualSignature00.txt",
    "chars": 6,
    "preview": "ABCDEF"
  },
  {
    "path": "tests/base/TestExprVarPar.Mod",
    "chars": 116,
    "preview": "MODULE TestExprVarPar;\n  VAR a: INTEGER;\n\n  PROCEDURE G(VAR i: INTEGER); END G;\n\nBEGIN\n  G(a+1)\nEND TestExprVarPar.\n"
  },
  {
    "path": "tests/base/TestFor.Mod",
    "chars": 1702,
    "preview": "MODULE TestFor;\n  CONST minus1 = -1;\n  TYPE R0 = RECORD x, y : INTEGER END;\n       ARR = ARRAY 5 OF INTEGER;\n  VAR  i, x"
  },
  {
    "path": "tests/base/TestFor.txt",
    "chars": 248,
    "preview": "   0   1   2   3   4   5   6   7   8   8   7   6   5   4   3   2   1   0\n   0   1   2   3   4   5\n   7   8   9\n   0   1 "
  },
  {
    "path": "tests/base/TestFor1.Mod",
    "chars": 813,
    "preview": "MODULE TestFor1;\n  VAR v, beg, end : INTEGER;\n  \n  PROCEDURE For0;\n    VAR v, beg, end : INTEGER;\n  BEGIN\n    beg := 3;\n"
  },
  {
    "path": "tests/base/TestFor1.txt",
    "chars": 154,
    "preview": "   3   5   7   9\n   0   1   2   3\n   0   2\n   3   4   5   6   7   8   9  10\n   0   1   2   3   4   5   6   7   8   9  10"
  },
  {
    "path": "tests/base/TestFunction0.Mod",
    "chars": 302,
    "preview": "MODULE TestFunction0;\n  VAR x : INTEGER;\n   \n  PROCEDURE P1(i : INTEGER): INTEGER;\n    PROCEDURE P2(j, z : INTEGER) : IN"
  },
  {
    "path": "tests/base/TestFunction0.txt",
    "chars": 4,
    "preview": "   4"
  },
  {
    "path": "tests/base/TestINC0.Mod",
    "chars": 3244,
    "preview": "MODULE TestINC0;\n  VAR xg : INTEGER;\n      yg : BYTE;\n      ig : ARRAY 2 OF INTEGER; \n      jg : RECORD x : INTEGER END;"
  },
  {
    "path": "tests/base/TestINC0.txt",
    "chars": 179,
    "preview": "   2 132   4 255 125 129   1\n   2 132   4 255 125 129   1\n   3   4   4   2   4   3   5   6   7   7  11   9  10   5  99 1"
  },
  {
    "path": "tests/base/TestINC1.Mod",
    "chars": 1077,
    "preview": "MODULE TestINC1;\n  VAR k: INTEGER;\n      i: ARRAY 2 OF RECORD b : BYTE END;\n      j: ARRAY 20 OF BYTE;\n      x: BYTE;\n  "
  },
  {
    "path": "tests/base/TestINC1.txt",
    "chars": 49,
    "preview": "   7   1   8  15  22  25\n   7   1   8  15  22  25"
  },
  {
    "path": "tests/base/TestINCLAndEXCL.Mod",
    "chars": 2120,
    "preview": "MODULE TestINCLAndEXCL;\n  CONST K = {1, 2, 3};\n  VAR y : SET;\n      j,f : INTEGER;\n      b : ARRAY 2 OF SET;\n      r: RE"
  },
  {
    "path": "tests/base/TestINCLAndEXCL.txt",
    "chars": 191,
    "preview": "   1   3\n   1   3\n   1   3\n   1   3\n   1   2   3   8\n   2   3   8\n   1   3\n   1   3\n   1   2\n   1   2   3\n   1   3   8\n "
  },
  {
    "path": "tests/base/TestImport00.Mod",
    "chars": 397,
    "preview": "MODULE TestImport00;\nCONST N* = 100;\nTYPE\n  Ptr* = POINTER TO Rec;\n  Rec* = RECORD n*: INTEGER; p: Ptr END ;\n  Rec2 = RE"
  },
  {
    "path": "tests/base/TestImport00.txt",
    "chars": 0,
    "preview": ""
  }
]

// ... and 122 more files (download for full content)

About this extraction

This page contains the full source code of the lboasso/oberonc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 322 files (386.4 KB), approximately 137.0k tokens, and a symbol index with 113 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!