M-Code

From Esolang
Jump to navigation Jump to search

M-Code is a Turing-complete esoteric programming language designed by User:Madk.

M-Code takes a lot of inspiration from 6502 assembly, brainfuck, and then it tosses a handful of features of its own. Every character in a source file represents a single value, and numerical values can be specified inside [] brackets. There are three variations: 8 bit M-Code, 16 bit M-Code, and 32 bit M-Code. The higher bit variations were introduced to allow a larger tape size than a mere 256 bytes. All values in each bit mode are that number of bits. 32 bit values are signed.


Key Features

  • Source code is stored in an accessible memory bank, making self-modification important to many functions.
  • 3 value register
  • File access
  • Relatively robust arithmetic and logic operands


Instruction Set

Commands

These are the single-character commands recognized by the interpreter. All commands and symbols use 1 value, arguments must immediately follow commands. Program execution starts at memory address 0.

Register handling for the A, B, C registers. > puts a value into the register and < takes it out.
> < 
} { 
) ( 
arguments: [memory address]

Store a literal value in the A, B, C registers
4 5 6
arguments: [number]

Handle the string in memory by printing it, getting it from input, or swapping it with the secondary (inactive) memory string
; \ ~

Clear the string in memory
s

Read characters from the memory string and put them in the A, B, C registers
^ ` '
arguments: [position in memory string]

Treat the memory string as a number and put it in the A, B, C register.
U V W

Write characters to the end of the memory string from the A, B, C registers
: . ,

Output the numerical value onto the end of the memory string
1 2 3

Store the length in characters of the string in memory to register A, B, C
7 8 9
 
Copy a byte, swap two bytes in memory from one place to another
# S
arguments: [origin memory address] [second memory address]

Increment, decrement a byte in memory
i d

increment, decrement the value in the A, B, C registers
o n
u t
f e

Jump unconditionally
j
arguments: [jump to address]

Jump if A > B
?
arguments: [jump to address]

Jump if A < B
!
arguments: [jump to address]

Jump if A = B
=
arguments: [jump to address]

Jump if A != B
N
arguments: [jump to address]

Swap A and B registers
x

Swap B and C registers
y

Swap C and A registers
z

Set A, B, C register to 0
a b c

Perform a logical NOT operation on registers A, B, C
A B C

Copy the value in the A register into both B and C
Y 

Shift a value in memory left, right by one bit
L R
arguments: [memory address]

Perform an operation on A and B and place the result in C
Add:			+
Subtract:		-
Divide: 		/
Multiply:		*
Modulo:		%
Raise to power:	v
Bit shift left:	l
Bit shift right:	r
Mean:			m
Logical OR:		O
Logical AND:		&
Logical XOR:		X 

Creates a new external data file if one doesn't already exist. It opens it into memory if it does.
F
arguments: [file #] 

Write a value from a program memory address into a file
w
arguments: [write from memory address][write to file address]

Read a byte from a file and store it in program memory
p
arguments: [write to memory address][read from file address]

Read a file's entire contents and store it all in program memory starting at a given location
P
arguments: [write to memory address]

Print memory string to console without a newline (with the exception of the inclusion of an actual newline character)
H

Print a newline to the console
I

Delay program execution for some number of milliseconds
D
arguments: [milliseconds]

Output a number, character to the end of the memory string from a memory address
0 @
arguments: [memory address]

Kill program
_

Non-runtime parser instructions

These are interpreter instructions. They aren't processed in memory at runtime, but aid in the writing of code and pre-runtime memory management.

Comment
// (...) // 

Reference a label and store its position as a value in this memory address
|(...)|
 
Define a label mid-line
|$(...)|

Define a value numerically instead of an ASCII character. For example, [65] and "A" and also [35] and "#" would be interchangeable.
[(...)] 

Set string memory contents before runtime - first instance sets first string, second instance sets second string.
$"(...)

Set the current location in memory that the following data will start being written at. It must be numerical.
$(...)

Define a label based on this line's position in the source file. 
It cannot start with a digit (0-9), "$", or a quote ("). It cannot contain "=". Labels are case-insensitive.
$(...) 

Simultaneously define a label and the contents of the immediate memory address. 
The assignment should be formatted no differently than typical program data. Example: $pound = # OR $pound = [35]
$(...)=(...)

Count whitespace as valid characters in this line of code, with the exception of preceeding and following space. 
(...) $

Self-reference this address in memory - if [$$] were at the very beginning of a program, 
it would be equivalent to [0]. At $42, [$$] would be [42].
[$$]

Put a $ character where the parser would normally see it as a non-runtime command 
(at the very beginning or very end of a line, excluding whitespace)
[$]

Put a [ character where the parser would normally see it as a non-runtime command
[[]

Put a | character where the parser would normally see it as a non-runtime command
[|]

Examples

Hello, world!

// Print "Hello, world!" to the console. //

4|data end|	// Store the location of the last character, "!", in register A. //
j|main|         // Jump to the main loop start // 


$ main		// This is a label signifying the beginning of the main loop. //
}|$pos||data|	// Push "H" into register B from the start of the "Hello, world!" string. $pos is a label definition. //
.		// Add "H" from B to the end of the memory string. //
i|pos|		// Increment the number in }|data| by one, so next loop it'll push "e", then "l", (...) //
}|pos|		// Push that same number into B to get ready for a comparison. //
?|main|         // Jump back to |main| to create a loop if the value in register A is > B to know if we've reached the end of "Hello, world!". //

;		// After the above loops has finished, ; prints the accumulated string to the console. //
_		// Terminate program execution. //

$data		// Label the location of the string data in memory //
Hello,[32]world!// Whitespace is not recognized by the parser, [32] is the ASCII code for a space. //
$data end	// This is a label to provide the ending point of the "Hello, world!" data. //

Much less elegant Hello, world!

$"Hello, world! I'm a big fat cheater.
;_

99 bottles of beer

// This program prints the lyrics to "99 bottles of beer". 
   It exceeds the program memory space offered by 8-bit
   M-Code and therefore is incompatible. //

d[183]>[183]bu=G16[224]4[15]j[184]6[209]4[21]j[184]>[183]16[224] 
4[30]j[184]4.:;s6[241]4)j[184]>[183]n1}[1]=76[224]4=j[184]6[273] 
4=j[184]6[209]4Cj[184];sj[0]0[183]6[273]4Oj[184]6[209]4Uj[184]0[183] 
6[273]4]j[184]4.:;s6[289]4hj[184]6[320]4nj[184]6[224]4tj[184]6[209] 
4zj[184];s6[320]4[130]j[184]6[224]4[136]j[184]6[209]4[142]j[184] 
6[320]4[148]j[184]6[224]4[154]j[184]4.:;s6[328]4[165]j[184]4c16[224] 
4[174]j[184]6[209]4[180]j[184];s_d([201]<[202]#[201][192]>[0]b=[203] 
i[201]:j[188][0][0]#[202][207]j[0]_[32]on[32]the[32]wall.[32][0][32]
bottles[32]of[32]beer[0]Take[32]one[32]down,[32]pass[32]it[32]around
.[32][0][32]bottle[32]of[32]beer[0]Take[32]it[32]down,[32]pass[32]it
[32]around.[32][0]No[32]more[0]Go[32]to[32]the[32]store[32]and[32]bu
y[32]some[32]more.[32]

Fibonacci sequence

// Calculate the first several numbers of the Fibonacci sequence. //

// A & B are added together each loop to calculate C, a member of the sequence. 
   A then becomes B and B becomes C and the next number is calculated next cycle. //

5[1]			// The A and C registers start at 0. The B register needs to be set to 1. //

$ loop start		// This label indicates the main loop initiation. //
3;s			// Output the value in C to the console. //
< |memoryA| { |memoryB|	// Store the values in the A and B registers into memory so we can do an
			   end-of-loop check. //
d |sequence|		// |sequence| is the label dictating how many numbers of the sequence to show.
			   Here it is decremented by 1 each loop. //
> |sequence| b		// Store |sequence| in the A register and 0 in B for comparison. //
= |program end|		// If A=B (sequence=0) jump to the program's end. //
> |memoryB| } |memoryA|	// Restore the previous states of A and B, but also swap them in the process. //
y			// Swap the contents of the B and C registers. //
+			// Add A and B and put the result in C. //
j |loop start|		// Go back to the loop start label and do it all again. // 

$ memoryA		// Used to temporarily store the A register during the loop. //
[0]
$ memoryB		// Used to store the B register. //
[0]
$ sequence		// Iterate the loop |sequence| number of times. The 15th member of the //
[14]			// sequence exceeds 255, so only the first 14 are calculated. //

$ program end		// The loop jumps here when it has completed |sequence| cycles. //
_			// Program termination command //

Collatz sequence

// Calculate a Collatz sequence from a starting amount. //
// This program is _not_ intended to be run in the 8-bit version of M-Code. //

$"Input a number to determine its Collatz sequence.
$"Number of cycles:
;s;\Us;

$loop
<|var|1;s5[2]%5[0]z
N|odd|
$even
>|var|5[2]/
j|thru|
$odd
>|var|5[3]*f
$thru
i|cycles|z5[1]=|end|
j|loop|

$end
1;~4[32]:>|cycles|1;
_
$var=0
$cycles
[0]

Output ASCII table

// Prints ASCII table to the console //

4[255]5[0]6[32].,,2;suN[6]_

Quine

In order to keep readable ASCII characters for memory address representations, the program memory storage starts at 35.

// This program outputs its source to the console. 
   To maintain readable characters, the first symbol 
   is located at $35 within program memory. //

$35
>#y}$N/5#.j1:yi$>$5;!#;_

Formatted:

$35

>[35]
}[36]
N[45]
5[35]
.
j[47]
:
i[36]
>[36]
5[59]
![35]

;_

Cat

\;_

Brainfuck Interpreter

While this interpreter is designed with 8-bit M-Code in mind and has a maximum code size of 256 commands and a tape size of only 32, these values can be expanded into greater than 60,000 or over a million commands and tape by changing the wraparound code at the end and using the 16-bit or 32-bit M-Code interpreters, respectively. It shows through simulation that M-Code, if given infinite memory, is Turing-complete.

// Interpret a brainfuck script. //

$">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.#

$"Please wait, this may take a while :P
~;s~

j |main loop|

	$ >
	 [62]
	$ <
	 [60]
	$ +
	 [43]
	$ -
	 [45]
	$ .
	 [46]
	$ ,
	 [44]
	$ [
	 [91]
	$ ]
	 [93]
	$ #
	 [35]

$ tape
[223]

$ main loop 

^
$ code
[1] 

$ check >
} |>|
N |check <|
i |tape|
j |end|

$ check <
} |<|
N |check +|
d |tape|
j |end| 

$ check +
} |+|
N |check -|
# |tape||+arg|
i
$ +arg
[ 0 ]
j |end|

$ check -
} |-|
N |check .|
# |tape||-arg|
d
$ -arg
[ 0 ]
j |end|

$ check .
} |.|
N |check ,|
# |tape||.arg|
) 
$ .arg
[ 0 ]
~s,H~
j |end|

$ check ,
} |,|
N |check [|
~\'[1]~
# |tape||,arg|
(
$ ,arg
[ 0 ]
j |end|

$ check [
} |[|
N |check ]|
# |tape||[mod|
}
$ [mod
[ 0 ]
4 [0]
N |end|
4 [1]
< |nest|
$ [start
4 [0]
} |nest|
= |end|
i |code|
# |code||[char|
^
$ [char
[ 0 ]
} |[|
N |[2|
i |nest|
$ [2
} |]|
N |[start|
d |nest|
j |[start|

$ check ]
} |]|
N |check #|
4 [1]
< |nest|
$ ]start
4 [0]
} |nest|
d |code|
= |end|
# |code||]char|
^
$ ]char
[ 0 ]
} |[|
N |]2|
d |nest|
$ ]2
} |]|
N |]start|
i |nest|
j |]start|

$ check #
} |#|
N |end|
I_ 

$end
i |code|
> |code|
5 [255]
N |nc|
# |const1||code|
$ nc
5 [221]
N |main loop|
# |const2||code|
j |main loop|

$ const1
[222]
$ const2
[254]

$nest
[0]

External resources