Migol 11

From Esolang
Jump to navigation Jump to search

Migol 11 is an imperative assembly-like programming designed by User:MigoMipo. It is almost backwards-compatible with Migol 09, but adds an advanced asynchronous I/O system with interrupts.

Basics

A Migol 11 program consists of multiple statements, which generally contains expressions. The statements are generally executed in sequence, unless a branch or interrupt occurs. The memory is an array of 32-bit signed integers. The elements in the array are addressed with 32-bit integers, and all non-negative integers are valid memory addresses. In addition to the memory, there are 8 additional special registers, which are used to handle control flow and the I/O interrupt system.

Integers are the only datatype in Migol 11, and the type of all values in the programs. References refer to a register or memory position. Values can be read from and assigned to them. All integers are valid as references, where they refer to a place in memory (negative addresses result in a runtime error). There are some non-integer references, which refer to special registers. These references can not be used as integer values.

Assignment statement

An assignment statement assigns a value to a reference. The syntax is {ref}<{val}, where {ref} is a reference and {val} is an value. As an example,

5<3

assigns the value 3 to position 5 in memory.

Dereferencing expression

Dereferencing expressions, [{ref}], are used to return the value of a register or memory position referred to by a reference. In the case of integers, [5] evaluates to the current value in memory position 5.

3<[5]

Takes the value in position 5 in memory and assigns it to position 3 in memory.

[5]<4

reads the value in position 5 in memory, and uses the result as a reference address for the assignment statement. In this case, 4 is stored in the memory position pointed to by position 5 in memory.

As a dereferencing expression yields an integer, which can be used as a reference, dereferencing expression can be nested, like

9<[[4]]

Modifying statement

Modifying statements take a reference, read the value in it and write back a modified value, generally with values as operands for the operations. The syntax is {ref}<${op}{val}, where {op} is an operator. For example,

5<$+3

adds 3 to the current value in memory position 5.

There are several operators defined in Migol 11.

Operator Description
+ Addition
- Subtraction
* Multiplication
/ Integer division
% Modulo
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
<< Left bitshift
>> Signed right bitshift
>>> Unsigned right bitshift
<<_ Left bit rotation
>>_ Right bit rotation
< Less than operand
> Greater than operand
= Equal to operand
<= Less than or equal to operand
>= Greater than or equal to operand
<> Not equal to operand

The comparison operators set the value in the reference to 0 if false, 1 if true.

The only backwards-incompatibility with Migol 09 is the lack of a bitwise NOT operation, use <$^-1 instead.

Special registers

The special registers are separate from the main memory. They are accessed with special assignment expressions. These are not integers and can not therefore be used as values directly. Their values can of course be read with the deferencing expressions. Several of the special registers cause side effects related to I/O operations or branching when read or written. There are 8 special registers in Migol 11.

The branch register is named #, and it is used to manage control flow.

All statements in Migol can be addressed with integers. The first statement in the program has address 1, the second has address 2 and so on. When # is read, it returns the address of the current statement. When # is written to, the program branches to the statement that is addressed by the written value.

#<3

jumps to statement 3 in the program.

#<$+3

also works, it reads the current value first, adds 3 to it, and branches. It jumps to the third statement after the executed statement.

The other special registers will be explained in the I/O section.

Conditionals

All statements can be made conditional by appending a conditional to it. The conditional consists of a comparison operator and a single value. When the statement is executed, the conditional is evaluated by comparing the value with 0. The statement is executed if the conditional evaluates to true. The syntax is {statement}?{op}{val}, where {op} is a comparison operator and {val} is the value that will be compared.

There are 6 comparison operators available:

Operator Description
< Less than 0
> Greater than 0
= Equal to 0
<= Less than 0
>= Greater than 0
<> Not equal to 0

For example,

4<$+3?=[2]

increases the value in memory position 4 by 3 only if the value in memory position 2 is exactly 0.

Sequence assignment statements

Several assignment and modifying statements can be chained to a single statement, if the destination reference is the same. The syntax is {ref}{op-1}{op-2}...{op-n}, where the ops can be assignment or modification operations.

5<3<$+8<$-[4]

sets memory position 5 to 3 + 8 - [4].

For each operation, the destination reference expression is re-evaluated, and the result of each operation is always written directly to the destination. There is no defined order of evaluation of the source and destination expressions.

Syntactical extras

Statements

Several statements can be put in a single line, by separating each statement with ,.

5<4,3<[[5]]

is an example.

NOP statement

The NOP statement is _. It works just like other statements, but nothing happens when it is executed.

Character values

