A programming language that compiles to human-readable mips assembly.
Made with Typescript, Assembly
web demo: https://pfg.pw/masc
generate mips from human code
example:
\\.text
\\j main
fn gcd(a: i32, b: i32) i32 {
if b == 0 {return a;}
return gcd(b, a%b);
}
\\main:
print_int(gcd(25, 15));
// ----- //
inline fn print_int(value: i32) void {
$v0 = 1;
$a0 = value;
syscall();
}
inline fn syscall() void {
\\syscall
!clear $call;
}↓
.text # .text
j main # j main
# ====================
# jal call_gcd
# args:
# $a0: a - i32
# $a1: b - i32
# return:
# $v0: i32
# ====================
call_gcd: # fn gcd(a: i32, b: i32) i32{
# save used s registers to stack
subiu $sp, $sp, 4 # $sp = &$sp[-1]
sw $ra, 0($sp) # $sp[0] = $ra
#
move $t0 $a0 # a = $a0
move $t1 $a1 # b = $a1
# body
bnez $t1, if_end # if b == 0 {
move $v0 $t0 # . a
j deinit_gcd # return ^;
if_end: # }
move $a0 $t1 # . b
rem $a1, $t0 $t1 # | a % b
jal call_gcd # | gcd(^, ^^^^^)
move $v0, $v0 # return$v0 = ^^^^^^^^^^^^^
#
deinit_gcd: # cleanup:
# reload used s registers from stack
lw $ra, 0($sp) # $ra = $sp[0]
addiu $sp, $sp, 4 # $sp = &$sp[1]
jr $ra # }
main: # main:
li $a0 25 # . 25
li $a1 15 # | 15
jal call_gcd # | gcd(^^, ^^)
move $t0, $v0 # | ^^^^^^^^^^^
li $v0 1 # print_int(^^^^^^^^^^^)
move $a0 $t0 #
syscall #inspired by zig.
statement:
-
var varname: TYPE = EXPRESSION;- define variable -
variable | register = EXPRESSION;- set variable. egmyvar = 3;or$v0 = 5; -
save EXPRESSION = EXPRESSION- save into memory. egsave myptr.* = 25;. you may wonder why this is not justmyptr.* = 25;. it should be. -
inline? fn function_name(arg_name: TYPE, ...) { STATEMENT... }- define ¿inline? function. make sure to jump over functions. functions will save any needed things to the stack (and unneeded because they will always save $ra even if they don't call any functions oops) -
loop { STATEMENT ... }- loop forever. break/continue out of the loop. -
if EXPRESSION operator EXPRESSION { STATEMENT... }- expression:<=,<,==,!=,>,>=. -
!clear $ra, $call, ...- tell the register allocator that these variables cannot be used.$callexpands into $t0-7, $a0-4, $ra, $v0-1. this way, using a variable across a syscall makes it go into $s0-7. -
\\inline assembly...- inline assembly currently has no option to use variables. make sure to !clear any used registers after inline assembly. eg:\syscall -
return EXPRESSION?;- return a value from a function -
break;- break from a loop -
continue;- continue in a loop
types:
-
u32,i32,u8- unsigned and signed integer types -
[*]TYPE- pointer to array (indexable, math supported) -
*TYPE- pointer to one (not indexable, no math) -
void- nothing. for use as a return value -
any- any value (up to 32 bits)
expression:
variable_name-
@TYPE:data_name- get something from the .data section with name data_name and type TYPE. example:@i32:lenor@[*]i32:x[25]. masc doesn't help with defining items in the .data section, do it yourself. -
$register_name- use a register -
function_name(EXPRESSION, ...)- call a function -
EXPRESSION + - * / ^ % EXPRESSION- math. eg1 + 1or5 * 3 + 6 % 8.^is binary xor. -
EXPRESSION[EXPRESSION]- index array, egsomearray[5] -
EXPRESSION.*- get value of pointer, egsomepointer.* -
&EXPRESSION- address of expression, eg&somepointer[2] -
undefined- anything. egvar varname: u32 = undefined; -
25- any number, eg-8or5325. no binary literals, hex literals, or floating point numbers supported.
comments:
-
// asdfnjdksalk- comment that will not be visible in the output code -
/* asdfnjdks */- comment that will not be visible in the output code -
\\# comment that will be visible in the output code(inline assembly "#") -
\\- newline that will be visible in the output code
there's probably more that I'm missing. look at the examples.
all variables are stored in a register. if you run out of registers, save some things to the stack manually yourself.
- no parenthesis expression. you can't do
(1 + 1) * 2 - newlines have to be explicitly preserved
- inline function call comments are all on one line
- no strings, no way to make a print function
- no way to make a macro fn
- lots of missing integer types
- supporting larger types (eg doubles) would require a pretty big refactor probably
- too many registers has no position associated with the error
- register allocation bugs probably
- intermediate representation is using untyped strings and regex replace rather than actual objects