Having to remember ASCII values all the time can be difficult. The expression '{char} returns the integer ASCII value of the character. All characters except newline are valid.

Comments

// starts a line comment.

Labels

GOTO-style labels are often useful, as it it tedious to count statements and rewrite branch statements if the code changes.

Appending :labelname to a statement adds a label containing the position of that statement. All lower-case characters between a and z are valid in label names. If the statement is conditional, the label must come after the conditional.

3<4:label

creates a new label named "label", containing the position of the statement 3<4.

Labels can then be used as values.

#<label

branches to the the position stored in "label".

I/O system

Migol 11 uses a asynchronous I/O system, the program continues as the I/O operations are performed. When the operation is complete, an I/O interrupt is triggered. 7 additional special registers are used to manage interrupt handling and I/O operations.

A Migol program can be in two different states when executed. The first is standard mode. In standard mode, the program is ready to handle interrupts. When an interrupt is triggered, the program branches to the interrupt handler and changes to interrupt handling mode. In interrupt handling mode, the program can fetch the result of a interrupt but not handle any additional interrupts until the program exits interrupt handling mode.

Initialization of I/O operations

I/O operations are initiated by writing to the execution register, !. The execution register expects an integer pointer to a in-memory data structure, which contains the arguments of the I/O operations. The first value in the data structure (the one directly pointed to by the pointer) is the I/O function id, and it is used to determine which I/O function to run. The other values in the data structure are specific for each I/O function, and they are used to pass arguments and store the results of the operation when it has completed.

As an example, an I/O stream read operation:

60<10, 61<1, 62<120, 63<3000
// 60 is the position of the data structure
// 10 is the ID of the read operation (10)
// The rest are arguments:
// 61 contains the stream handle, in this case 1 (STDIN)
// 62 contains the position of the memory buffer
// 63 contains the size of the memory buffer
// 64 will contain the error number when the operation is complete
// 65 will contain the number of bytes read, or -1 if it failed
!<60 // Start I/O operation
// The program keeps on until the operation is complete

When an I/O function is complete, it adds a pointer to the result data structure to a queue of I/O results. In most cases, the result data structure is the same as the argument structure with a few values modified.

The program is expected to wait for all pending I/O operations to complete before terminating, and what happens with pending I/O operations when the program terminates is undefined.

Handling I/O interrupts

When the program is in standard mode, it checks the I/O queue after each executed statement. If a result is available, the program enters interrupt handling mode, and branches to the interrupt handler. The interrupt handler address is accessed and changed with the !# register.

In interrupt handling mode, the interrupt handler can get interrupt data from 2 registers.

*! : In interrupt handling mode, this register contains the pointer to the interrupt result data structure.

*# : In interrupt handling mode, this register contains the address of the statement after the statement where the program was interrupted. This is used to return to the same place in the program after the interrupt has been handled.

Both registers return -1 in standard mode. They are also both read-only, and write operations are ignored.

To get out of the interrupt handler, interrupt handling mode and back to standard mode, the #! register is used. It works just like the branch register, except the program leaves interrupt handling mode when branching. #! is often used with *#:

#!<[*#]

leaves the interrupt handler and resumes standard mode execution where the program was before the interrupt.

Interrupt wait

The \ register makes the program block until an interrupt is available, when written to (any value works). Once an interrupt result is available, the program handles that interrupt. The *# will contain the address of the statement after the wait statement.

Easy console I/O

As a legacy feature from Migol 09, simple non-interrupt-based console I/O functions are also available.

Console output can be done through the output statements. There are two types of output statements, {val}>, which outputs the value as an ASCII character, and {val}>-, which converts the integer to a string and outputs it.

Console input can be done through the console input register, @. It ignores write operations, but when read from, it blocks until it can read a byte from stdin and returns it. It is a bad idea to combine @ with interrupt, as it blocks until a byte has been found, therefore new interrupts can't be handled until the statement has completed.

List of standard I/O functions

See Migol 11/IO Functions.

Examples

Interrupt-based "Hello, World!"

100<'H,101<'e,102<'l,103<'l,104<'o,105<',,106<' ,107<'W,108<'o,109<'r,110<'l,111<'d,112<'! 
// Output buffer
!#<handler
 
2<0 // I/O completion flag
20<11 // Write operation (11)
21<2 // I/O handle (2 = STDOUT)
22<100 // Buffer address
23<13 // Buffer length
 
!<20 // Start output operation
\<1?<>[2] // Block until operation has completed
#<$-1?<>[2]
#<30000 // End program
 
2<1:handler // sets I/O completion flag
#!<[*#] // ..and returns. No error numbers are checked


Implementations