Data commit
This commit is contained in:
parent
7387c8f97b
commit
cb5bb5e222
199093 changed files with 3378972 additions and 0 deletions
2
Task/Compiler-virtual-machine-interpreter/00-META.yaml
Normal file
2
Task/Compiler-virtual-machine-interpreter/00-META.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
from: http://rosettacode.org/wiki/Compiler/virtual_machine_interpreter
|
||||
211
Task/Compiler-virtual-machine-interpreter/00-TASK.txt
Normal file
211
Task/Compiler-virtual-machine-interpreter/00-TASK.txt
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
Virtual Machine Interpreter
|
||||
|
||||
A virtual machine implements a computer in software.
|
||||
|
||||
{{task heading}}
|
||||
|
||||
Write a virtual machine interpreter. This interpreter should be able to run virtual
|
||||
assembly language programs created via the [[Compiler/code_generator|task]]. This is a
|
||||
byte-coded, 32-bit word stack based virtual machine.
|
||||
|
||||
The program should read input from a file and/or stdin, and write output to a file and/or
|
||||
stdout.
|
||||
|
||||
Input format:
|
||||
|
||||
Given the following program:
|
||||
|
||||
count = 1;
|
||||
while (count < 10) {
|
||||
print("count is: ", count, "\n");
|
||||
count = count + 1;
|
||||
}
|
||||
|
||||
The output from the [[Compiler/code_generator|Code generator]] is a virtual assembly code program:
|
||||
|
||||
{| class="wikitable"
|
||||
|-
|
||||
! Output from gen, input to VM
|
||||
|-
|
||||
|
||||
| style="vertical-align:top" |
|
||||
<b><pre>Datasize: 1 Strings: 2
|
||||
"count is: "
|
||||
"\n"
|
||||
0 push 1
|
||||
5 store [0]
|
||||
10 fetch [0]
|
||||
15 push 10
|
||||
20 lt
|
||||
21 jz (43) 65
|
||||
26 push 0
|
||||
31 prts
|
||||
32 fetch [0]
|
||||
37 prti
|
||||
38 push 1
|
||||
43 prts
|
||||
44 fetch [0]
|
||||
49 push 1
|
||||
54 add
|
||||
55 store [0]
|
||||
60 jmp (-51) 10
|
||||
65 halt</pre></b>
|
||||
|}
|
||||
|
||||
The first line of the input specifies the datasize required and the number of constant
|
||||
strings, in the order that they are reference via the code.
|
||||
|
||||
The data can be stored in a separate array, or the data can be stored at the beginning of
|
||||
the stack. Data is addressed starting at 0. If there are 3 variables, the 3rd one if
|
||||
referenced at address 2.
|
||||
|
||||
If there are one or more constant strings, they come next. The code refers to these
|
||||
strings by their index. The index starts at 0. So if there are 3 strings, and the code
|
||||
wants to reference the 3rd string, 2 will be used.
|
||||
|
||||
Next comes the actual virtual assembly code. The first number is the code address of that
|
||||
instruction. After that is the instruction mnemonic, followed by optional operands,
|
||||
depending on the instruction.
|
||||
|
||||
Registers:
|
||||
|
||||
sp:
|
||||
the stack pointer - points to the next top of stack. The stack is a 32-bit integer
|
||||
array.
|
||||
|
||||
pc:
|
||||
the program counter - points to the current instruction to be performed. The code is an
|
||||
array of bytes.
|
||||
|
||||
Data:
|
||||
data
|
||||
string pool
|
||||
|
||||
Instructions:
|
||||
|
||||
Each instruction is one byte. The following instructions also have a 32-bit integer
|
||||
operand:
|
||||
|
||||
fetch [index]
|
||||
|
||||
where index is an index into the data array.
|
||||
|
||||
store [index]
|
||||
|
||||
where index is an index into the data array.
|
||||
|
||||
push n
|
||||
|
||||
where value is a 32-bit integer that will be pushed onto the stack.
|
||||
|
||||
jmp (n) addr
|
||||
|
||||
where (n) is a 32-bit integer specifying the distance between the current location and the
|
||||
desired location. addr is an unsigned value of the actual code address.
|
||||
|
||||
jz (n) addr
|
||||
|
||||
where (n) is a 32-bit integer specifying the distance between the current location and the
|
||||
desired location. addr is an unsigned value of the actual code address.
|
||||
|
||||
The following instructions do not have an operand. They perform their operation directly
|
||||
against the stack:
|
||||
|
||||
For the following instructions, the operation is performed against the top two entries in
|
||||
the stack:
|
||||
|
||||
add
|
||||
sub
|
||||
mul
|
||||
div
|
||||
mod
|
||||
lt
|
||||
gt
|
||||
le
|
||||
ge
|
||||
eq
|
||||
ne
|
||||
and
|
||||
or
|
||||
|
||||
For the following instructions, the operation is performed against the top entry in the
|
||||
stack:
|
||||
|
||||
neg
|
||||
not
|
||||
|
||||
Print the word at stack top as a character.
|
||||
|
||||
prtc
|
||||
|
||||
Print the word at stack top as an integer.
|
||||
|
||||
prti
|
||||
|
||||
Stack top points to an index into the string pool. Print that entry.
|
||||
|
||||
prts
|
||||
|
||||
Unconditional stop.
|
||||
|
||||
halt
|
||||
|
||||
; A simple example virtual machine:
|
||||
|
||||
<syntaxhighlight lang="python">def run_vm(data_size)
|
||||
int stack[data_size + 1000]
|
||||
set stack[0..data_size - 1] to 0
|
||||
int pc = 0
|
||||
while True:
|
||||
op = code[pc]
|
||||
pc += 1
|
||||
|
||||
if op == FETCH:
|
||||
stack.append(stack[bytes_to_int(code[pc:pc+word_size])[0]]);
|
||||
pc += word_size
|
||||
elif op == STORE:
|
||||
stack[bytes_to_int(code[pc:pc+word_size])[0]] = stack.pop();
|
||||
pc += word_size
|
||||
elif op == PUSH:
|
||||
stack.append(bytes_to_int(code[pc:pc+word_size])[0]);
|
||||
pc += word_size
|
||||
elif op == ADD: stack[-2] += stack[-1]; stack.pop()
|
||||
elif op == SUB: stack[-2] -= stack[-1]; stack.pop()
|
||||
elif op == MUL: stack[-2] *= stack[-1]; stack.pop()
|
||||
elif op == DIV: stack[-2] /= stack[-1]; stack.pop()
|
||||
elif op == MOD: stack[-2] %= stack[-1]; stack.pop()
|
||||
elif op == LT: stack[-2] = stack[-2] < stack[-1]; stack.pop()
|
||||
elif op == GT: stack[-2] = stack[-2] > stack[-1]; stack.pop()
|
||||
elif op == LE: stack[-2] = stack[-2] <= stack[-1]; stack.pop()
|
||||
elif op == GE: stack[-2] = stack[-2] >= stack[-1]; stack.pop()
|
||||
elif op == EQ: stack[-2] = stack[-2] == stack[-1]; stack.pop()
|
||||
elif op == NE: stack[-2] = stack[-2] != stack[-1]; stack.pop()
|
||||
elif op == AND: stack[-2] = stack[-2] and stack[-1]; stack.pop()
|
||||
elif op == OR: stack[-2] = stack[-2] or stack[-1]; stack.pop()
|
||||
elif op == NEG: stack[-1] = -stack[-1]
|
||||
elif op == NOT: stack[-1] = not stack[-1]
|
||||
elif op == JMP: pc += bytes_to_int(code[pc:pc+word_size])[0]
|
||||
elif op == JZ: if stack.pop() then pc += word_size else pc += bytes_to_int(code[pc:pc+word_size])[0]
|
||||
elif op == PRTC: print stack[-1] as a character; stack.pop()
|
||||
elif op == PRTS: print the constant string referred to by stack[-1]; stack.pop()
|
||||
elif op == PRTI: print stack[-1] as an integer; stack.pop()
|
||||
elif op == HALT: break</syntaxhighlight>
|
||||
|
||||
; Additional examples
|
||||
|
||||
Your solution should pass all the test cases above and the additional tests found '''[[Compiler/Sample_programs|Here]]'''.
|
||||
|
||||
{{task heading|Reference}}
|
||||
|
||||
The C and Python versions can be considered reference implementations.
|
||||
|
||||
;Related Tasks
|
||||
|
||||
* [[Compiler/lexical_analyzer|Lexical Analyzer task]]
|
||||
* [[Compiler/syntax_analyzer|Syntax Analyzer task]]
|
||||
* [[Compiler/code_generator|Code Generator task]]
|
||||
* [[Compiler/AST_interpreter|AST Interpreter task]]
|
||||
|
||||
<hr>
|
||||
__TOC__
|
||||
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
begin % virtual machine interpreter %
|
||||
% string literals %
|
||||
string(256) array stringValue ( 0 :: 256 );
|
||||
integer array stringLength ( 0 :: 256 );
|
||||
integer MAX_STRINGS;
|
||||
% op codes %
|
||||
integer oFetch, oStore, oPush
|
||||
, oAdd, oSub, oMul, oDiv, oMod, oLt, oGt, oLe, oGe, oEq, oNe
|
||||
, oAnd, oOr, oNeg, oNot, oJmp, oJz, oPrtc, oPrts, oPrti, oHalt
|
||||
;
|
||||
string(6) array opName ( 1 :: 24 );
|
||||
integer OP_MAX;
|
||||
% code %
|
||||
string(1) array byteCode ( 0 :: 4096 );
|
||||
integer nextLocation, MAX_LOCATION;
|
||||
% data %
|
||||
integer array data ( 0 :: 4096 );
|
||||
integer dataSize, MAX_DATA, MAX_STACK;
|
||||
% tracing %
|
||||
logical trace;
|
||||
|
||||
% reports an error and stops %
|
||||
procedure rtError( string(80) value message ); begin
|
||||
integer errorPos;
|
||||
write( s_w := 0, "**** Runtime error: " );
|
||||
errorPos := 0;
|
||||
while errorPos < 80 and message( errorPos // 1 ) not = "." do begin
|
||||
writeon( s_w := 0, message( errorPos // 1 ) );
|
||||
errorPos := errorPos + 1
|
||||
end while_not_at_end_of_message ;
|
||||
writeon( s_w := 0, "." );
|
||||
assert( false )
|
||||
end genError ;
|
||||
|
||||
oFetch := 1; opName( oFetch ) := "fetch"; oStore := 2; opName( oStore ) := "store"; oPush := 3; opName( oPush ) := "push";
|
||||
oAdd := 4; opName( oAdd ) := "add"; oSub := 5; opName( oSub ) := "sub"; oMul := 6; opName( oMul ) := "mul";
|
||||
oDiv := 7; opName( oDiv ) := "div"; oMod := 8; opName( oMod ) := "mod"; oLt := 9; opName( oLt ) := "lt";
|
||||
oGt := 10; opName( oGt ) := "gt"; oLe := 11; opName( oLe ) := "le"; oGe := 12; opName( oGe ) := "ge";
|
||||
oEq := 13; opName( oEq ) := "eq"; oNe := 14; opName( oNe ) := "ne"; oAnd := 15; opName( oAnd ) := "and";
|
||||
oOr := 16; opName( oOr ) := "or"; oNeg := 17; opName( oNeg ) := "neg"; oNot := 18; opName( oNot ) := "not";
|
||||
oJmp := 19; opName( oJmp ) := "jmp"; oJz := 20; opName( oJz ) := "jz"; oPrtc := 21; opName( oPrtc ) := "prtc";
|
||||
oPrts := 22; opName( oPrts ) := "prts"; oPrti := 23; opName( oPrti ) := "prti"; oHalt := 24; opName( oHalt ) := "halt";
|
||||
OP_MAX := oHalt;
|
||||
|
||||
trace := false;
|
||||
MAX_STACK := 256;
|
||||
MAX_LOCATION := 4096;
|
||||
for pc := 0 until MAX_LOCATION do byteCode( pc ) := code( 0 );
|
||||
MAX_DATA := 4096;
|
||||
for dPos := 0 until MAX_DATA do data( dPos ) := 0;
|
||||
MAX_STRINGS := 256;
|
||||
for sPos := 0 until MAX_STRINGS do begin
|
||||
stringValue( sPos ) := " ";
|
||||
stringLength( sPos ) := 0
|
||||
end for_sPos ;
|
||||
|
||||
% load thge output from syntaxc analyser %
|
||||
begin % readCode %
|
||||
|
||||
% skips spaces on the source line %
|
||||
procedure skipSpaces ; begin
|
||||
while line( lPos // 1 ) = " " do lPos := lPos + 1
|
||||
end skipSpaces ;
|
||||
|
||||
% parses a string from line and stores it in the string literals table %
|
||||
procedure readString ( integer value stringNumber ) ; begin
|
||||
string(256) str;
|
||||
integer sLen;
|
||||
str := " ";
|
||||
sLen := 0;
|
||||
lPos := lPos + 1; % skip the opening double-quote %
|
||||
while lPos <= 255 and line( lPos // 1 ) not = """" do begin
|
||||
str( sLen // 1 ) := line( lPos // 1 );
|
||||
sLen := sLen + 1;
|
||||
lPos := lPos + 1
|
||||
end while_more_string ;
|
||||
if lPos > 255 then rtError( "Unterminated String." );
|
||||
% store the string %
|
||||
stringValue( stringNumber ) := str;
|
||||
stringLength( stringNumber ) := sLen
|
||||
end readString ;
|
||||
|
||||
% gets an integer from the line - checks for valid digits %
|
||||
integer procedure readInteger ; begin
|
||||
integer n;
|
||||
skipSpaces;
|
||||
n := 0;
|
||||
while line( lPos // 1 ) >= "0" and line( lPos // 1 ) <= "9" do begin
|
||||
n := ( n * 10 ) + ( decode( line( lPos // 1 ) ) - decode( "0" ) );
|
||||
lPos := lPos + 1
|
||||
end while_not_end_of_integer ;
|
||||
n
|
||||
end readInteger ;
|
||||
|
||||
% reads the next line from standard input %
|
||||
procedure readALine ; begin
|
||||
lPos := 0;
|
||||
readcard( line );
|
||||
if trace then write( s_w := 0, ">> ", line( 0 // 32 ) )
|
||||
end readALine ;
|
||||
|
||||
% loads an instruction from the current source line %
|
||||
procedure loadCodeFromLine ; begin
|
||||
integer pc, opCode, operand, oPos;
|
||||
string(256) op;
|
||||
logical haveOperand;
|
||||
% get the code location %
|
||||
pc := readInteger;
|
||||
if pc > MAX_LOCATION then rtError( "Code too large." );
|
||||
% get the opCode %
|
||||
skipSpaces;
|
||||
oPos := 0;
|
||||
op := " ";
|
||||
while lPos <= 255 and line( lPos // 1 ) not = " " do begin
|
||||
op( oPos // 1 ) := line( lPos // 1 );
|
||||
oPos := oPos + 1;
|
||||
lPos := lPos + 1
|
||||
end while_more_opName ;
|
||||
% lookup the op code %
|
||||
opCode := 0;
|
||||
oPos := 1;
|
||||
while oPos <= OP_MAX and opCode = 0 do begin
|
||||
if opName( oPos ) = op then opCode := oPos
|
||||
else oPos := oPos + 1
|
||||
end while_op_not_found ;
|
||||
if opCode = 0 then rtError( "Unknown op code." );
|
||||
% get the operand if there is one %
|
||||
operand := 0;
|
||||
haveOperand := false;
|
||||
if opCode = oFetch or opCode = oStore then begin
|
||||
% fetch or store - operand is enclosed in square brackets %
|
||||
skipSpaces;
|
||||
if line( lPos // 1 ) not = "[" then rtError( """["" expected after fetch/store." );
|
||||
lPos := lPos + 1;
|
||||
operand := readInteger;
|
||||
if operand > dataSize then rtError( "fetch/store address out of range." );
|
||||
haveOperand := true
|
||||
end
|
||||
else if opCode = oPush then begin
|
||||
% push integer literal instruction %
|
||||
operand := readInteger;
|
||||
haveOperand := true
|
||||
end
|
||||
else if opCode = oJmp or opCode = oJz then begin
|
||||
% jump - the operand is the relative address enclosed in parenthesis %
|
||||
% followed by the absolute address - we use the absolute address so %
|
||||
% the opewrand will be >= 0 %
|
||||
skipSpaces;
|
||||
if line( lPos // 1 ) not = "(" then rtError( """("" expected after jmp/jz." );
|
||||
lPos := lPos + 1;
|
||||
if line( lPos // 1 ) = "-" then % negative relative address % lPos := lPos + 1;
|
||||
operand := readInteger;
|
||||
if line( lPos // 1 ) not = ")" then rtError( """)"" expected after jmp/jz." );
|
||||
lPos := lPos + 1;
|
||||
operand := readInteger;
|
||||
haveOperand := true
|
||||
end if_various_opcodes ;
|
||||
% store the code %
|
||||
byteCode( pc ) := code( opCode );
|
||||
if haveOperand then begin
|
||||
% have an operand for the op code %
|
||||
if ( pc + 4 ) > MAX_LOCATION then rtError( "Code too large." );
|
||||
for oPos := 1 until 4 do begin
|
||||
pc := pc + 1;
|
||||
byteCode( pc ) := code( operand rem 256 );
|
||||
operand := operand div 256;
|
||||
end for_oPos
|
||||
end if_have_operand ;
|
||||
end loadCodeFromLine ;
|
||||
|
||||
string(256) line;
|
||||
string(16) name;
|
||||
integer lPos, tPos, stringCount;
|
||||
|
||||
% allow us to detect EOF %
|
||||
ENDFILE := EXCEPTION( false, 1, 0, false, "EOF" );
|
||||
|
||||
% first line should be "Datasize: d Strings: s" where d = number variables %
|
||||
% and s = number of strings %
|
||||
readALine;
|
||||
if line = "trace" then begin
|
||||
% extension - run in trace mode %
|
||||
trace := true;
|
||||
readALine
|
||||
end if_line_eq_trace ;
|
||||
if XCPNOTED(ENDFILE) then rtError( "Empty program file." );
|
||||
if line( 0 // 10 ) not = "Datasize: " then rtError( "Header line missing." );
|
||||
lPos := 10;
|
||||
dataSize := readInteger;
|
||||
if dataSize > MAX_DATA then rtError( "Datasize too large." );
|
||||
skipSpaces;
|
||||
if line( lPos // 9 ) not = "Strings: " then rtError( """Strings: "" missing on header line." );
|
||||
lPos := lPos + 9;
|
||||
stringCount := readInteger;
|
||||
if stringCount > MAX_STRINGS then rtError( "Too many strings." );
|
||||
% read the string table %
|
||||
for stringNumber := 0 until stringCount - 1 do begin
|
||||
string(256) str;
|
||||
integer sLen, sPos;
|
||||
readALine;
|
||||
if XCPNOTED(ENDFILE) then rtError( "End-of-file in string table." );
|
||||
if line( lPos // 1 ) not = """" then rtError( "String literal expected." );
|
||||
str := " ";
|
||||
sLen := 0;
|
||||
lPos := lPos + 1; % skip the opening double-quote %
|
||||
while lPos <= 255 and line( lPos // 1 ) not = """" do begin
|
||||
str( sLen // 1 ) := line( lPos // 1 );
|
||||
sLen := sLen + 1;
|
||||
lPos := lPos + 1
|
||||
end while_more_string ;
|
||||
if lPos > 255 then rtError( "Unterminated String." );
|
||||
% store the string %
|
||||
stringValue( stringNumber ) := str;
|
||||
stringLength( stringNumber ) := sLen
|
||||
end for_sPos ;
|
||||
% read the code %
|
||||
readALine;
|
||||
while not XCPNOTED(ENDFILE) do begin
|
||||
if line not = " " then loadCodeFromLine;
|
||||
readALine
|
||||
end while_not_eof
|
||||
end;
|
||||
% run the program %
|
||||
begin
|
||||
integer pc, opCode, operand, sp;
|
||||
integer array st ( 0 :: MAX_STACK );
|
||||
logical halted;
|
||||
% prints a string from the string pool, escape sequences are interpreted %
|
||||
procedure writeOnString( integer value stringNumber ) ;
|
||||
begin
|
||||
integer cPos, sLen;
|
||||
string(256) text;
|
||||
if stringNumber < 0 or stringNumber > MAX_STRINGS then rtError( "Invalid string number." );
|
||||
cPos := 0;
|
||||
sLen := stringLength( stringNumber );
|
||||
text := stringValue( stringNumber );
|
||||
while cPos < stringLength( stringNumber ) do begin
|
||||
string(1) ch;
|
||||
ch := text( cPos // 1 );
|
||||
if ch not = "\" then writeon( s_w := 0, ch )
|
||||
else begin
|
||||
% escaped character %
|
||||
cPos := cPos + 1;
|
||||
if cPos > sLen then rtError( "String terminates with ""\""." );
|
||||
ch := text( cPos // 1 );
|
||||
if ch = "n" then % newline % write()
|
||||
else writeon( s_w := 0, ch )
|
||||
end;
|
||||
cPos := cPos + 1
|
||||
end while_not_end_of_string
|
||||
end writeOnString ;
|
||||
|
||||
pc := 0;
|
||||
sp := -1;
|
||||
halted := false;
|
||||
while not halted do begin;
|
||||
% get the next op code and operand %
|
||||
opCode := decode( byteCode( pc ) );
|
||||
pc := pc + 1;
|
||||
operand := 0;
|
||||
if opCode = oFetch or opCode = oStore or opCode = oPush or opCode = oJmp or opCode = oJz then begin
|
||||
% this opCode has an operand %
|
||||
pc := pc + 4;
|
||||
for bPos := 1 until 4 do begin
|
||||
operand := ( operand * 256 ) + decode( byteCode( pc - bPos ) );
|
||||
end for_bPos
|
||||
end if_opCode_with_an_operand ;
|
||||
if trace then begin
|
||||
write( i_w:= 1, s_w := 0, pc, " op(", opCode, "): ", opName( opCode ), " ", operand );
|
||||
write()
|
||||
end if_trace ;
|
||||
% interpret the instruction %
|
||||
if opCode = oFetch then begin sp := sp + 1; st( sp ) := data( operand ) end
|
||||
else if opCode = oStore then begin data( operand ) := st( sp ); sp := sp - 1 end
|
||||
else if opCode = oPush then begin sp := sp + 1; st( sp ) := operand end
|
||||
else if opCode = oHalt then halted := true
|
||||
else if opCode = oJmp then pc := operand
|
||||
else if oPCode = oJz then begin
|
||||
if st( sp ) = 0 then pc := operand;
|
||||
sp := sp - 1
|
||||
end
|
||||
else if opCode = oPrtc then begin writeon( i_w := 1, s_w := 0, code( st( sp ) ) ); sp := sp - 1 end
|
||||
else if opCode = oPrti then begin writeon( i_w := 1, s_w := 0, st( sp ) ); sp := sp - 1 end
|
||||
else if opCode = oPrts then begin writeonString( st( sp ) ); sp := sp - 1 end
|
||||
else if opCode = oNeg then st( sp ) := - st( sp )
|
||||
else if opCode = oNot then st( sp ) := ( if st( sp ) = 0 then 1 else 0 )
|
||||
else begin
|
||||
operand := st( sp );
|
||||
sp := sp - 1;
|
||||
if opCode = oAdd then st( sp ) := st( sp ) + operand
|
||||
else if opCode = oSub then st( sp ) := st( sp ) - operand
|
||||
else if opCode = oMul then st( sp ) := st( sp ) * operand
|
||||
else if opCode = oDiv then st( sp ) := st( sp ) div operand
|
||||
else if opCode = oMod then st( sp ) := st( sp ) rem operand
|
||||
else if opCode = oLt then st( sp ) := if st( sp ) < operand then 1 else 0
|
||||
else if opCode = oGt then st( sp ) := if st( sp ) > operand then 1 else 0
|
||||
else if opCode = oLe then st( sp ) := if st( sp ) <= operand then 1 else 0
|
||||
else if opCode = oGe then st( sp ) := if st( sp ) >= operand then 1 else 0
|
||||
else if opCode = oEq then st( sp ) := if st( sp ) = operand then 1 else 0
|
||||
else if opCode = oNe then st( sp ) := if st( sp ) not = operand then 1 else 0
|
||||
else if opCode = oAnd then st( sp ) := if st( sp ) not = 0 and operand not = 0 then 1 else 0
|
||||
else if opCode = oOr then st( sp ) := if st( sp ) not = 0 or operand not = 0 then 1 else 0
|
||||
else rtError( "Unknown opCode." )
|
||||
end if_various_opCodes
|
||||
end while_not_halted
|
||||
end
|
||||
end.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,154 @@
|
|||
function error(msg) {
|
||||
printf("%s\n", msg)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
function bytes_to_int(bstr, i, sum) {
|
||||
sum = 0
|
||||
for (i=word_size-1; i>=0; i--) {
|
||||
sum *= 256
|
||||
sum += code[bstr+i]
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
function emit_byte(x) {
|
||||
code[next_free_code_index++] = x
|
||||
}
|
||||
|
||||
function emit_word(x, i) {
|
||||
for (i=0; i<word_size; i++) {
|
||||
emit_byte(int(x)%256);
|
||||
x = int(x/256)
|
||||
}
|
||||
}
|
||||
|
||||
function run_vm(data_size) {
|
||||
sp = data_size + 1
|
||||
pc = 0
|
||||
while (1) {
|
||||
op = code[pc++]
|
||||
if (op == FETCH) {
|
||||
stack[sp++] = stack[bytes_to_int(pc)]
|
||||
pc += word_size
|
||||
} else if (op == STORE) {
|
||||
stack[bytes_to_int(pc)] = stack[--sp]
|
||||
pc += word_size
|
||||
} else if (op == PUSH) {
|
||||
stack[sp++] = bytes_to_int(pc)
|
||||
pc += word_size
|
||||
} else if (op == ADD ) { stack[sp-2] += stack[sp-1]; sp--
|
||||
} else if (op == SUB ) { stack[sp-2] -= stack[sp-1]; sp--
|
||||
} else if (op == MUL ) { stack[sp-2] *= stack[sp-1]; sp--
|
||||
} else if (op == DIV ) { stack[sp-2] = int(stack[sp-2] / stack[sp-1]); sp--
|
||||
} else if (op == MOD ) { stack[sp-2] %= stack[sp-1]; sp--
|
||||
} else if (op == LT ) { stack[sp-2] = stack[sp-2] < stack[sp-1]; sp--
|
||||
} else if (op == GT ) { stack[sp-2] = stack[sp-2] > stack[sp-1]; sp--
|
||||
} else if (op == LE ) { stack[sp-2] = stack[sp-2] <= stack[sp-1]; sp--
|
||||
} else if (op == GE ) { stack[sp-2] = stack[sp-2] >= stack[sp-1]; sp--
|
||||
} else if (op == EQ ) { stack[sp-2] = stack[sp-2] == stack[sp-1]; sp--
|
||||
} else if (op == NE ) { stack[sp-2] = stack[sp-2] != stack[sp-1]; sp--
|
||||
} else if (op == AND ) { stack[sp-2] = stack[sp-2] && stack[sp-1]; sp--
|
||||
} else if (op == OR ) { stack[sp-2] = stack[sp-2] || stack[sp-1]; sp--
|
||||
} else if (op == NEG ) { stack[sp-1] = - stack[sp-1]
|
||||
} else if (op == NOT ) { stack[sp-1] = ! stack[sp-1]
|
||||
} else if (op == JMP ) { pc += bytes_to_int(pc)
|
||||
} else if (op == JZ ) { if (stack[--sp]) { pc += word_size } else { pc += bytes_to_int(pc) }
|
||||
} else if (op == PRTC) { printf("%c", stack[--sp])
|
||||
} else if (op == PRTS) { printf("%s", string_pool[stack[--sp]])
|
||||
} else if (op == PRTI) { printf("%d", stack[--sp])
|
||||
} else if (op == HALT) { break
|
||||
}
|
||||
} # while
|
||||
}
|
||||
|
||||
function str_trans(srce, dest, i) {
|
||||
dest = ""
|
||||
for (i=1; i <= length(srce); ) {
|
||||
if (substr(srce, i, 1) == "\\" && i < length(srce)) {
|
||||
if (substr(srce, i+1, 1) == "n") {
|
||||
dest = dest "\n"
|
||||
i += 2
|
||||
} else if (substr(srce, i+1, 1) == "\\") {
|
||||
dest = dest "\\"
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
dest = dest substr(srce, i, 1)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
function load_code( n, i) {
|
||||
getline line
|
||||
if (line == "")
|
||||
error("empty line")
|
||||
n=split(line, line_list)
|
||||
data_size = line_list[2]
|
||||
n_strings = line_list[4]
|
||||
for (i=0; i<n_strings; i++) {
|
||||
getline line
|
||||
gsub(/\n/, "", line)
|
||||
gsub(/"/ , "", line)
|
||||
string_pool[i] = str_trans(line)
|
||||
}
|
||||
while (getline) {
|
||||
offset = int($1)
|
||||
instr = $2
|
||||
opcode = code_map[instr]
|
||||
if (opcode == "")
|
||||
error("Unknown instruction " instr " at " offset)
|
||||
emit_byte(opcode)
|
||||
if (opcode == JMP || opcode == JZ) {
|
||||
p = int($4)
|
||||
emit_word(p - (offset + 1))
|
||||
} else if (opcode == PUSH) {
|
||||
value = int($3)
|
||||
emit_word(value)
|
||||
} else if (opcode == FETCH || opcode == STORE) {
|
||||
gsub(/\[/, "", $3)
|
||||
gsub(/\]/, "", $3)
|
||||
value = int($3)
|
||||
emit_word(value)
|
||||
}
|
||||
}
|
||||
return data_size
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
code_map["fetch"] = FETCH = 1
|
||||
code_map["store"] = STORE = 2
|
||||
code_map["push" ] = PUSH = 3
|
||||
code_map["add" ] = ADD = 4
|
||||
code_map["sub" ] = SUB = 5
|
||||
code_map["mul" ] = MUL = 6
|
||||
code_map["div" ] = DIV = 7
|
||||
code_map["mod" ] = MOD = 8
|
||||
code_map["lt" ] = LT = 9
|
||||
code_map["gt" ] = GT = 10
|
||||
code_map["le" ] = LE = 11
|
||||
code_map["ge" ] = GE = 12
|
||||
code_map["eq" ] = EQ = 13
|
||||
code_map["ne" ] = NE = 14
|
||||
code_map["and" ] = AND = 15
|
||||
code_map["or" ] = OR = 16
|
||||
code_map["neg" ] = NEG = 17
|
||||
code_map["not" ] = NOT = 18
|
||||
code_map["jmp" ] = JMP = 19
|
||||
code_map["jz" ] = JZ = 20
|
||||
code_map["prtc" ] = PRTC = 21
|
||||
code_map["prts" ] = PRTS = 22
|
||||
code_map["prti" ] = PRTI = 23
|
||||
code_map["halt" ] = HALT = 24
|
||||
|
||||
next_free_node_index = 1
|
||||
next_free_code_index = 0
|
||||
word_size = 4
|
||||
input_file = "-"
|
||||
if (ARGC > 1)
|
||||
input_file = ARGV[1]
|
||||
data_size = load_code()
|
||||
run_vm(data_size)
|
||||
}
|
||||
|
|
@ -0,0 +1,935 @@
|
|||
--
|
||||
-- The Rosetta Code Virtual Machine, in Ada.
|
||||
--
|
||||
-- It is assumed the platform on which this program is run
|
||||
-- has two's-complement integers. (Otherwise one could modify
|
||||
-- the vmint_to_vmsigned and vmsigned_to_vmint functions. But
|
||||
-- the chances your binary integers are not two's-complement
|
||||
-- seem pretty low.)
|
||||
--
|
||||
|
||||
with Ada.Characters.Handling; use Ada.Characters.Handling;
|
||||
with Ada.Command_Line; use Ada.Command_Line;
|
||||
|
||||
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
|
||||
with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO;
|
||||
|
||||
with Ada.Text_IO; use Ada.Text_IO;
|
||||
with Ada.Text_IO.Text_Streams; use Ada.Text_IO.Text_Streams;
|
||||
|
||||
with Ada.Unchecked_Conversion;
|
||||
|
||||
procedure VM
|
||||
is
|
||||
bad_vm : exception;
|
||||
vm_limit_exceeded : exception;
|
||||
vm_runtime_error : exception;
|
||||
|
||||
status : Exit_Status;
|
||||
input_file_name : Unbounded_String;
|
||||
output_file_name : Unbounded_String;
|
||||
input_file : File_Type;
|
||||
output_file : File_Type;
|
||||
|
||||
-- Some limits of this implementation. You can adjust these to taste.
|
||||
strings_size : constant := 2_048;
|
||||
stack_size : constant := 2_048;
|
||||
data_size : constant := 2_048;
|
||||
code_size : constant := 32_768;
|
||||
|
||||
type byte is mod 16#100#;
|
||||
type vmint is mod 16#1_0000_0000#;
|
||||
subtype vmsigned is Integer range -2_147_483_648 .. 2_147_483_647;
|
||||
|
||||
op_halt : constant byte := 0;
|
||||
op_add : constant byte := 1;
|
||||
op_sub : constant byte := 2;
|
||||
op_mul : constant byte := 3;
|
||||
op_div : constant byte := 4;
|
||||
op_mod : constant byte := 5;
|
||||
op_lt : constant byte := 6;
|
||||
op_gt : constant byte := 7;
|
||||
op_le : constant byte := 8;
|
||||
op_ge : constant byte := 9;
|
||||
op_eq : constant byte := 10;
|
||||
op_ne : constant byte := 11;
|
||||
op_and : constant byte := 12;
|
||||
op_or : constant byte := 13;
|
||||
op_neg : constant byte := 14;
|
||||
op_not : constant byte := 15;
|
||||
op_prtc : constant byte := 16;
|
||||
op_prti : constant byte := 17;
|
||||
op_prts : constant byte := 18;
|
||||
op_fetch : constant byte := 19;
|
||||
op_store : constant byte := 20;
|
||||
op_push : constant byte := 21;
|
||||
op_jmp : constant byte := 22;
|
||||
op_jz : constant byte := 23;
|
||||
|
||||
strings : array (0 .. strings_size - 1) of Unbounded_String;
|
||||
stack : array (0 .. stack_size - 1) of vmint;
|
||||
data : array (0 .. data_size - 1) of vmint;
|
||||
code : array (0 .. code_size) of byte;
|
||||
sp : vmint;
|
||||
pc : vmint;
|
||||
|
||||
output_stream : Stream_Access;
|
||||
|
||||
function vmsigned_to_vmint is new Ada.Unchecked_Conversion
|
||||
(Source => vmsigned, Target => vmint);
|
||||
|
||||
function vmint_to_vmsigned is new Ada.Unchecked_Conversion
|
||||
(Source => vmint, Target => vmsigned);
|
||||
|
||||
function twos_complement
|
||||
(x : in vmint)
|
||||
return vmint
|
||||
is
|
||||
begin
|
||||
return (not x) + 1;
|
||||
end twos_complement;
|
||||
|
||||
function vmint_to_digits
|
||||
(x : in vmint)
|
||||
return Unbounded_String
|
||||
is
|
||||
s : Unbounded_String;
|
||||
z : vmint;
|
||||
begin
|
||||
if x = 0 then
|
||||
s := To_Unbounded_String ("0");
|
||||
else
|
||||
s := To_Unbounded_String ("");
|
||||
z := x;
|
||||
while z /= 0 loop
|
||||
s := Character'Val ((z rem 10) + Character'Pos ('0')) & s;
|
||||
z := z / 10;
|
||||
end loop;
|
||||
end if;
|
||||
return s;
|
||||
end vmint_to_digits;
|
||||
|
||||
function digits_to_vmint
|
||||
(s : in String)
|
||||
return vmint
|
||||
is
|
||||
zero : constant Character := '0';
|
||||
zero_pos : constant Integer := Character'Pos (zero);
|
||||
retval : vmint;
|
||||
begin
|
||||
if s'Length < 1 then
|
||||
raise bad_vm with "expected a numeric literal";
|
||||
end if;
|
||||
retval := 0;
|
||||
for i in s'Range loop
|
||||
if Is_Decimal_Digit (s (i)) then
|
||||
retval :=
|
||||
(10 * retval) + vmint (Character'Pos (s (i)) - zero_pos);
|
||||
else
|
||||
raise bad_vm with "expected a decimal digit";
|
||||
end if;
|
||||
end loop;
|
||||
return retval;
|
||||
end digits_to_vmint;
|
||||
|
||||
function string_to_vmint
|
||||
(s : in String)
|
||||
return vmint
|
||||
is
|
||||
retval : vmint;
|
||||
begin
|
||||
if s'Length < 1 then
|
||||
raise bad_vm with "expected a numeric literal";
|
||||
end if;
|
||||
if s (s'First) = '-' then
|
||||
if s'Length < 2 then
|
||||
raise bad_vm with "expected a numeric literal";
|
||||
end if;
|
||||
retval :=
|
||||
twos_complement (digits_to_vmint (s (s'First + 1 .. s'Last)));
|
||||
else
|
||||
retval := digits_to_vmint (s);
|
||||
end if;
|
||||
return retval;
|
||||
end string_to_vmint;
|
||||
|
||||
procedure parse_header
|
||||
(s : in String;
|
||||
data_count : out vmint;
|
||||
strings_count : out vmint)
|
||||
is
|
||||
i : Positive;
|
||||
j : Positive;
|
||||
begin
|
||||
i := s'First;
|
||||
while i <= s'Last and then not Is_Decimal_Digit (s (i)) loop
|
||||
i := i + 1;
|
||||
end loop;
|
||||
|
||||
j := i;
|
||||
while j <= s'Last and then Is_Decimal_Digit (s (j)) loop
|
||||
j := j + 1;
|
||||
end loop;
|
||||
|
||||
data_count := digits_to_vmint (s (i .. j - 1));
|
||||
|
||||
i := j;
|
||||
while i <= s'Last and then not Is_Decimal_Digit (s (i)) loop
|
||||
i := i + 1;
|
||||
end loop;
|
||||
|
||||
j := i;
|
||||
while j <= s'Last and then Is_Decimal_Digit (s (j)) loop
|
||||
j := j + 1;
|
||||
end loop;
|
||||
|
||||
strings_count := digits_to_vmint (s (i .. j - 1));
|
||||
end parse_header;
|
||||
|
||||
function parse_string_literal
|
||||
(s : in String)
|
||||
return Unbounded_String
|
||||
is
|
||||
t : Unbounded_String;
|
||||
i : Positive;
|
||||
|
||||
--
|
||||
-- A little trick to get around mistaken highlighting on the
|
||||
-- Rosetta Code site.
|
||||
--
|
||||
quote_string : constant String := """";
|
||||
quote : constant Character := quote_string (1);
|
||||
|
||||
begin
|
||||
t := To_Unbounded_String ("");
|
||||
|
||||
i := s'First;
|
||||
while i <= s'Last and then s (i) /= quote loop
|
||||
i := i + 1;
|
||||
end loop;
|
||||
|
||||
if s'Last < i or else s (i) /= quote then
|
||||
raise bad_vm with "expected a '""'";
|
||||
end if;
|
||||
|
||||
i := i + 1;
|
||||
while i <= s'Last and then s (i) /= quote loop
|
||||
if s (i) /= '\' then
|
||||
Append (t, s (i));
|
||||
i := i + 1;
|
||||
elsif s'Last < i + 1 then
|
||||
raise bad_vm with "truncated string literal";
|
||||
elsif s (i + 1) = 'n' then
|
||||
Append (t, Character'Val (10));
|
||||
i := i + 2;
|
||||
elsif s (i + 1) = '\' then
|
||||
Append (t, '\');
|
||||
i := i + 2;
|
||||
else
|
||||
raise bad_vm with "unsupported escape sequence";
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
return t;
|
||||
end parse_string_literal;
|
||||
|
||||
function name_to_opcode
|
||||
(s : in String)
|
||||
return byte
|
||||
is
|
||||
retval : byte;
|
||||
begin
|
||||
if s = "halt" then
|
||||
retval := op_halt;
|
||||
elsif s = "add" then
|
||||
retval := op_add;
|
||||
elsif s = "sub" then
|
||||
retval := op_sub;
|
||||
elsif s = "mul" then
|
||||
retval := op_mul;
|
||||
elsif s = "div" then
|
||||
retval := op_div;
|
||||
elsif s = "mod" then
|
||||
retval := op_mod;
|
||||
elsif s = "lt" then
|
||||
retval := op_lt;
|
||||
elsif s = "gt" then
|
||||
retval := op_gt;
|
||||
elsif s = "le" then
|
||||
retval := op_le;
|
||||
elsif s = "ge" then
|
||||
retval := op_ge;
|
||||
elsif s = "eq" then
|
||||
retval := op_eq;
|
||||
elsif s = "ne" then
|
||||
retval := op_ne;
|
||||
elsif s = "and" then
|
||||
retval := op_and;
|
||||
elsif s = "or" then
|
||||
retval := op_or;
|
||||
elsif s = "neg" then
|
||||
retval := op_neg;
|
||||
elsif s = "not" then
|
||||
retval := op_not;
|
||||
elsif s = "prtc" then
|
||||
retval := op_prtc;
|
||||
elsif s = "prti" then
|
||||
retval := op_prti;
|
||||
elsif s = "prts" then
|
||||
retval := op_prts;
|
||||
elsif s = "fetch" then
|
||||
retval := op_fetch;
|
||||
elsif s = "store" then
|
||||
retval := op_store;
|
||||
elsif s = "push" then
|
||||
retval := op_push;
|
||||
elsif s = "jmp" then
|
||||
retval := op_jmp;
|
||||
elsif s = "jz" then
|
||||
retval := op_jz;
|
||||
else
|
||||
raise bad_vm with ("unexpected opcode name");
|
||||
end if;
|
||||
return retval;
|
||||
end name_to_opcode;
|
||||
|
||||
procedure parse_instruction
|
||||
(s : in String;
|
||||
address : out vmint;
|
||||
opcode : out byte;
|
||||
arg : out vmint)
|
||||
is
|
||||
i : Positive;
|
||||
j : Positive;
|
||||
begin
|
||||
i := s'First;
|
||||
while i <= s'Last and then not Is_Decimal_Digit (s (i)) loop
|
||||
i := i + 1;
|
||||
end loop;
|
||||
|
||||
j := i;
|
||||
while j <= s'Last and then Is_Decimal_Digit (s (j)) loop
|
||||
j := j + 1;
|
||||
end loop;
|
||||
|
||||
address := digits_to_vmint (s (i .. j - 1));
|
||||
|
||||
i := j;
|
||||
while i <= s'Last and then not Is_Letter (s (i)) loop
|
||||
i := i + 1;
|
||||
end loop;
|
||||
|
||||
j := i;
|
||||
while j <= s'Last and then Is_Letter (s (j)) loop
|
||||
j := j + 1;
|
||||
end loop;
|
||||
|
||||
opcode := name_to_opcode (s (i .. j - 1));
|
||||
|
||||
i := j;
|
||||
while i <= s'Last and then Is_Space (s (i)) loop
|
||||
i := i + 1;
|
||||
end loop;
|
||||
|
||||
if s'Last < i then
|
||||
arg := 0;
|
||||
else
|
||||
if not Is_Decimal_Digit (s (i)) and then s (i) /= '-' then
|
||||
i := i + 1;
|
||||
end if;
|
||||
j := i;
|
||||
while j <= s'Last
|
||||
and then (Is_Decimal_Digit (s (j)) or else s (j) = '-')
|
||||
loop
|
||||
j := j + 1;
|
||||
end loop;
|
||||
arg := string_to_vmint (s (i .. j - 1));
|
||||
end if;
|
||||
end parse_instruction;
|
||||
|
||||
procedure read_and_parse_header
|
||||
(data_count : out vmint;
|
||||
strings_count : out vmint)
|
||||
is
|
||||
line : Unbounded_String;
|
||||
begin
|
||||
Get_Line (Current_Input, line);
|
||||
parse_header (To_String (line), data_count, strings_count);
|
||||
end read_and_parse_header;
|
||||
|
||||
procedure read_parse_and_store_strings
|
||||
(strings_count : in vmint)
|
||||
is
|
||||
line : Unbounded_String;
|
||||
begin
|
||||
if strings_count /= 0 then
|
||||
if strings_size < strings_count then
|
||||
raise vm_limit_exceeded with "strings limit exceeded";
|
||||
end if;
|
||||
for i in 0 .. strings_count - 1 loop
|
||||
Get_Line (Current_Input, line);
|
||||
strings (Integer (i)) :=
|
||||
parse_string_literal (To_String (line));
|
||||
end loop;
|
||||
end if;
|
||||
end read_parse_and_store_strings;
|
||||
|
||||
function opcode_takes_arg
|
||||
(opcode : in byte)
|
||||
return Boolean
|
||||
is
|
||||
retval : Boolean;
|
||||
begin
|
||||
if opcode = op_fetch then
|
||||
retval := True;
|
||||
elsif opcode = op_store then
|
||||
retval := True;
|
||||
elsif opcode = op_push then
|
||||
retval := True;
|
||||
elsif opcode = op_jmp then
|
||||
retval := True;
|
||||
elsif opcode = op_jz then
|
||||
retval := True;
|
||||
else
|
||||
retval := False;
|
||||
end if;
|
||||
return retval;
|
||||
end opcode_takes_arg;
|
||||
|
||||
procedure read_parse_and_store_instructions
|
||||
is
|
||||
line : Unbounded_String;
|
||||
address : vmint;
|
||||
opcode : byte;
|
||||
arg : vmint;
|
||||
j : Positive;
|
||||
begin
|
||||
while not End_Of_File (Current_Input) loop
|
||||
Get_Line (Current_Input, line);
|
||||
|
||||
j := 1;
|
||||
while j <= Length (line) and then Is_Space (Element (line, j))
|
||||
loop
|
||||
j := j + 1;
|
||||
end loop;
|
||||
|
||||
if j <= Length (line) then
|
||||
parse_instruction (To_String (line), address, opcode, arg);
|
||||
if opcode_takes_arg (opcode) then
|
||||
if code_size - 4 <= address then
|
||||
raise vm_limit_exceeded with "code space limit exceeded";
|
||||
end if;
|
||||
code (Integer (address)) := opcode;
|
||||
--
|
||||
-- Little-endian storage.
|
||||
--
|
||||
code (Integer (address) + 1) := byte (arg and 16#FF#);
|
||||
code (Integer (address) + 2) :=
|
||||
byte ((arg / 16#100#) and 16#FF#);
|
||||
code (Integer (address) + 3) :=
|
||||
byte ((arg / 16#1_0000#) and 16#FF#);
|
||||
code (Integer (address) + 4) :=
|
||||
byte ((arg / 16#100_0000#) and 16#FF#);
|
||||
else
|
||||
if code_size <= address then
|
||||
raise vm_limit_exceeded with "code space limit exceeded";
|
||||
end if;
|
||||
code (Integer (address)) := opcode;
|
||||
end if;
|
||||
end if;
|
||||
end loop;
|
||||
end read_parse_and_store_instructions;
|
||||
|
||||
procedure read_parse_and_store_program
|
||||
is
|
||||
data_count : vmint;
|
||||
strings_count : vmint;
|
||||
begin
|
||||
read_and_parse_header (data_count, strings_count);
|
||||
read_parse_and_store_strings (strings_count);
|
||||
read_parse_and_store_instructions;
|
||||
end read_parse_and_store_program;
|
||||
|
||||
procedure pop_value
|
||||
(x : out vmint)
|
||||
is
|
||||
begin
|
||||
if sp = 0 then
|
||||
raise vm_runtime_error with "stack underflow";
|
||||
end if;
|
||||
sp := sp - 1;
|
||||
x := stack (Integer (sp));
|
||||
end pop_value;
|
||||
|
||||
procedure push_value
|
||||
(x : in vmint)
|
||||
is
|
||||
begin
|
||||
if stack_size <= sp then
|
||||
raise vm_runtime_error with "stack overflow";
|
||||
end if;
|
||||
stack (Integer (sp)) := x;
|
||||
sp := sp + 1;
|
||||
end push_value;
|
||||
|
||||
procedure get_value
|
||||
(x : out vmint)
|
||||
is
|
||||
begin
|
||||
if sp = 0 then
|
||||
raise vm_runtime_error with "stack underflow";
|
||||
end if;
|
||||
x := stack (Integer (sp) - 1);
|
||||
end get_value;
|
||||
|
||||
procedure put_value
|
||||
(x : in vmint)
|
||||
is
|
||||
begin
|
||||
if sp = 0 then
|
||||
raise vm_runtime_error with "stack underflow";
|
||||
end if;
|
||||
stack (Integer (sp) - 1) := x;
|
||||
end put_value;
|
||||
|
||||
procedure fetch_value
|
||||
(i : in vmint;
|
||||
x : out vmint)
|
||||
is
|
||||
begin
|
||||
if data_size <= i then
|
||||
raise vm_runtime_error with "data boundary exceeded";
|
||||
end if;
|
||||
x := data (Integer (i));
|
||||
end fetch_value;
|
||||
|
||||
procedure store_value
|
||||
(i : in vmint;
|
||||
x : in vmint)
|
||||
is
|
||||
begin
|
||||
if data_size <= i then
|
||||
raise vm_runtime_error with "data boundary exceeded";
|
||||
end if;
|
||||
data (Integer (i)) := x;
|
||||
end store_value;
|
||||
|
||||
procedure immediate_value
|
||||
(x : out vmint)
|
||||
is
|
||||
b0, b1, b2, b3 : vmint;
|
||||
begin
|
||||
if code_size - 4 <= pc then
|
||||
raise vm_runtime_error with "code boundary exceeded";
|
||||
end if;
|
||||
--
|
||||
-- Little-endian order.
|
||||
--
|
||||
b0 := vmint (code (Integer (pc)));
|
||||
b1 := vmint (code (Integer (pc) + 1));
|
||||
b2 := vmint (code (Integer (pc) + 2));
|
||||
b3 := vmint (code (Integer (pc) + 3));
|
||||
x :=
|
||||
b0 + (16#100# * b1) + (16#1_0000# * b2) + (16#100_0000# * b3);
|
||||
end immediate_value;
|
||||
|
||||
procedure machine_add
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
put_value (x + y);
|
||||
end machine_add;
|
||||
|
||||
procedure machine_sub
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
put_value (x - y);
|
||||
end machine_sub;
|
||||
|
||||
procedure machine_mul
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
put_value
|
||||
(vmsigned_to_vmint
|
||||
(vmint_to_vmsigned (x) * vmint_to_vmsigned (y)));
|
||||
end machine_mul;
|
||||
|
||||
procedure machine_div
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
put_value
|
||||
(vmsigned_to_vmint
|
||||
(vmint_to_vmsigned (x) / vmint_to_vmsigned (y)));
|
||||
end machine_div;
|
||||
|
||||
procedure machine_mod
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
put_value
|
||||
(vmsigned_to_vmint
|
||||
(vmint_to_vmsigned (x) rem vmint_to_vmsigned (y)));
|
||||
end machine_mod;
|
||||
|
||||
procedure machine_lt
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if vmint_to_vmsigned (x) < vmint_to_vmsigned (y) then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_lt;
|
||||
|
||||
procedure machine_gt
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if vmint_to_vmsigned (x) > vmint_to_vmsigned (y) then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_gt;
|
||||
|
||||
procedure machine_le
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if vmint_to_vmsigned (x) <= vmint_to_vmsigned (y) then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_le;
|
||||
|
||||
procedure machine_ge
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if vmint_to_vmsigned (x) >= vmint_to_vmsigned (y) then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_ge;
|
||||
|
||||
procedure machine_eq
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if x = y then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_eq;
|
||||
|
||||
procedure machine_ne
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if x /= y then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_ne;
|
||||
|
||||
procedure machine_and
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if x /= 0 and y /= 0 then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_and;
|
||||
|
||||
procedure machine_or
|
||||
is
|
||||
x, y : vmint;
|
||||
begin
|
||||
pop_value (y);
|
||||
get_value (x);
|
||||
if x /= 0 or y /= 0 then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_or;
|
||||
|
||||
procedure machine_neg
|
||||
is
|
||||
x : vmint;
|
||||
begin
|
||||
get_value (x);
|
||||
put_value (twos_complement (x));
|
||||
end machine_neg;
|
||||
|
||||
procedure machine_not
|
||||
is
|
||||
x : vmint;
|
||||
begin
|
||||
get_value (x);
|
||||
if x = 0 then
|
||||
put_value (1);
|
||||
else
|
||||
put_value (0);
|
||||
end if;
|
||||
end machine_not;
|
||||
|
||||
procedure machine_prtc
|
||||
is
|
||||
x : vmint;
|
||||
begin
|
||||
pop_value (x);
|
||||
Character'Write (output_stream, Character'Val (x));
|
||||
end machine_prtc;
|
||||
|
||||
procedure machine_prti
|
||||
is
|
||||
x : vmint;
|
||||
begin
|
||||
pop_value (x);
|
||||
if 16#7FFF_FFFF# < x then
|
||||
Character'Write (output_stream, '-');
|
||||
String'Write
|
||||
(output_stream,
|
||||
To_String (vmint_to_digits (twos_complement (x))));
|
||||
else
|
||||
String'Write (output_stream, To_String (vmint_to_digits (x)));
|
||||
end if;
|
||||
end machine_prti;
|
||||
|
||||
procedure machine_prts
|
||||
is
|
||||
k : vmint;
|
||||
begin
|
||||
pop_value (k);
|
||||
if strings_size <= k then
|
||||
raise vm_runtime_error with "strings boundary exceeded";
|
||||
end if;
|
||||
String'Write (output_stream, To_String (strings (Integer (k))));
|
||||
end machine_prts;
|
||||
|
||||
procedure machine_fetch
|
||||
is
|
||||
k : vmint;
|
||||
x : vmint;
|
||||
begin
|
||||
immediate_value (k);
|
||||
fetch_value (k, x);
|
||||
push_value (x);
|
||||
pc := pc + 4;
|
||||
end machine_fetch;
|
||||
|
||||
procedure machine_store
|
||||
is
|
||||
k : vmint;
|
||||
x : vmint;
|
||||
begin
|
||||
immediate_value (k);
|
||||
pop_value (x);
|
||||
store_value (k, x);
|
||||
pc := pc + 4;
|
||||
end machine_store;
|
||||
|
||||
procedure machine_push
|
||||
is
|
||||
x : vmint;
|
||||
begin
|
||||
immediate_value (x);
|
||||
push_value (x);
|
||||
pc := pc + 4;
|
||||
end machine_push;
|
||||
|
||||
procedure machine_jmp
|
||||
is
|
||||
offset : vmint;
|
||||
begin
|
||||
immediate_value (offset);
|
||||
pc := pc + offset;
|
||||
end machine_jmp;
|
||||
|
||||
procedure machine_jz
|
||||
is
|
||||
x : vmint;
|
||||
offset : vmint;
|
||||
begin
|
||||
pop_value (x);
|
||||
if x = 0 then
|
||||
immediate_value (offset);
|
||||
pc := pc + offset;
|
||||
else
|
||||
pc := pc + 4;
|
||||
end if;
|
||||
end machine_jz;
|
||||
|
||||
procedure machine_step
|
||||
(halt : out Boolean)
|
||||
is
|
||||
opcode : byte;
|
||||
op_div_4, op_rem_4 : byte;
|
||||
begin
|
||||
if code_size <= pc then
|
||||
raise vm_runtime_error with "code boundary exceeded";
|
||||
end if;
|
||||
opcode := code (Integer (pc));
|
||||
pc := pc + 1;
|
||||
halt := False;
|
||||
op_div_4 := opcode / 4;
|
||||
op_rem_4 := opcode rem 4;
|
||||
if op_div_4 = 0 then
|
||||
if op_rem_4 = 0 then
|
||||
halt := True;
|
||||
elsif op_rem_4 = 1 then
|
||||
machine_add;
|
||||
elsif op_rem_4 = 2 then
|
||||
machine_sub;
|
||||
else
|
||||
machine_mul;
|
||||
end if;
|
||||
elsif op_div_4 = 1 then
|
||||
if op_rem_4 = 0 then
|
||||
machine_div;
|
||||
elsif op_rem_4 = 1 then
|
||||
machine_mod;
|
||||
elsif op_rem_4 = 2 then
|
||||
machine_lt;
|
||||
else
|
||||
machine_gt;
|
||||
end if;
|
||||
elsif op_div_4 = 2 then
|
||||
if op_rem_4 = 0 then
|
||||
machine_le;
|
||||
elsif op_rem_4 = 1 then
|
||||
machine_ge;
|
||||
elsif op_rem_4 = 2 then
|
||||
machine_eq;
|
||||
else
|
||||
machine_ne;
|
||||
end if;
|
||||
elsif op_div_4 = 3 then
|
||||
if op_rem_4 = 0 then
|
||||
machine_and;
|
||||
elsif op_rem_4 = 1 then
|
||||
machine_or;
|
||||
elsif op_rem_4 = 2 then
|
||||
machine_neg;
|
||||
else
|
||||
machine_not;
|
||||
end if;
|
||||
elsif op_div_4 = 4 then
|
||||
if op_rem_4 = 0 then
|
||||
machine_prtc;
|
||||
elsif op_rem_4 = 1 then
|
||||
machine_prti;
|
||||
elsif op_rem_4 = 2 then
|
||||
machine_prts;
|
||||
else
|
||||
machine_fetch;
|
||||
end if;
|
||||
elsif op_div_4 = 5 then
|
||||
if op_rem_4 = 0 then
|
||||
machine_store;
|
||||
elsif op_rem_4 = 1 then
|
||||
machine_push;
|
||||
elsif op_rem_4 = 2 then
|
||||
machine_jmp;
|
||||
else
|
||||
machine_jz;
|
||||
end if;
|
||||
else
|
||||
-- Treat anything unrecognized as equivalent to a halt.
|
||||
halt := True;
|
||||
end if;
|
||||
end machine_step;
|
||||
|
||||
procedure machine_continue
|
||||
is
|
||||
halt : Boolean;
|
||||
begin
|
||||
halt := False;
|
||||
while not halt loop
|
||||
machine_step (halt);
|
||||
end loop;
|
||||
end machine_continue;
|
||||
|
||||
procedure machine_run
|
||||
is
|
||||
begin
|
||||
sp := 0;
|
||||
pc := 0;
|
||||
for i in data'Range loop
|
||||
data (i) := 0;
|
||||
end loop;
|
||||
machine_continue;
|
||||
end machine_run;
|
||||
|
||||
begin
|
||||
status := 0;
|
||||
|
||||
input_file_name := To_Unbounded_String ("-");
|
||||
|
||||
if Argument_Count = 0 then
|
||||
null;
|
||||
elsif Argument_Count = 1 then
|
||||
input_file_name := To_Unbounded_String (Argument (1));
|
||||
else
|
||||
Put ("Usage: ");
|
||||
Put (Command_Name);
|
||||
Put_Line (" [INPUTFILE]");
|
||||
Put ("If either INPUTFILE is missing or ""-"",");
|
||||
Put_Line (" standard input is used.");
|
||||
Put_Line ("Output is always to standard output.");
|
||||
status := 1;
|
||||
end if;
|
||||
|
||||
if status = 0 then
|
||||
if input_file_name /= "-" then
|
||||
Open (input_file, In_File, To_String (input_file_name));
|
||||
Set_Input (input_file);
|
||||
end if;
|
||||
|
||||
output_stream := Stream (Current_Output);
|
||||
read_parse_and_store_program;
|
||||
machine_run;
|
||||
|
||||
if input_file_name /= "-" then
|
||||
Set_Input (Standard_Input);
|
||||
Close (input_file);
|
||||
end if;
|
||||
end if;
|
||||
|
||||
Set_Exit_Status (status);
|
||||
end VM;
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
integer n, pc, sp;
|
||||
file f;
|
||||
text s;
|
||||
index code, Data;
|
||||
list l, stack, strings;
|
||||
|
||||
f.affix(argv(1));
|
||||
|
||||
f.list(l, 0);
|
||||
|
||||
n = atoi(l[-1]);
|
||||
while (n) {
|
||||
f.lead(s);
|
||||
strings.append(erase(s, -1, 0));
|
||||
n -= 1;
|
||||
}
|
||||
|
||||
while (f.list(l, 0) ^ -1) {
|
||||
code.put(atoi(lf_x_text(l)), l);
|
||||
}
|
||||
|
||||
pc = sp = 0;
|
||||
while (1) {
|
||||
l = code[pc];
|
||||
s = l[0];
|
||||
if (s == "jz") {
|
||||
if (lb_pick(stack)) {
|
||||
isk_greater(code, pc, pc);
|
||||
} else {
|
||||
pc = atoi(l[-1]);
|
||||
}
|
||||
} elif (s == "jmp") {
|
||||
pc = atoi(l[-1]);
|
||||
} else {
|
||||
if (s == "push") {
|
||||
lb_push(stack, atoi(l[1]));
|
||||
} elif (s == "fetch") {
|
||||
lb_push(stack, Data[atoi(erase(l[1], -1, 0))]);
|
||||
} elif (s == "neg") {
|
||||
stack[-1] = -stack[-1];
|
||||
} elif (s == "not") {
|
||||
stack[-1] = !stack[-1];
|
||||
} elif (s == "halt") {
|
||||
break;
|
||||
} else {
|
||||
n = lb_pick(stack);
|
||||
if (s == "store") {
|
||||
Data[atoi(erase(l[1], -1, 0))] = n;
|
||||
} elif (s == "add") {
|
||||
stack[-1] = stack[-1] + n;
|
||||
} elif (s == "sub") {
|
||||
stack[-1] = stack[-1] - n;
|
||||
} elif (s == "mul") {
|
||||
stack[-1] = stack[-1] * n;
|
||||
} elif (s == "div") {
|
||||
stack[-1] = stack[-1] / n;
|
||||
} elif (s == "mod") {
|
||||
stack[-1] = stack[-1] % n;
|
||||
} elif (s == "lt") {
|
||||
stack[-1] = stack[-1] < n;
|
||||
} elif (s == "gt") {
|
||||
stack[-1] = stack[-1] > n;
|
||||
} elif (s == "le") {
|
||||
stack[-1] = stack[-1] <= n;
|
||||
} elif (s == "ge") {
|
||||
stack[-1] = stack[-1] >= n;
|
||||
} elif (s == "eq") {
|
||||
stack[-1] = stack[-1] == n;
|
||||
} elif (s == "ne") {
|
||||
stack[-1] = stack[-1] != n;
|
||||
} elif (s == "and") {
|
||||
stack[-1] = stack[-1] && n;
|
||||
} elif (s == "or") {
|
||||
stack[-1] = stack[-1] || n;
|
||||
} elif (s == "prtc") {
|
||||
o_byte(n);
|
||||
} elif (s == "prti") {
|
||||
o_(n);
|
||||
} elif (s == "prts") {
|
||||
o_(strings[n]);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
isk_greater(code, pc, pc);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
|
||||
#define da_dim(name, type) type *name = NULL; \
|
||||
int _qy_ ## name ## _p = 0; \
|
||||
int _qy_ ## name ## _max = 0
|
||||
|
||||
#define da_redim(name) do {if (_qy_ ## name ## _p >= _qy_ ## name ## _max) \
|
||||
name = realloc(name, (_qy_ ## name ## _max += 32) * sizeof(name[0]));} while (0)
|
||||
|
||||
#define da_rewind(name) _qy_ ## name ## _p = 0
|
||||
|
||||
#define da_append(name, x) do {da_redim(name); name[_qy_ ## name ## _p++] = x;} while (0)
|
||||
|
||||
typedef unsigned char uchar;
|
||||
typedef uchar code;
|
||||
|
||||
typedef enum { FETCH, STORE, PUSH, ADD, SUB, MUL, DIV, MOD, LT, GT, LE, GE, EQ, NE, AND,
|
||||
OR, NEG, NOT, JMP, JZ, PRTC, PRTS, PRTI, HALT
|
||||
} Code_t;
|
||||
|
||||
typedef struct Code_map {
|
||||
char *text;
|
||||
Code_t op;
|
||||
} Code_map;
|
||||
|
||||
Code_map code_map[] = {
|
||||
{"fetch", FETCH},
|
||||
{"store", STORE},
|
||||
{"push", PUSH },
|
||||
{"add", ADD },
|
||||
{"sub", SUB },
|
||||
{"mul", MUL },
|
||||
{"div", DIV },
|
||||
{"mod", MOD },
|
||||
{"lt", LT },
|
||||
{"gt", GT },
|
||||
{"le", LE },
|
||||
{"ge", GE },
|
||||
{"eq", EQ },
|
||||
{"ne", NE },
|
||||
{"and", AND },
|
||||
{"or", OR },
|
||||
{"neg", NEG },
|
||||
{"not", NOT },
|
||||
{"jmp", JMP },
|
||||
{"jz", JZ },
|
||||
{"prtc", PRTC },
|
||||
{"prts", PRTS },
|
||||
{"prti", PRTI },
|
||||
{"halt", HALT },
|
||||
};
|
||||
|
||||
FILE *source_fp;
|
||||
da_dim(object, code);
|
||||
|
||||
void error(const char *fmt, ... ) {
|
||||
va_list ap;
|
||||
char buf[1000];
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsprintf(buf, fmt, ap);
|
||||
va_end(ap);
|
||||
printf("error: %s\n", buf);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*** Virtual Machine interpreter ***/
|
||||
void run_vm(const code obj[], int32_t data[], int g_size, char **string_pool) {
|
||||
int32_t *sp = &data[g_size + 1];
|
||||
const code *pc = obj;
|
||||
|
||||
again:
|
||||
switch (*pc++) {
|
||||
case FETCH: *sp++ = data[*(int32_t *)pc]; pc += sizeof(int32_t); goto again;
|
||||
case STORE: data[*(int32_t *)pc] = *--sp; pc += sizeof(int32_t); goto again;
|
||||
case PUSH: *sp++ = *(int32_t *)pc; pc += sizeof(int32_t); goto again;
|
||||
case ADD: sp[-2] += sp[-1]; --sp; goto again;
|
||||
case SUB: sp[-2] -= sp[-1]; --sp; goto again;
|
||||
case MUL: sp[-2] *= sp[-1]; --sp; goto again;
|
||||
case DIV: sp[-2] /= sp[-1]; --sp; goto again;
|
||||
case MOD: sp[-2] %= sp[-1]; --sp; goto again;
|
||||
case LT: sp[-2] = sp[-2] < sp[-1]; --sp; goto again;
|
||||
case GT: sp[-2] = sp[-2] > sp[-1]; --sp; goto again;
|
||||
case LE: sp[-2] = sp[-2] <= sp[-1]; --sp; goto again;
|
||||
case GE: sp[-2] = sp[-2] >= sp[-1]; --sp; goto again;
|
||||
case EQ: sp[-2] = sp[-2] == sp[-1]; --sp; goto again;
|
||||
case NE: sp[-2] = sp[-2] != sp[-1]; --sp; goto again;
|
||||
case AND: sp[-2] = sp[-2] && sp[-1]; --sp; goto again;
|
||||
case OR: sp[-2] = sp[-2] || sp[-1]; --sp; goto again;
|
||||
case NEG: sp[-1] = -sp[-1]; goto again;
|
||||
case NOT: sp[-1] = !sp[-1]; goto again;
|
||||
case JMP: pc += *(int32_t *)pc; goto again;
|
||||
case JZ: pc += (*--sp == 0) ? *(int32_t *)pc : (int32_t)sizeof(int32_t); goto again;
|
||||
case PRTC: printf("%c", sp[-1]); --sp; goto again;
|
||||
case PRTS: printf("%s", string_pool[sp[-1]]); --sp; goto again;
|
||||
case PRTI: printf("%d", sp[-1]); --sp; goto again;
|
||||
case HALT: break;
|
||||
default: error("Unknown opcode %d\n", *(pc - 1));
|
||||
}
|
||||
}
|
||||
|
||||
char *read_line(int *len) {
|
||||
static char *text = NULL;
|
||||
static int textmax = 0;
|
||||
|
||||
for (*len = 0; ; (*len)++) {
|
||||
int ch = fgetc(source_fp);
|
||||
if (ch == EOF || ch == '\n') {
|
||||
if (*len == 0)
|
||||
return NULL;
|
||||
break;
|
||||
}
|
||||
if (*len + 1 >= textmax) {
|
||||
textmax = (textmax == 0 ? 128 : textmax * 2);
|
||||
text = realloc(text, textmax);
|
||||
}
|
||||
text[*len] = ch;
|
||||
}
|
||||
text[*len] = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
char *rtrim(char *text, int *len) { // remove trailing spaces
|
||||
for (; *len > 0 && isspace(text[*len - 1]); --(*len))
|
||||
;
|
||||
|
||||
text[*len] = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
char *translate(char *st) {
|
||||
char *p, *q;
|
||||
if (st[0] == '"') // skip leading " if there
|
||||
++st;
|
||||
p = q = st;
|
||||
|
||||
while ((*p++ = *q++) != '\0') {
|
||||
if (q[-1] == '\\') {
|
||||
if (q[0] == 'n') {
|
||||
p[-1] = '\n';
|
||||
++q;
|
||||
} else if (q[0] == '\\') {
|
||||
++q;
|
||||
}
|
||||
}
|
||||
if (q[0] == '"' && q[1] == '\0') // skip trialing " if there
|
||||
++q;
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
/* convert an opcode string into its byte value */
|
||||
int findit(const char text[], int offset) {
|
||||
for (size_t i = 0; i < sizeof(code_map) / sizeof(code_map[0]); i++) {
|
||||
if (strcmp(code_map[i].text, text) == 0)
|
||||
return code_map[i].op;
|
||||
}
|
||||
error("Unknown instruction %s at %d\n", text, offset);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void emit_byte(int c) {
|
||||
da_append(object, (uchar)c);
|
||||
}
|
||||
|
||||
void emit_int(int32_t n) {
|
||||
union {
|
||||
int32_t n;
|
||||
unsigned char c[sizeof(int32_t)];
|
||||
} x;
|
||||
|
||||
x.n = n;
|
||||
|
||||
for (size_t i = 0; i < sizeof(x.n); ++i) {
|
||||
emit_byte(x.c[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Datasize: 5 Strings: 3
|
||||
" is prime\n"
|
||||
"Total primes found: "
|
||||
"\n"
|
||||
154 jmp (-73) 82
|
||||
164 jz (32) 197
|
||||
175 push 0
|
||||
159 fetch [4]
|
||||
149 store [3]
|
||||
*/
|
||||
|
||||
/* Load code into global array object, return the string pool and data size */
|
||||
char **load_code(int *ds) {
|
||||
int line_len, n_strings;
|
||||
char **string_pool;
|
||||
char *text = read_line(&line_len);
|
||||
text = rtrim(text, &line_len);
|
||||
|
||||
strtok(text, " "); // skip "Datasize:"
|
||||
*ds = atoi(strtok(NULL, " ")); // get actual data_size
|
||||
strtok(NULL, " "); // skip "Strings:"
|
||||
n_strings = atoi(strtok(NULL, " ")); // get number of strings
|
||||
|
||||
string_pool = malloc(n_strings * sizeof(char *));
|
||||
for (int i = 0; i < n_strings; ++i) {
|
||||
text = read_line(&line_len);
|
||||
text = rtrim(text, &line_len);
|
||||
text = translate(text);
|
||||
string_pool[i] = strdup(text);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int len;
|
||||
|
||||
text = read_line(&line_len);
|
||||
if (text == NULL)
|
||||
break;
|
||||
text = rtrim(text, &line_len);
|
||||
|
||||
int offset = atoi(strtok(text, " ")); // get the offset
|
||||
char *instr = strtok(NULL, " "); // get the instruction
|
||||
int opcode = findit(instr, offset);
|
||||
emit_byte(opcode);
|
||||
char *operand = strtok(NULL, " ");
|
||||
|
||||
switch (opcode) {
|
||||
case JMP: case JZ:
|
||||
operand++; // skip the '('
|
||||
len = strlen(operand);
|
||||
operand[len - 1] = '\0'; // remove the ')'
|
||||
emit_int(atoi(operand));
|
||||
break;
|
||||
case PUSH:
|
||||
emit_int(atoi(operand));
|
||||
break;
|
||||
case FETCH: case STORE:
|
||||
operand++; // skip the '['
|
||||
len = strlen(operand);
|
||||
operand[len - 1] = '\0'; // remove the ']'
|
||||
emit_int(atoi(operand));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return string_pool;
|
||||
}
|
||||
|
||||
void init_io(FILE **fp, FILE *std, const char mode[], const char fn[]) {
|
||||
if (fn[0] == '\0')
|
||||
*fp = std;
|
||||
else if ((*fp = fopen(fn, mode)) == NULL)
|
||||
error(0, 0, "Can't open %s\n", fn);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
init_io(&source_fp, stdin, "r", argc > 1 ? argv[1] : "");
|
||||
int data_size;
|
||||
char **string_pool = load_code(&data_size);
|
||||
int data[1000 + data_size];
|
||||
run_vm(object, data, data_size, string_pool);
|
||||
}
|
||||
|
|
@ -0,0 +1,422 @@
|
|||
>>SOURCE FORMAT IS FREE
|
||||
identification division.
|
||||
*> this code is dedicated to the public domain
|
||||
*> (GnuCOBOL) 2.3-dev.0
|
||||
program-id. vminterpreter.
|
||||
environment division.
|
||||
configuration section.
|
||||
repository. function all intrinsic.
|
||||
input-output section.
|
||||
file-control.
|
||||
select input-file assign using input-name
|
||||
status is input-status
|
||||
organization is line sequential.
|
||||
data division.
|
||||
|
||||
file section.
|
||||
fd input-file.
|
||||
01 input-record pic x(64).
|
||||
|
||||
working-storage section.
|
||||
01 program-name pic x(32).
|
||||
01 input-name pic x(32).
|
||||
01 input-status pic xx.
|
||||
|
||||
01 error-record pic x(64) value spaces global.
|
||||
|
||||
01 v-max pic 99.
|
||||
01 parameters.
|
||||
03 offset pic 999.
|
||||
03 opcode pic x(8).
|
||||
03 parm0 pic x(16).
|
||||
03 parm1 pic x(16).
|
||||
03 parm2 pic x(16).
|
||||
|
||||
01 opcodes.
|
||||
03 opFETCH pic x value x'00'.
|
||||
03 opSTORE pic x value x'01'.
|
||||
03 opPUSH pic x value x'02'.
|
||||
03 opADD pic x value x'03'.
|
||||
03 opSUB pic x value x'04'.
|
||||
03 opMUL pic x value x'05'.
|
||||
03 opDIV pic x value x'06'.
|
||||
03 opMOD pic x value x'07'.
|
||||
03 opLT pic x value x'08'.
|
||||
03 opGT pic x value x'09'.
|
||||
03 opLE pic x value x'0A'.
|
||||
03 opGE pic x value x'0B'.
|
||||
03 opEQ pic x value x'0C'.
|
||||
03 opNE pic x value x'0D'.
|
||||
03 opAND pic x value x'0E'.
|
||||
03 opOR pic x value x'0F'.
|
||||
03 opNEG pic x value x'10'.
|
||||
03 opNOT pic x value x'11'.
|
||||
03 opJMP pic x value x'13'.
|
||||
03 opJZ pic x value x'14'.
|
||||
03 opPRTC pic x value x'15'.
|
||||
03 opPRTS pic x value x'16'.
|
||||
03 opPRTI pic x value x'17'.
|
||||
03 opHALT pic x value x'18'.
|
||||
|
||||
01 filler.
|
||||
03 s pic 99.
|
||||
03 s-max pic 99 value 0.
|
||||
03 s-lim pic 99 value 16.
|
||||
03 filler occurs 16.
|
||||
05 string-length pic 99.
|
||||
05 string-entry pic x(48).
|
||||
|
||||
01 filler.
|
||||
03 v pic 99.
|
||||
03 v-lim pic 99 value 16.
|
||||
03 variables occurs 16 usage binary-int.
|
||||
|
||||
01 generated-code global.
|
||||
03 c pic 999 value 1.
|
||||
03 pc pic 999.
|
||||
03 c-lim pic 999 value 512.
|
||||
03 kode pic x(512).
|
||||
|
||||
01 filler.
|
||||
03 stack1 pic 999 value 2.
|
||||
03 stack2 pic 999 value 1.
|
||||
03 stack-lim pic 999 value 998.
|
||||
03 stack occurs 998 usage binary-int.
|
||||
|
||||
01 display-definitions global.
|
||||
03 ascii-character.
|
||||
05 numeric-value usage binary-char.
|
||||
03 display-integer pic -(9)9.
|
||||
03 word-x.
|
||||
05 word usage binary-int.
|
||||
03 word-length pic 9.
|
||||
03 string1 pic 99.
|
||||
03 length1 pic 99.
|
||||
03 count1 pic 99.
|
||||
03 display-pending pic x.
|
||||
|
||||
procedure division.
|
||||
start-vminterpreter.
|
||||
display 1 upon command-line *> get arg(1)
|
||||
accept program-name from argument-value
|
||||
move length(word) to word-length
|
||||
perform load-code
|
||||
perform run-code
|
||||
stop run
|
||||
.
|
||||
run-code.
|
||||
move 1 to pc
|
||||
perform until pc >= c
|
||||
evaluate kode(pc:1)
|
||||
when opFETCH
|
||||
perform push-stack
|
||||
move kode(pc + 1:word-length) to word-x
|
||||
add 1 to word *> convert offset to subscript
|
||||
move variables(word) to stack(stack1)
|
||||
add word-length to pc
|
||||
when opPUSH
|
||||
perform push-stack
|
||||
move kode(pc + 1:word-length) to word-x
|
||||
move word to stack(stack1)
|
||||
add word-length to pc
|
||||
when opNEG
|
||||
compute stack(stack1) = -stack(stack1)
|
||||
when opNOT
|
||||
if stack(stack1) = 0
|
||||
move 1 to stack(stack1)
|
||||
else
|
||||
move 0 to stack(stack1)
|
||||
end-if
|
||||
when opJMP
|
||||
move kode(pc + 1:word-length) to word-x
|
||||
move word to pc
|
||||
when opHALT
|
||||
if display-pending = 'Y'
|
||||
display space
|
||||
end-if
|
||||
exit perform
|
||||
when opJZ
|
||||
if stack(stack1) = 0
|
||||
move kode(pc + 1:word-length) to word-x
|
||||
move word to pc
|
||||
else
|
||||
add word-length to pc
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opSTORE
|
||||
move kode(pc + 1:word-length) to word-x
|
||||
add 1 to word *> convert offset to subscript
|
||||
move stack(stack1) to variables(word)
|
||||
add word-length to pc
|
||||
perform pop-stack
|
||||
when opADD
|
||||
add stack(stack1) to stack(stack2)
|
||||
perform pop-stack
|
||||
when opSUB
|
||||
subtract stack(stack1) from stack(stack2)
|
||||
perform pop-stack
|
||||
when opMUL
|
||||
multiply stack(stack1) by stack(stack2)
|
||||
*>rounded mode nearest-toward-zero *> doesn't match python
|
||||
perform pop-stack
|
||||
when opDIV
|
||||
divide stack(stack1) into stack(stack2)
|
||||
*>rounded mode nearest-toward-zero *> doesn't match python
|
||||
perform pop-stack
|
||||
when opMOD
|
||||
move mod(stack(stack2),stack(stack1)) to stack(stack2)
|
||||
perform pop-stack
|
||||
when opLT
|
||||
if stack(stack2) < stack(stack1)
|
||||
move 1 to stack(stack2)
|
||||
else
|
||||
move 0 to stack(stack2)
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opGT
|
||||
if stack(stack2) > stack(stack1)
|
||||
move 1 to stack(stack2)
|
||||
else
|
||||
move 0 to stack(stack2)
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opLE
|
||||
if stack(stack2) <= stack(stack1)
|
||||
move 1 to stack(stack2)
|
||||
else
|
||||
move 0 to stack(stack2)
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opGE
|
||||
if stack(stack2) >= stack(stack1)
|
||||
move 1 to stack(stack2)
|
||||
else
|
||||
move 0 to stack(stack2)
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opEQ
|
||||
if stack(stack2) = stack(stack1)
|
||||
move 1 to stack(stack2)
|
||||
else
|
||||
move 0 to stack(stack2)
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opNE
|
||||
if stack(stack2) <> stack(stack1)
|
||||
move 1 to stack(stack2)
|
||||
else
|
||||
move 0 to stack(stack2)
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opAND
|
||||
call "CBL_AND" using stack(stack1) stack(stack2) by value word-length
|
||||
perform pop-stack
|
||||
when opOR
|
||||
call "CBL_OR" using stack(stack1) stack(stack2) by value word-length
|
||||
perform pop-stack
|
||||
when opPRTC
|
||||
move stack(stack1) to numeric-value
|
||||
if numeric-value = 10
|
||||
display space
|
||||
move 'N' to display-pending
|
||||
else
|
||||
display ascii-character with no advancing
|
||||
move 'Y' to display-pending
|
||||
end-if
|
||||
perform pop-stack
|
||||
when opPRTS
|
||||
add 1 to word *> convert offset to subscript
|
||||
move 1 to string1
|
||||
move string-length(word) to length1
|
||||
perform until string1 > string-length(word)
|
||||
move 0 to count1
|
||||
inspect string-entry(word)(string1:length1)
|
||||
tallying count1 for characters before initial '\' *> ' workaround code highlighter problem
|
||||
evaluate true
|
||||
when string-entry(word)(string1 + count1 + 1:1) = 'n' *> \n
|
||||
display string-entry(word)(string1:count1)
|
||||
move 'N' to display-pending
|
||||
compute string1 = string1 + 2 + count1
|
||||
compute length1 = length1 - 2 - count1
|
||||
when string-entry(word)(string1 + count1 + 1:1) = '\' *> ' \\
|
||||
display string-entry(word)(string1:count1 + 1) with no advancing
|
||||
move 'Y' to display-pending
|
||||
compute string1 = string1 + 2 + count1
|
||||
compute length1 = length1 - 2 - count1
|
||||
when other
|
||||
display string-entry(word)(string1:count1) with no advancing
|
||||
move 'Y' to display-pending
|
||||
add count1 to string1
|
||||
subtract count1 from length1
|
||||
end-evaluate
|
||||
end-perform
|
||||
perform pop-stack
|
||||
when opPRTI
|
||||
move stack(stack1) to display-integer
|
||||
display trim(display-integer) with no advancing
|
||||
move 'Y' to display-pending
|
||||
perform pop-stack
|
||||
end-evaluate
|
||||
add 1 to pc
|
||||
end-perform
|
||||
.
|
||||
push-stack.
|
||||
if stack1 >= stack-lim
|
||||
string 'in vminterpreter at ' pc ' stack overflow at ' stack-lim into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
add 1 to stack1 stack2
|
||||
>>d display ' push at ' pc space stack1 space stack2
|
||||
.
|
||||
pop-stack.
|
||||
if stack1 < 2
|
||||
string 'in vminterpreter at ' pc ' stack underflow' into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
>>d display ' pop at ' pc space stack1 space stack2
|
||||
subtract 1 from stack1 stack2
|
||||
.
|
||||
load-code.
|
||||
perform read-input
|
||||
if input-status <> '00'
|
||||
string 'in vminterpreter no input data' into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
|
||||
unstring input-record delimited by all spaces into parm1 v-max parm2 s-max
|
||||
if v-max > v-lim
|
||||
string 'in vminterpreter datasize exceeds ' v-lim into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
if s-max > s-lim
|
||||
string 'in vminterpreter number of strings exceeds ' s-lim into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
|
||||
perform read-input
|
||||
perform varying s from 1 by 1 until s > s-max
|
||||
or input-status <> '00'
|
||||
compute string-length(s) string-length(word) = length(trim(input-record)) - 2
|
||||
move input-record(2:string-length(word)) to string-entry(s)
|
||||
perform read-input
|
||||
end-perform
|
||||
if s <= s-max
|
||||
string 'in vminterpreter not all strings found' into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
|
||||
perform until input-status <> '00'
|
||||
initialize parameters
|
||||
unstring input-record delimited by all spaces into
|
||||
parm0 offset opcode parm1 parm2
|
||||
evaluate opcode
|
||||
when 'fetch'
|
||||
call 'emitbyte' using opFETCH
|
||||
call 'emitword' using parm1
|
||||
when 'store'
|
||||
call 'emitbyte' using opSTORE
|
||||
call 'emitword' using parm1
|
||||
when 'push'
|
||||
call 'emitbyte' using opPUSH
|
||||
call 'emitword' using parm1
|
||||
when 'add' call 'emitbyte' using opADD
|
||||
when 'sub' call 'emitbyte' using opSUB
|
||||
when 'mul' call 'emitbyte' using opMUL
|
||||
when 'div' call 'emitbyte' using opDIV
|
||||
when 'mod' call 'emitbyte' using opMOD
|
||||
when 'lt' call 'emitbyte' using opLT
|
||||
when 'gt' call 'emitbyte' using opGT
|
||||
when 'le' call 'emitbyte' using opLE
|
||||
when 'ge' call 'emitbyte' using opGE
|
||||
when 'eq' call 'emitbyte' using opEQ
|
||||
when 'ne' call 'emitbyte' using opNE
|
||||
when 'and' call 'emitbyte' using opAND
|
||||
when 'or' call 'emitbyte' using opOR
|
||||
when 'not' call 'emitbyte' using opNOT
|
||||
when 'neg' call 'emitbyte' using opNEG
|
||||
when 'jmp'
|
||||
call 'emitbyte' using opJMP
|
||||
call 'emitword' using parm2
|
||||
when 'jz'
|
||||
call 'emitbyte' using opJZ
|
||||
call 'emitword' using parm2
|
||||
when 'prtc' call 'emitbyte' using opPRTC
|
||||
when 'prts' call 'emitbyte' using opPRTS
|
||||
when 'prti' call 'emitbyte' using opPRTI
|
||||
when 'halt' call 'emitbyte' using opHALT
|
||||
when other
|
||||
string 'in vminterpreter unknown opcode ' trim(opcode) ' at ' offset into error-record
|
||||
perform report-error
|
||||
end-evaluate
|
||||
perform read-input
|
||||
end-perform
|
||||
.
|
||||
read-input.
|
||||
if program-name = spaces
|
||||
move '00' to input-status
|
||||
accept input-record on exception move '10' to input-status end-accept
|
||||
exit paragraph
|
||||
end-if
|
||||
if input-name = spaces
|
||||
string program-name delimited by space '.gen' into input-name
|
||||
open input input-file
|
||||
if input-status <> '00'
|
||||
string 'in vminterpreter ' trim(input-name) ' file open status ' input-status
|
||||
into error-record
|
||||
perform report-error
|
||||
end-if
|
||||
end-if
|
||||
read input-file into input-record
|
||||
evaluate input-status
|
||||
when '00'
|
||||
continue
|
||||
when '10'
|
||||
close input-file
|
||||
when other
|
||||
string 'in vminterpreter unexpected input-status: ' input-status into error-record
|
||||
perform report-error
|
||||
end-evaluate
|
||||
.
|
||||
report-error.
|
||||
display error-record upon syserr
|
||||
stop run with error status -1
|
||||
.
|
||||
identification division.
|
||||
program-id. emitbyte.
|
||||
data division.
|
||||
linkage section.
|
||||
01 opcode pic x.
|
||||
procedure division using opcode.
|
||||
start-emitbyte.
|
||||
if c >= c-lim
|
||||
string 'in vminterpreter emitbyte c exceeds ' c-lim into error-record
|
||||
call 'reporterror'
|
||||
end-if
|
||||
move opcode to kode(c:1)
|
||||
add 1 to c
|
||||
.
|
||||
end program emitbyte.
|
||||
|
||||
identification division.
|
||||
program-id. emitword.
|
||||
data division.
|
||||
working-storage section.
|
||||
01 word-temp pic x(8).
|
||||
linkage section.
|
||||
01 word-value any length.
|
||||
procedure division using word-value.
|
||||
start-emitword.
|
||||
if c + word-length >= c-lim
|
||||
string 'in vminterpreter emitword c exceeds ' c-lim into error-record
|
||||
call 'reporterror'
|
||||
end-if
|
||||
move word-value to word-temp
|
||||
inspect word-temp converting '[' to ' '
|
||||
inspect word-temp converting ']' to ' '
|
||||
move numval(trim(word-temp)) to word
|
||||
move word-x to kode(c:word-length)
|
||||
add word-length to c
|
||||
.
|
||||
end program emitword.
|
||||
|
||||
end program vminterpreter.
|
||||
|
|
@ -0,0 +1,725 @@
|
|||
#!/bin/sh
|
||||
#|-*- mode:lisp -*-|#
|
||||
#|
|
||||
exec ros -Q -- $0 "$@"
|
||||
|#
|
||||
(progn ;;init forms
|
||||
(ros:ensure-asdf)
|
||||
#+quicklisp(ql:quickload '() :silent t)
|
||||
)
|
||||
|
||||
(defpackage :ros.script.vm.3858678051
|
||||
(:use :cl))
|
||||
(in-package :ros.script.vm.3858678051)
|
||||
|
||||
;;;
|
||||
;;; The Rosetta Code Virtual Machine, in Common Lisp.
|
||||
;;;
|
||||
;;; Notes:
|
||||
;;;
|
||||
;;; * I have tried not to use foreign types or similar means of
|
||||
;;; optimization.
|
||||
;;;
|
||||
;;; * Integers are stored in the VM's executable memory in
|
||||
;;; big-endian order. Not because I prefer it, but because I do
|
||||
;;; not want to get myself into a little-endian rut.
|
||||
;;;
|
||||
|
||||
(require "cl-ppcre")
|
||||
(require "trivia")
|
||||
|
||||
;;; Yes, I could compute how much memory is needed, or I could assume
|
||||
;;; that the instructions are in address order. However, for *this*
|
||||
;;; implementation I am going to use a large fixed-size memory and use
|
||||
;;; the address fields of instructions to place the instructions.
|
||||
(defconstant executable-memory-size 65536
|
||||
"The size of memory for executable code, in 8-bit words.")
|
||||
|
||||
;;; Similarly, I am going to have fixed size data and stack memory.
|
||||
(defconstant data-memory-size 2048
|
||||
"The size of memory for stored data, in 32-bit words.")
|
||||
(defconstant stack-memory-size 2048
|
||||
"The size of memory for the stack, in 32-bit words.")
|
||||
|
||||
;;; And so I am going to have specialized types for the different
|
||||
;;; kinds of memory the platform contains. Also for its "word" and
|
||||
;;; register types.
|
||||
(deftype word ()
|
||||
'(unsigned-byte 32))
|
||||
(deftype register ()
|
||||
'(simple-array word (1)))
|
||||
(deftype executable-memory ()
|
||||
`(simple-array (unsigned-byte 8) ,(list executable-memory-size)))
|
||||
(deftype data-memory ()
|
||||
`(simple-array word ,(list data-memory-size)))
|
||||
(deftype stack-memory ()
|
||||
`(simple-array word ,(list stack-memory-size)))
|
||||
|
||||
(defconstant re-blank-line
|
||||
(ppcre:create-scanner "^\\s*$"))
|
||||
|
||||
(defconstant re-parse-instr-1
|
||||
(ppcre:create-scanner "^\\s*(\\d+)\\s*(.*\\S)"))
|
||||
|
||||
(defconstant re-parse-instr-2
|
||||
(ppcre:create-scanner "(?i)^(\\S+)\\s*(.*)"))
|
||||
|
||||
(defconstant re-parse-instr-3
|
||||
(ppcre:create-scanner "^[[(]?([0-9-]+)"))
|
||||
|
||||
(defconstant opcode-names
|
||||
#("halt"
|
||||
"add"
|
||||
"sub"
|
||||
"mul"
|
||||
"div"
|
||||
"mod"
|
||||
"lt"
|
||||
"gt"
|
||||
"le"
|
||||
"ge"
|
||||
"eq"
|
||||
"ne"
|
||||
"and"
|
||||
"or"
|
||||
"neg"
|
||||
"not"
|
||||
"prtc"
|
||||
"prti"
|
||||
"prts"
|
||||
"fetch"
|
||||
"store"
|
||||
"push"
|
||||
"jmp"
|
||||
"jz"))
|
||||
|
||||
(defun blank-line-p (s)
|
||||
(not (not (ppcre:scan re-blank-line s))))
|
||||
|
||||
(defun opcode-from-name (s)
|
||||
(position-if (lambda (name)
|
||||
(string= s name))
|
||||
opcode-names))
|
||||
|
||||
(defun create-executable-memory ()
|
||||
(coerce (make-list executable-memory-size
|
||||
:initial-element (opcode-from-name "halt"))
|
||||
'executable-memory))
|
||||
|
||||
(defun create-data-memory ()
|
||||
(coerce (make-list data-memory-size :initial-element 0)
|
||||
'data-memory))
|
||||
|
||||
(defun create-stack-memory ()
|
||||
(coerce (make-list stack-memory-size :initial-element 0)
|
||||
'stack-memory))
|
||||
|
||||
(defun create-register ()
|
||||
(coerce (make-list 1 :initial-element 0) 'register))
|
||||
|
||||
(defstruct machine
|
||||
(sp (create-register) :type register) ; Stack pointer.
|
||||
(ip (create-register) :type register) ; Instruction pointer (same
|
||||
; thing as program counter).
|
||||
(code (create-executable-memory) :type executable-memory)
|
||||
(data (create-data-memory) :type data-memory)
|
||||
(stack (create-stack-memory) :type stack-memory)
|
||||
(strings nil)
|
||||
output *standard-output*)
|
||||
|
||||
(defun insert-instruction (memory instr)
|
||||
(declare (type executable-memory memory))
|
||||
(trivia:match instr
|
||||
((list address opcode arg)
|
||||
(let ((instr-size (if arg 5 1)))
|
||||
(unless (<= (+ address instr-size) executable-memory-size)
|
||||
(warn "the VM's executable memory size is exceeded")
|
||||
(uiop:quit 1))
|
||||
(setf (elt memory address) opcode)
|
||||
(when arg
|
||||
;; Big-endian order.
|
||||
(setf (elt memory (+ address 1)) (ldb (byte 8 24) arg))
|
||||
(setf (elt memory (+ address 2)) (ldb (byte 8 16) arg))
|
||||
(setf (elt memory (+ address 3)) (ldb (byte 8 8) arg))
|
||||
(setf (elt memory (+ address 4)) (ldb (byte 8 0) arg)))))))
|
||||
|
||||
(defun load-executable-memory (memory instr-lst)
|
||||
(declare (type executable-memory memory))
|
||||
(loop for instr in instr-lst
|
||||
do (insert-instruction memory instr)))
|
||||
|
||||
(defun parse-instruction (s)
|
||||
(if (blank-line-p s)
|
||||
nil
|
||||
(let* ((strings (nth-value 1 (ppcre:scan-to-strings
|
||||
re-parse-instr-1 s)))
|
||||
(address (parse-integer (elt strings 0)))
|
||||
(split (nth-value 1 (ppcre:scan-to-strings
|
||||
re-parse-instr-2 (elt strings 1))))
|
||||
(opcode-name (string-downcase (elt split 0)))
|
||||
(opcode (opcode-from-name opcode-name))
|
||||
(arguments (elt split 1))
|
||||
(has-arg (trivia:match opcode-name
|
||||
((or "fetch" "store" "push" "jmp" "jz") t)
|
||||
(_ nil))))
|
||||
(if has-arg
|
||||
(let* ((argstr-lst
|
||||
(nth-value 1 (ppcre:scan-to-strings
|
||||
re-parse-instr-3 arguments)))
|
||||
(argstr (elt argstr-lst 0)))
|
||||
`(,address ,opcode ,(parse-integer argstr)))
|
||||
`(,address ,opcode ())))))
|
||||
|
||||
(defun read-instructions (inpf)
|
||||
(loop for line = (read-line inpf nil 'eoi)
|
||||
until (eq line 'eoi)
|
||||
for instr = (parse-instruction line)
|
||||
when instr collect instr))
|
||||
|
||||
(defun read-datasize-and-strings-count (inpf)
|
||||
(let ((line (read-line inpf)))
|
||||
(multiple-value-bind (_whole-match strings)
|
||||
;; This is a permissive implementation.
|
||||
(ppcre:scan-to-strings
|
||||
"(?i)^\\s*Datasize\\s*:\\s*(\\d+)\\s*Strings\\s*:\\s*(\\d+)"
|
||||
line)
|
||||
(declare (ignore _whole-match))
|
||||
`(,(parse-integer (elt strings 0))
|
||||
,(parse-integer (elt strings 1))))))
|
||||
|
||||
(defun parse-string-literal (s)
|
||||
;; This is a permissive implementation, but only in that it skips
|
||||
;; any leading space. It does not check carefully for outright
|
||||
;; mistakes.
|
||||
(let* ((s (ppcre:regex-replace "^\\s*" s ""))
|
||||
(quote-mark (elt s 0))
|
||||
(i 1)
|
||||
(lst
|
||||
(loop until (char= (elt s i) quote-mark)
|
||||
collect (let ((c (elt s i)))
|
||||
(if (char= c #\\)
|
||||
(let ((c0 (trivia:match (elt s (1+ i))
|
||||
(#\n #\newline)
|
||||
(c1 c1))))
|
||||
(setq i (+ i 2))
|
||||
c0)
|
||||
(progn
|
||||
(setq i (1+ i))
|
||||
c))))))
|
||||
(coerce lst 'string)))
|
||||
|
||||
(defun read-string-literals (inpf strings-count)
|
||||
(loop for i from 1 to strings-count
|
||||
collect (parse-string-literal (read-line inpf))))
|
||||
|
||||
(defun open-inpf (inpf-filename)
|
||||
(if (string= inpf-filename "-")
|
||||
*standard-input*
|
||||
(open inpf-filename :direction :input)))
|
||||
|
||||
(defun open-outf (outf-filename)
|
||||
(if (string= outf-filename "-")
|
||||
*standard-output*
|
||||
(open outf-filename :direction :output
|
||||
:if-exists :overwrite
|
||||
:if-does-not-exist :create)))
|
||||
|
||||
(defun word-signbit-p (x)
|
||||
"True if and only if the sign bit is set."
|
||||
(declare (type word x))
|
||||
(/= 0 (logand x #x80000000)))
|
||||
|
||||
(defun word-add (x y)
|
||||
"Addition with overflow freely allowed."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(coerce (logand (+ x y) #xFFFFFFFF) 'word))
|
||||
|
||||
(defun word-neg (x)
|
||||
"The two's complement."
|
||||
(declare (type word x))
|
||||
(word-add (logxor x #xFFFFFFFF) 1))
|
||||
|
||||
(defun word-sub (x y)
|
||||
"Subtraction with overflow freely allowed."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(word-add x (word-neg y)))
|
||||
|
||||
(defun word-mul (x y)
|
||||
"Signed multiplication."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(let ((abs-x (if x<0 (word-neg x) x))
|
||||
(abs-y (if y<0 (word-neg y) y)))
|
||||
(let* ((abs-xy (the word
|
||||
(logand (* abs-x abs-y) #xFFFFFFFF))))
|
||||
(if x<0
|
||||
(if y<0 abs-xy (word-neg abs-xy))
|
||||
(if y<0 (word-neg abs-xy) abs-xy))))))
|
||||
|
||||
(defun word-div (x y)
|
||||
"The quotient after signed integer division with truncation towards
|
||||
zero."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(let ((abs-x (if x<0 (word-neg x) x))
|
||||
(abs-y (if y<0 (word-neg y) y)))
|
||||
(let* ((abs-x/y (the word
|
||||
(logand (floor abs-x abs-y) #xFFFFFFFF))))
|
||||
(if x<0
|
||||
(if y<0 abs-x/y (word-neg abs-x/y))
|
||||
(if y<0 (word-neg abs-x/y) abs-x/y))))))
|
||||
|
||||
(defun word-mod (x y)
|
||||
"The remainder after signed integer division with truncation towards
|
||||
zero."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(let ((abs-x (if x<0 (word-neg x) x))
|
||||
(abs-y (if y<0 (word-neg y) y)))
|
||||
(let* ((abs-x%y (the word
|
||||
(logand (nth-value 1 (floor abs-x abs-y))
|
||||
#xFFFFFFFF))))
|
||||
(if x<0 (word-neg abs-x%y) abs-x%y)))))
|
||||
|
||||
(defun b2i (b)
|
||||
(declare (type boolean b))
|
||||
(if b 1 0))
|
||||
|
||||
(defun word-lt (x y)
|
||||
"Signed comparison: is x less than y?"
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (< x y) t)
|
||||
(if y<0 nil (< x y))))))
|
||||
|
||||
(defun word-le (x y)
|
||||
"Signed comparison: is x less than or equal to y?"
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (<= x y) t)
|
||||
(if y<0 nil (<= x y))))))
|
||||
|
||||
(defun word-gt (x y)
|
||||
"Signed comparison: is x greater than y?"
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (> x y) nil)
|
||||
(if y<0 t (> x y))))))
|
||||
|
||||
(defun word-ge (x y)
|
||||
"Signed comparison: is x greater than or equal to y?"
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(let ((x<0 (word-signbit-p x))
|
||||
(y<0 (word-signbit-p y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (>= x y) nil)
|
||||
(if y<0 t (>= x y))))))
|
||||
|
||||
(defun word-eq (x y)
|
||||
"Is x equal to y?"
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(b2i (= x y)))
|
||||
|
||||
(defun word-ne (x y)
|
||||
"Is x not equal to y?"
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(b2i (/= x y)))
|
||||
|
||||
(defun word-cmp (x)
|
||||
"The logical complement."
|
||||
(declare (type word x))
|
||||
(b2i (= x 0)))
|
||||
|
||||
(defun word-and (x y)
|
||||
"The logical conjunction."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(b2i (and (/= x 0) (/= y 0))))
|
||||
|
||||
(defun word-or (x y)
|
||||
"The logical disjunction."
|
||||
(declare (type word x))
|
||||
(declare (type word y))
|
||||
(b2i (or (/= x 0) (/= y 0))))
|
||||
|
||||
(defun unop (stack sp operation)
|
||||
"Perform a unary operation on the stack."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(declare (type (function (word) word) operation))
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (<= 1 i)
|
||||
(warn "stack underflow")
|
||||
(uiop:quit 1))
|
||||
(let ((x (elt stack (1- i))))
|
||||
(setf (elt stack (1- i)) (funcall operation x)))))
|
||||
|
||||
(defun binop (stack sp operation)
|
||||
"Perform a binary operation on the stack."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(declare (type (function (word word) word) operation))
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (<= 2 i)
|
||||
(warn "stack underflow")
|
||||
(uiop:quit 1))
|
||||
(let ((x (elt stack (- i 2)))
|
||||
(y (elt stack (1- i))))
|
||||
(setf (elt stack (- i 2)) (funcall operation x y)))
|
||||
(setf (elt sp 0) (1- i))))
|
||||
|
||||
(defun jri (code ip)
|
||||
"Jump relative immediate."
|
||||
(declare (type executable-memory code))
|
||||
(declare (type register ip))
|
||||
;; Big-endian order.
|
||||
(let ((j (elt ip 0)))
|
||||
(unless (<= (+ j 4) executable-memory-size)
|
||||
(warn "address past end of executable memory")
|
||||
(uiop:quit 1))
|
||||
(let* ((offset (elt code (+ j 3)))
|
||||
(offset (dpb (elt code (+ j 2)) (byte 8 8) offset))
|
||||
(offset (dpb (elt code (+ j 1)) (byte 8 16) offset))
|
||||
(offset (dpb (elt code j) (byte 8 24) offset)))
|
||||
(setf (elt ip 0) (word-add j offset)))))
|
||||
|
||||
(defun jriz (stack sp code ip)
|
||||
"Jump relative immediate, if zero."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(declare (type executable-memory code))
|
||||
(declare (type register ip))
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (<= 1 i)
|
||||
(warn "stack underflow")
|
||||
(uiop:quit 1))
|
||||
(let ((x (elt stack (1- i))))
|
||||
(setf (elt sp 0) (1- i))
|
||||
(if (= x 0)
|
||||
(jri code ip)
|
||||
(setf (elt ip 0) (+ (elt ip 0) 4))))))
|
||||
|
||||
(defun get-immediate-value (code ip)
|
||||
(declare (type executable-memory code))
|
||||
(declare (type register ip))
|
||||
;; Big-endian order.
|
||||
(let ((j (elt ip 0)))
|
||||
(unless (<= (+ j 4) executable-memory-size)
|
||||
(warn "address past end of executable memory")
|
||||
(uiop:quit 1))
|
||||
(let* ((x (elt code (+ j 3)))
|
||||
(x (dpb (elt code (+ j 2)) (byte 8 8) x))
|
||||
(x (dpb (elt code (+ j 1)) (byte 8 16) x))
|
||||
(x (dpb (elt code j) (byte 8 24) x)))
|
||||
(setf (elt ip 0) (+ j 4))
|
||||
x)))
|
||||
|
||||
(defun pushi (stack sp code ip)
|
||||
"Push-immediate a value from executable memory onto the stack."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(declare (type executable-memory code))
|
||||
(declare (type register ip))
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (< i stack-memory-size)
|
||||
(warn "stack overflow")
|
||||
(uiop:quit 1))
|
||||
(setf (elt stack i) (get-immediate-value code ip))
|
||||
(setf (elt sp 0) (1+ i))))
|
||||
|
||||
(defun fetch (stack sp code ip data)
|
||||
"Fetch data to the stack, using the storage location given in
|
||||
executable memory."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(declare (type executable-memory code))
|
||||
(declare (type register ip))
|
||||
(declare (type data-memory data))
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (< i stack-memory-size)
|
||||
(warn "stack overflow")
|
||||
(uiop:quit 1))
|
||||
(let* ((k (get-immediate-value code ip))
|
||||
(x (elt data k)))
|
||||
(setf (elt stack i) x)
|
||||
(setf (elt sp 0) (1+ i)))))
|
||||
|
||||
(defun pop-one (stack sp)
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (<= 1 i)
|
||||
(warn "stack underflow")
|
||||
(uiop:quit 1))
|
||||
(let* ((x (elt stack (1- i))))
|
||||
(setf (elt sp 0) (1- i))
|
||||
x)))
|
||||
|
||||
(defun store (stack sp code ip data)
|
||||
"Store data from the stack, using the storage location given in
|
||||
executable memory."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(declare (type executable-memory code))
|
||||
(declare (type register ip))
|
||||
(declare (type data-memory data))
|
||||
(let ((i (elt sp 0)))
|
||||
(unless (<= 1 i)
|
||||
(warn "stack underflow")
|
||||
(uiop:quit 1))
|
||||
(let ((k (get-immediate-value code ip))
|
||||
(x (pop-one stack sp)))
|
||||
(setf (elt data k) x))))
|
||||
|
||||
(defun prti (stack sp outf)
|
||||
"Print the top value of the stack, as a signed decimal value."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(let* ((n (pop-one stack sp))
|
||||
(n<0 (word-signbit-p n)))
|
||||
(if n<0
|
||||
(format outf "-~D" (word-neg n))
|
||||
(format outf "~D" n))))
|
||||
|
||||
(defun prtc (stack sp outf)
|
||||
"Print the top value of the stack, as a character."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(let* ((c (pop-one stack sp)))
|
||||
(format outf "~C" (code-char c))))
|
||||
|
||||
(defun prts (stack sp strings outf)
|
||||
"Print the string specified by the top of the stack."
|
||||
(declare (type stack-memory stack))
|
||||
(declare (type register sp))
|
||||
(let* ((k (pop-one stack sp))
|
||||
(s (elt strings k)))
|
||||
(format outf "~A" s)))
|
||||
|
||||
(defmacro defun-machine-binop (op)
|
||||
(let ((machine-op (read-from-string
|
||||
(concatenate 'string "machine-" (string op))))
|
||||
(word-op (read-from-string
|
||||
(concatenate 'string "word-" (string op)))))
|
||||
`(defun ,machine-op (mach)
|
||||
(declare (type machine mach))
|
||||
(binop (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
#',word-op))))
|
||||
|
||||
(defmacro defun-machine-unop (op)
|
||||
(let ((machine-op (read-from-string
|
||||
(concatenate 'string "machine-" (string op))))
|
||||
(word-op (read-from-string
|
||||
(concatenate 'string "word-" (string op)))))
|
||||
`(defun ,machine-op (mach)
|
||||
(declare (type machine mach))
|
||||
(unop (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
#',word-op))))
|
||||
|
||||
(defun-machine-binop "add")
|
||||
(defun-machine-binop "sub")
|
||||
(defun-machine-binop "mul")
|
||||
(defun-machine-binop "div")
|
||||
(defun-machine-binop "mod")
|
||||
(defun-machine-binop "lt")
|
||||
(defun-machine-binop "gt")
|
||||
(defun-machine-binop "le")
|
||||
(defun-machine-binop "ge")
|
||||
(defun-machine-binop "eq")
|
||||
(defun-machine-binop "ne")
|
||||
(defun-machine-binop "and")
|
||||
(defun-machine-binop "or")
|
||||
|
||||
(defun-machine-unop "neg")
|
||||
(defun machine-not (mach)
|
||||
(declare (type machine mach))
|
||||
(unop (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
#'word-cmp))
|
||||
|
||||
(defun machine-prtc (mach)
|
||||
(declare (type machine mach))
|
||||
(prtc (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-output mach)))
|
||||
|
||||
(defun machine-prti (mach)
|
||||
(declare (type machine mach))
|
||||
(prti (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-output mach)))
|
||||
|
||||
(defun machine-prts (mach)
|
||||
(declare (type machine mach))
|
||||
(prts (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-strings mach)
|
||||
(machine-output mach)))
|
||||
|
||||
(defun machine-fetch (mach)
|
||||
(declare (type machine mach))
|
||||
(fetch (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)
|
||||
(machine-data mach)))
|
||||
|
||||
(defun machine-store (mach)
|
||||
(declare (type machine mach))
|
||||
(store (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)
|
||||
(machine-data mach)))
|
||||
|
||||
(defun machine-push (mach)
|
||||
(declare (type machine mach))
|
||||
(pushi (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)))
|
||||
|
||||
(defun machine-jmp (mach)
|
||||
(declare (type machine mach))
|
||||
(jri (machine-code mach)
|
||||
(machine-ip mach)))
|
||||
|
||||
(defun machine-jz (mach)
|
||||
(declare (type machine mach))
|
||||
(jriz (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)))
|
||||
|
||||
(defun get-opcode (mach)
|
||||
(declare (type machine mach))
|
||||
(let ((code (machine-code mach))
|
||||
(ip (machine-ip mach)))
|
||||
(let ((j (elt ip 0)))
|
||||
(unless (< j executable-memory-size)
|
||||
(warn "address past end of executable memory")
|
||||
(uiop:quit 1))
|
||||
(let ((opcode (elt code j)))
|
||||
(setf (elt ip 0) (1+ j))
|
||||
opcode))))
|
||||
|
||||
(defun run-instruction (mach opcode)
|
||||
(declare (type machine mach))
|
||||
(declare (type fixnum opcode))
|
||||
(let ((op-mod-4 (logand opcode #x3))
|
||||
(op-div-4 (ash opcode -2)))
|
||||
(trivia:match op-div-4
|
||||
(0 (trivia:match op-mod-4
|
||||
(1 (machine-add mach))
|
||||
(2 (machine-sub mach))
|
||||
(3 (machine-mul mach))))
|
||||
(1 (trivia:match op-mod-4
|
||||
(0 (machine-div mach))
|
||||
(1 (machine-mod mach))
|
||||
(2 (machine-lt mach))
|
||||
(3 (machine-gt mach))))
|
||||
(2 (trivia:match op-mod-4
|
||||
(0 (machine-le mach))
|
||||
(1 (machine-ge mach))
|
||||
(2 (machine-eq mach))
|
||||
(3 (machine-ne mach))))
|
||||
(3 (trivia:match op-mod-4
|
||||
(0 (machine-and mach))
|
||||
(1 (machine-or mach))
|
||||
(2 (machine-neg mach))
|
||||
(3 (machine-not mach))))
|
||||
(4 (trivia:match op-mod-4
|
||||
(0 (machine-prtc mach))
|
||||
(1 (machine-prti mach))
|
||||
(2 (machine-prts mach))
|
||||
(3 (machine-fetch mach))))
|
||||
(5 (trivia:match op-mod-4
|
||||
(0 (machine-store mach))
|
||||
(1 (machine-push mach))
|
||||
(2 (machine-jmp mach))
|
||||
(3 (machine-jz mach)))))))
|
||||
|
||||
(defun run-vm (mach)
|
||||
(declare (type machine mach))
|
||||
(let ((opcode-for-halt (the fixnum (opcode-from-name "halt")))
|
||||
(opcode-for-add (the fixnum (opcode-from-name "add")))
|
||||
(opcode-for-jz (the fixnum (opcode-from-name "jz"))))
|
||||
(loop for opcode = (the fixnum (get-opcode mach))
|
||||
until (= opcode opcode-for-halt)
|
||||
do (progn (when (or (< opcode opcode-for-add)
|
||||
(< opcode-for-jz opcode))
|
||||
(warn "unsupported opcode")
|
||||
(uiop:quit 1))
|
||||
(run-instruction mach opcode)))))
|
||||
|
||||
(defun usage-error ()
|
||||
(princ "Usage: vm [INPUTFILE [OUTPUTFILE]]" *standard-output*)
|
||||
(terpri *standard-output*)
|
||||
(princ "If either INPUTFILE or OUTPUTFILE is \"-\", the respective"
|
||||
*standard-output*)
|
||||
(princ " standard I/O is used." *standard-output*)
|
||||
(terpri *standard-output*)
|
||||
(uiop:quit 1))
|
||||
|
||||
(defun get-filenames (argv)
|
||||
(trivia:match argv
|
||||
((list) '("-" "-"))
|
||||
((list inpf-filename) `(,inpf-filename "-"))
|
||||
((list inpf-filename outf-filename) `(,inpf-filename
|
||||
,outf-filename))
|
||||
(_ (usage-error))))
|
||||
|
||||
(defun main (&rest argv)
|
||||
(let* ((filenames (get-filenames argv))
|
||||
(inpf-filename (car filenames))
|
||||
(inpf (open-inpf inpf-filename))
|
||||
(outf-filename (cadr filenames))
|
||||
(outf (open-outf outf-filename))
|
||||
|
||||
(sizes (read-datasize-and-strings-count inpf))
|
||||
(datasize (car sizes))
|
||||
(strings-count (cadr sizes))
|
||||
(strings (read-string-literals inpf strings-count))
|
||||
(instructions (read-instructions inpf))
|
||||
;; We shall remain noncommittal about how strings are stored
|
||||
;; on the hypothetical machine.
|
||||
(strings (coerce strings 'simple-vector))
|
||||
|
||||
(mach (make-machine :strings strings
|
||||
:output outf)))
|
||||
|
||||
(unless (<= datasize data-memory-size)
|
||||
(warn "the VM's data memory size is exceeded")
|
||||
(uiop:quit 1))
|
||||
|
||||
(load-executable-memory (machine-code mach) instructions)
|
||||
(run-vm mach)
|
||||
|
||||
(unless (string= inpf-filename "-")
|
||||
(close inpf))
|
||||
(unless (string= outf-filename "-")
|
||||
(close outf))
|
||||
|
||||
(uiop:quit 0)))
|
||||
|
||||
;;; vim: set ft=lisp lisp:
|
||||
|
|
@ -0,0 +1,907 @@
|
|||
//
|
||||
// The Rosetta Code Virtual Machine in D.
|
||||
//
|
||||
// This code was migrated from an implementation in ATS. I have tried
|
||||
// to keep it possible to compare the two languages easily, although
|
||||
// in some cases the demonstration of "low level" techniques in ATS
|
||||
// (such as avoiding memory leaks that might require garbage
|
||||
// collection), or the use of linked lists as intermediate storage, or
|
||||
// other such matters, seemed inappropriate to duplicate in D
|
||||
// programming.
|
||||
//
|
||||
// (For example: in ATS, using a fully built linked list to initialize
|
||||
// an array solves typechecking issues that simply do not exist in D's
|
||||
// type system.)
|
||||
//
|
||||
|
||||
import std.ascii;
|
||||
import std.conv;
|
||||
import std.stdint;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.typecons;
|
||||
|
||||
enum Op {
|
||||
HALT = 0x0000, // 00000
|
||||
ADD = 0x0001, // 00001
|
||||
SUB = 0x0002, // 00010
|
||||
MUL = 0x0003, // 00011
|
||||
DIV = 0x0004, // 00100
|
||||
MOD = 0x0005, // 00101
|
||||
LT = 0x0006, // 00110
|
||||
GT = 0x0007, // 00111
|
||||
LE = 0x0008, // 01000
|
||||
GE = 0x0009, // 01001
|
||||
EQ = 0x000A, // 01010
|
||||
NE = 0x000B, // 01011
|
||||
AND = 0x000C, // 01100
|
||||
OR = 0x000D, // 01101
|
||||
NEG = 0x000E, // 01110
|
||||
NOT = 0x000F, // 01111
|
||||
PRTC = 0x0010, // 10000
|
||||
PRTI = 0x0011, // 10001
|
||||
PRTS = 0x0012, // 10010
|
||||
FETCH = 0x0013, // 10011
|
||||
STORE = 0x0014, // 10100
|
||||
PUSH = 0x0015, // 10101
|
||||
JMP = 0x0016, // 10110
|
||||
JZ = 0x0017 // 10111
|
||||
}
|
||||
|
||||
const string[] opcodeOrder =
|
||||
["halt", // 00000 bit pattern
|
||||
"add", // 00001
|
||||
"sub", // 00010
|
||||
"mul", // 00011
|
||||
"div", // 00100
|
||||
"mod", // 00101
|
||||
"lt", // 00110
|
||||
"gt", // 00111
|
||||
"le", // 01000
|
||||
"ge", // 01001
|
||||
"eq", // 01010
|
||||
"ne", // 01011
|
||||
"and", // 01100
|
||||
"or", // 01101
|
||||
"neg", // 01110
|
||||
"not", // 01111
|
||||
"prtc", // 10000
|
||||
"prti", // 10001
|
||||
"prts", // 10010
|
||||
"fetch", // 10011
|
||||
"store", // 10100
|
||||
"push", // 10101
|
||||
"jmp", // 10110
|
||||
"jz"]; // 10111
|
||||
|
||||
enum Register {
|
||||
PC = 0,
|
||||
SP = 1,
|
||||
MAX = SP
|
||||
}
|
||||
|
||||
alias vmint = uint32_t;
|
||||
|
||||
class VM {
|
||||
string[] strings;
|
||||
ubyte[] code;
|
||||
vmint[] data;
|
||||
vmint[] stack;
|
||||
vmint[Register.MAX + 1] registers;
|
||||
}
|
||||
|
||||
class BadVMException : Exception
|
||||
{
|
||||
this(string msg, string file = __FILE__, size_t line = __LINE__)
|
||||
{
|
||||
super(msg, file, line);
|
||||
}
|
||||
}
|
||||
|
||||
class VMRuntimeException : Exception
|
||||
{
|
||||
this(string msg, string file = __FILE__, size_t line = __LINE__)
|
||||
{
|
||||
super(msg, file, line);
|
||||
}
|
||||
}
|
||||
|
||||
vmint
|
||||
twosComplement (vmint x)
|
||||
{
|
||||
// This computes the negative of x, if x is regarded as signed.
|
||||
pragma(inline);
|
||||
return (~x) + vmint(1U);
|
||||
}
|
||||
|
||||
vmint
|
||||
add (vmint x, vmint y)
|
||||
{
|
||||
// This works whether x or y is regarded as unsigned or signed.
|
||||
pragma(inline);
|
||||
return x + y;
|
||||
}
|
||||
|
||||
vmint
|
||||
sub (vmint x, vmint y)
|
||||
{
|
||||
// This works whether x or y is regarded as unsigned or signed.
|
||||
pragma(inline);
|
||||
return x - y;
|
||||
}
|
||||
|
||||
vmint
|
||||
equality (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(x == y);
|
||||
}
|
||||
|
||||
vmint
|
||||
inequality (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(x != y);
|
||||
}
|
||||
|
||||
vmint
|
||||
signedLt (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) < int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
signedGt (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) > int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
signedLte (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) <= int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
signedGte (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) >= int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
signedMul (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) * int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
signedDiv (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) / int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
signedMod (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(int32_t(x) % int32_t(y));
|
||||
}
|
||||
|
||||
vmint
|
||||
logicalNot (vmint x)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint(!x);
|
||||
}
|
||||
|
||||
vmint
|
||||
logicalAnd (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return vmint((!!x) * (!!y));
|
||||
}
|
||||
|
||||
vmint
|
||||
logicalOr (vmint x, vmint y)
|
||||
{
|
||||
pragma(inline);
|
||||
return (vmint(1) - vmint((!x) * (!y)));
|
||||
}
|
||||
|
||||
vmint
|
||||
parseDigits (string s, size_t i, size_t j)
|
||||
{
|
||||
const badInteger = "bad integer";
|
||||
|
||||
if (j == i)
|
||||
throw new BadVMException (badInteger);
|
||||
auto x = vmint(0);
|
||||
for (size_t k = i; k < j; k += 1)
|
||||
if (!isDigit (s[k]))
|
||||
throw new BadVMException (badInteger);
|
||||
else
|
||||
// The result is allowed to overflow freely.
|
||||
x = (vmint(10) * x) + vmint(s[k] - '0');
|
||||
return x;
|
||||
}
|
||||
|
||||
vmint
|
||||
parseInteger (string s, size_t i, size_t j)
|
||||
{
|
||||
const badInteger = "bad integer";
|
||||
|
||||
vmint retval;
|
||||
if (j == i)
|
||||
throw new BadVMException (badInteger);
|
||||
else if (j == i + vmint(1) && !isDigit (s[i]))
|
||||
throw new BadVMException (badInteger);
|
||||
else if (s[i] != '-')
|
||||
retval = parseDigits (s, i, j);
|
||||
else if (j == i + vmint(1))
|
||||
throw new BadVMException (badInteger);
|
||||
else
|
||||
retval = twosComplement (parseDigits (s, i + vmint(1), j));
|
||||
return retval;
|
||||
}
|
||||
|
||||
size_t
|
||||
skipWhitespace (string s, size_t n, size_t i)
|
||||
{
|
||||
while (i < n && isWhite (s[i]))
|
||||
i += 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t
|
||||
skipNonwhitespace (string s, size_t n, size_t i)
|
||||
{
|
||||
while (i < n && !isWhite (s[i]))
|
||||
i += 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
bool
|
||||
substrEqual (string s, size_t i, size_t j, string t)
|
||||
{
|
||||
// Is s[i .. j-1] equal to t?
|
||||
|
||||
auto retval = false;
|
||||
auto m = t.length;
|
||||
if (m == j - i)
|
||||
{
|
||||
auto k = size_t(0);
|
||||
while (k < m && s[i + k] == t[k])
|
||||
k += 1;
|
||||
retval = (k == m);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
string
|
||||
dequoteString (string s, size_t n)
|
||||
{
|
||||
const badQuotedString = "bad quoted string";
|
||||
|
||||
string t = "";
|
||||
s = strip(s);
|
||||
if (s.length < 2 || s[0] != '"' || s[$ - 1] != '"')
|
||||
throw new BadVMException (badQuotedString);
|
||||
auto i = 1;
|
||||
while (i < s.length - 1)
|
||||
if (s[i] != '\\')
|
||||
{
|
||||
t ~= s[i];
|
||||
i += 1;
|
||||
}
|
||||
else if (i + 1 == s.length - 1)
|
||||
throw new BadVMException (badQuotedString);
|
||||
else if (s[i + 1] == 'n')
|
||||
{
|
||||
t ~= '\n';
|
||||
i += 2;
|
||||
}
|
||||
else if (s[i + 1] == '\\')
|
||||
{
|
||||
t ~= '\\';
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
throw new BadVMException (badQuotedString);
|
||||
return t;
|
||||
}
|
||||
|
||||
string[]
|
||||
readStrings (File f, size_t stringsSize)
|
||||
{
|
||||
const badQuotedString = "Bad quoted string.";
|
||||
|
||||
string[] strings;
|
||||
strings.length = stringsSize;
|
||||
for (size_t k = 0; k < stringsSize; k += 1)
|
||||
{
|
||||
auto line = f.readln();
|
||||
strings[k] = dequoteString (line, line.length);
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
ubyte
|
||||
opcodeNameTo_ubyte (string str, size_t i, size_t j)
|
||||
{
|
||||
size_t k = 0;
|
||||
while (k < opcodeOrder.length &&
|
||||
!substrEqual (str, i, j, opcodeOrder[k]))
|
||||
k += 1;
|
||||
if (k == opcodeOrder.length)
|
||||
throw new BadVMException ("unrecognized opcode name");
|
||||
return to!ubyte(k);
|
||||
}
|
||||
|
||||
ubyte
|
||||
vmintByte0 (vmint i)
|
||||
{
|
||||
return (i & 0xFF);
|
||||
}
|
||||
|
||||
ubyte
|
||||
vmintByte1 (vmint i)
|
||||
{
|
||||
return ((i >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
ubyte
|
||||
vmintByte2 (vmint i)
|
||||
{
|
||||
return ((i >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
ubyte
|
||||
vmintByte3 (vmint i)
|
||||
{
|
||||
return (i >> 24);
|
||||
}
|
||||
|
||||
ubyte[]
|
||||
parseInstruction (string line)
|
||||
{
|
||||
const bad_instruction = "bad VM instruction";
|
||||
|
||||
const n = line.length;
|
||||
auto i = skipWhitespace (line, n, 0);
|
||||
|
||||
// Skip the address field.
|
||||
i = skipNonwhitespace (line, n, i);
|
||||
|
||||
i = skipWhitespace (line, n, i);
|
||||
auto j = skipNonwhitespace (line, n, i);
|
||||
auto opcode = opcodeNameTo_ubyte (line, i, j);
|
||||
|
||||
auto startOfArgument = j;
|
||||
|
||||
ubyte[] finishPush ()
|
||||
{
|
||||
const i1 = skipWhitespace (line, n, startOfArgument);
|
||||
const j1 = skipNonwhitespace (line, n, i1);
|
||||
const arg = parseInteger (line, i1, j1);
|
||||
// Little-endian storage.
|
||||
return [opcode, vmintByte0 (arg), vmintByte1 (arg),
|
||||
vmintByte2 (arg), vmintByte3 (arg)];
|
||||
}
|
||||
|
||||
ubyte[] finishFetchOrStore ()
|
||||
{
|
||||
const i1 = skipWhitespace (line, n, startOfArgument);
|
||||
const j1 = skipNonwhitespace (line, n, i1);
|
||||
if (j1 - i1 < 3 || line[i1] != '[' || line[j1 - 1] != ']')
|
||||
throw new BadVMException (bad_instruction);
|
||||
const arg = parseInteger (line, i1 + 1, j1 - 1);
|
||||
// Little-endian storage.
|
||||
return [opcode, vmintByte0 (arg), vmintByte1 (arg),
|
||||
vmintByte2 (arg), vmintByte3 (arg)];
|
||||
}
|
||||
|
||||
ubyte[] finishJmpOrJz ()
|
||||
{
|
||||
const i1 = skipWhitespace (line, n, startOfArgument);
|
||||
const j1 = skipNonwhitespace (line, n, i1);
|
||||
if (j1 - i1 < 3 || line[i1] != '(' || line[j1 - 1] != ')')
|
||||
throw new BadVMException (bad_instruction);
|
||||
const arg = parseInteger (line, i1 + 1, j1 - 1);
|
||||
// Little-endian storage.
|
||||
return [opcode, vmintByte0 (arg), vmintByte1 (arg),
|
||||
vmintByte2 (arg), vmintByte3 (arg)];
|
||||
}
|
||||
|
||||
ubyte[] retval;
|
||||
switch (opcode)
|
||||
{
|
||||
case Op.PUSH:
|
||||
retval = finishPush ();
|
||||
break;
|
||||
case Op.FETCH:
|
||||
case Op.STORE:
|
||||
retval = finishFetchOrStore ();
|
||||
break;
|
||||
case Op.JMP:
|
||||
case Op.JZ:
|
||||
retval = finishJmpOrJz ();
|
||||
break;
|
||||
default:
|
||||
retval = [opcode];
|
||||
break;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
ubyte[]
|
||||
readCode (File f)
|
||||
{
|
||||
// Read the instructions from the input, producing an array of
|
||||
// array of instruction bytes.
|
||||
ubyte[] code = [];
|
||||
auto line = f.readln();
|
||||
while (line !is null)
|
||||
{
|
||||
code ~= parseInstruction (line);
|
||||
line = f.readln();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
void
|
||||
parseHeaderLine (string line, ref size_t dataSize,
|
||||
ref size_t stringsSize)
|
||||
{
|
||||
const bad_vm_header_line = "bad VM header line";
|
||||
|
||||
const n = line.length;
|
||||
auto i = skipWhitespace (line, n, 0);
|
||||
auto j = skipNonwhitespace (line, n, i);
|
||||
if (!substrEqual (line, i, j, "Datasize:"))
|
||||
throw new BadVMException (bad_vm_header_line);
|
||||
i = skipWhitespace (line, n, j);
|
||||
j = skipNonwhitespace (line, n, i);
|
||||
dataSize = parseInteger (line, i, j);
|
||||
i = skipWhitespace (line, n, j);
|
||||
j = skipNonwhitespace (line, n, i);
|
||||
if (!substrEqual (line, i, j, "Strings:"))
|
||||
throw new BadVMException (bad_vm_header_line);
|
||||
i = skipWhitespace (line, n, j);
|
||||
j = skipNonwhitespace (line, n, i);
|
||||
stringsSize = parseInteger (line, i, j);
|
||||
}
|
||||
|
||||
VM
|
||||
readVM (File f)
|
||||
{
|
||||
const line = f.readln();
|
||||
|
||||
size_t dataSize;
|
||||
size_t stringsSize;
|
||||
parseHeaderLine (line, dataSize, stringsSize);
|
||||
|
||||
VM vm = new VM();
|
||||
vm.strings = readStrings (f, stringsSize);
|
||||
vm.code = readCode (f);
|
||||
vm.data.length = dataSize;
|
||||
vm.stack.length = 65536; // A VERY big stack, MUCH bigger than is
|
||||
// "reasonable" for this VM. The same size
|
||||
// as in the ATS, however.
|
||||
vm.registers[Register.PC] = vmint(0);
|
||||
vm.registers[Register.SP] = vmint(0);
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
vmint
|
||||
pop (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const spBefore = vm.registers[Register.SP];
|
||||
if (spBefore == 0)
|
||||
throw new VMRuntimeException ("stack underflow");
|
||||
const spAfter = spBefore - vmint(1);
|
||||
vm.registers[Register.SP] = spAfter;
|
||||
return vm.stack[spAfter];
|
||||
}
|
||||
|
||||
void
|
||||
push (VM vm, vmint x)
|
||||
{
|
||||
pragma(inline);
|
||||
const spBefore = vm.registers[Register.SP];
|
||||
if (vm.stack.length <= spBefore)
|
||||
throw new VMRuntimeException ("stack overflow");
|
||||
vm.stack[spBefore] = x;
|
||||
const spAfter = spBefore + vmint(1);
|
||||
vm.registers[Register.SP] = spAfter;
|
||||
}
|
||||
|
||||
vmint
|
||||
fetchData (VM vm, vmint index)
|
||||
{
|
||||
pragma(inline);
|
||||
if (vm.data.length <= index)
|
||||
throw new VMRuntimeException
|
||||
("fetch from outside the data section");
|
||||
return vm.data[index];
|
||||
}
|
||||
|
||||
void
|
||||
storeData (VM vm, vmint index, vmint x)
|
||||
{
|
||||
pragma(inline);
|
||||
if (vm.data.length <= index)
|
||||
throw new VMRuntimeException
|
||||
("store to outside the data section");
|
||||
vm.data[index] = x;
|
||||
}
|
||||
|
||||
vmint
|
||||
getArgument (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
auto pc = vm.registers[Register.PC];
|
||||
if (vm.code.length <= pc + vmint(4))
|
||||
throw new VMRuntimeException
|
||||
("the program counter is out of bounds");
|
||||
// The data is stored little-endian.
|
||||
const byte0 = vmint (vm.code[pc]);
|
||||
const byte1 = vmint (vm.code[pc + vmint(1)]);
|
||||
const byte2 = vmint (vm.code[pc + vmint(2)]);
|
||||
const byte3 = vmint (vm.code[pc + vmint(3)]);
|
||||
return (byte0) | (byte1 << 8) | (byte2 << 16) | (byte3 << 24);
|
||||
}
|
||||
|
||||
void
|
||||
skipArgument (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
vm.registers[Register.PC] += vmint(4);
|
||||
}
|
||||
|
||||
//
|
||||
// The string mixins below are going to do for us *some* of what the
|
||||
// ATS template system did for us. The two methods hardly resemble
|
||||
// each other, but both can be used to construct function definitions
|
||||
// at compile time.
|
||||
//
|
||||
|
||||
template
|
||||
UnaryOperation (alias name, alias func)
|
||||
{
|
||||
const char[] UnaryOperation =
|
||||
"void " ~
|
||||
name ~ " (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const sp = vm.registers[Register.SP];
|
||||
if (sp == vmint(0))
|
||||
throw new VMRuntimeException (\"stack underflow\");
|
||||
const x = vm.stack[sp - vmint(1)];
|
||||
const z = " ~ func ~ " (x);
|
||||
vm.stack[sp - vmint(1)] = z;
|
||||
}";
|
||||
}
|
||||
|
||||
template
|
||||
BinaryOperation (alias name, alias func)
|
||||
{
|
||||
const char[] BinaryOperation =
|
||||
"void " ~
|
||||
name ~ " (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const spBefore = vm.registers[Register.SP];
|
||||
if (spBefore <= vmint(1))
|
||||
throw new VMRuntimeException (\"stack underflow\");
|
||||
const spAfter = spBefore - vmint(1);
|
||||
vm.registers[Register.SP] = spAfter;
|
||||
const x = vm.stack[spAfter - vmint(1)];
|
||||
const y = vm.stack[spAfter];
|
||||
const z = " ~ func ~ "(x, y);
|
||||
vm.stack[spAfter - vmint(1)] = z;
|
||||
}";
|
||||
}
|
||||
|
||||
mixin (UnaryOperation!("uopNeg", "twosComplement"));
|
||||
mixin (UnaryOperation!("uopNot", "logicalNot"));
|
||||
|
||||
mixin (BinaryOperation!("binopAdd", "add"));
|
||||
mixin (BinaryOperation!("binopSub", "sub"));
|
||||
mixin (BinaryOperation!("binopMul", "signedMul"));
|
||||
mixin (BinaryOperation!("binopDiv", "signedDiv"));
|
||||
mixin (BinaryOperation!("binopMod", "signedMod"));
|
||||
mixin (BinaryOperation!("binopEq", "equality"));
|
||||
mixin (BinaryOperation!("binopNe", "inequality"));
|
||||
mixin (BinaryOperation!("binopLt", "signedLt"));
|
||||
mixin (BinaryOperation!("binopGt", "signedGt"));
|
||||
mixin (BinaryOperation!("binopLe", "signedLte"));
|
||||
mixin (BinaryOperation!("binopGe", "signedGte"));
|
||||
mixin (BinaryOperation!("binopAnd", "logicalAnd"));
|
||||
mixin (BinaryOperation!("binopOr", "logicalOr"));
|
||||
|
||||
void
|
||||
doPush (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const arg = getArgument (vm);
|
||||
push (vm, arg);
|
||||
skipArgument (vm);
|
||||
}
|
||||
|
||||
void
|
||||
doFetch (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const i = getArgument (vm);
|
||||
const x = fetchData (vm, i);
|
||||
push (vm, x);
|
||||
skipArgument (vm);
|
||||
}
|
||||
|
||||
void
|
||||
doStore (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const i = getArgument (vm);
|
||||
const x = pop (vm);
|
||||
storeData (vm, i, x);
|
||||
skipArgument (vm);
|
||||
}
|
||||
|
||||
void
|
||||
doJmp (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const arg = getArgument (vm);
|
||||
vm.registers[Register.PC] += arg;
|
||||
}
|
||||
|
||||
void
|
||||
doJz (VM vm)
|
||||
{
|
||||
pragma(inline);
|
||||
const x = pop (vm);
|
||||
if (x == vmint(0))
|
||||
doJmp (vm);
|
||||
else
|
||||
skipArgument (vm);
|
||||
}
|
||||
|
||||
void
|
||||
doPrtc (File fOut, VM vm)
|
||||
{
|
||||
const x = pop (vm);
|
||||
fOut.write (to!char(x));
|
||||
}
|
||||
|
||||
void
|
||||
doPrti (File fOut, VM vm)
|
||||
{
|
||||
const x = pop (vm);
|
||||
fOut.write (int32_t(x));
|
||||
}
|
||||
|
||||
void
|
||||
doPrts (File fOut, VM vm)
|
||||
{
|
||||
const i = pop (vm);
|
||||
if (vm.strings.length <= i)
|
||||
throw new VMRuntimeException ("string index out of bounds");
|
||||
fOut.write (vm.strings[i]);
|
||||
}
|
||||
|
||||
void
|
||||
vmStep (File fOut, VM vm, ref bool machineHalt, ref bool badOpcode)
|
||||
{
|
||||
const pc = vm.registers[Register.PC];
|
||||
if (vm.code.length <= pc)
|
||||
throw new VMRuntimeException
|
||||
("the program counter is out of bounds");
|
||||
vm.registers[Register.PC] = pc + vmint(1);
|
||||
const opcode = vm.code[pc];
|
||||
const uOpcode = uint(opcode);
|
||||
|
||||
// Dispatch by bifurcation on the bit pattern of the opcode. This
|
||||
// method is logarithmic in the number of opcode values.
|
||||
|
||||
machineHalt = false;
|
||||
badOpcode = false;
|
||||
if ((uOpcode & (~0x1FU)) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x10U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x08U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x04U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x02U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
machineHalt = true;
|
||||
else
|
||||
binopAdd (vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
binopSub (vm);
|
||||
else
|
||||
binopMul (vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x02U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
binopDiv (vm);
|
||||
else
|
||||
binopMod (vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
binopLt (vm);
|
||||
else
|
||||
binopGt (vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x04U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x02U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
binopLe (vm);
|
||||
else
|
||||
binopGe (vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
binopEq (vm);
|
||||
else
|
||||
binopNe (vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x02U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
binopAnd (vm);
|
||||
else
|
||||
binopOr (vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
uopNeg (vm);
|
||||
else
|
||||
uopNot (vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x08U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x04U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x02U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
doPrtc (fOut, vm);
|
||||
else
|
||||
doPrti (fOut, vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
doPrts (fOut, vm);
|
||||
else
|
||||
doFetch (vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x02U) == 0U)
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
doStore (vm);
|
||||
else
|
||||
doPush (vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((uOpcode & 0x01U) == 0U)
|
||||
doJmp (vm);
|
||||
else
|
||||
doJz (vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
badOpcode = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
badOpcode = true;
|
||||
}
|
||||
|
||||
void
|
||||
vmContinue (File fOut, VM vm)
|
||||
{
|
||||
auto machineHalt = false;
|
||||
auto badOpcode = false;
|
||||
while (!machineHalt && !badOpcode)
|
||||
vmStep (fOut, vm, machineHalt, badOpcode);
|
||||
if (badOpcode)
|
||||
throw new VMRuntimeException ("unrecognized opcode at runtime");
|
||||
}
|
||||
|
||||
void
|
||||
vmInitialize (VM vm)
|
||||
{
|
||||
foreach (ref x; vm.data)
|
||||
x = vmint(0);
|
||||
vm.registers[Register.PC] = vmint(0);
|
||||
vm.registers[Register.SP] = vmint(0);
|
||||
}
|
||||
|
||||
void
|
||||
vmRun (File fOut, VM vm)
|
||||
{
|
||||
vmInitialize (vm);
|
||||
vmContinue (fOut, vm);
|
||||
}
|
||||
|
||||
void
|
||||
ensure_that_vmint_is_suitable ()
|
||||
{
|
||||
// Try to guarantee that vmint is exactly 32 bits, and that it
|
||||
// allows overflow in either direction.
|
||||
assert (vmint(0xFFFFFFFFU) + vmint(1U) == vmint(0U));
|
||||
assert (vmint(0U) - vmint(1U) == vmint(0xFFFFFFFFU));
|
||||
assert (vmint(-1234) == twosComplement (vmint(1234)));
|
||||
}
|
||||
|
||||
int
|
||||
main (char[][] args)
|
||||
{
|
||||
auto inpFilename = "-";
|
||||
auto outFilename = "-";
|
||||
if (2 <= args.length)
|
||||
inpFilename = to!string (args[1]);
|
||||
if (3 <= args.length)
|
||||
outFilename = to!string (args[2]);
|
||||
|
||||
auto inpF = stdin;
|
||||
if (inpFilename != "-")
|
||||
inpF = File (inpFilename, "r");
|
||||
auto vm = readVM (inpF);
|
||||
if (inpFilename != "-")
|
||||
inpF.close();
|
||||
|
||||
auto outF = stdout;
|
||||
if (outFilename != "-")
|
||||
outF = File (outFilename, "w");
|
||||
ensure_that_vmint_is_suitable ();
|
||||
vmRun (outF, vm);
|
||||
if (outFilename != "-")
|
||||
outF.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
CREATE BUF 0 , \ single-character look-ahead buffer
|
||||
: PEEK BUF @ 0= IF KEY BUF ! THEN BUF @ ;
|
||||
: GETC PEEK 0 BUF ! ;
|
||||
: SPACE? DUP BL = SWAP 9 14 WITHIN OR ;
|
||||
: >SPACE BEGIN PEEK SPACE? WHILE GETC DROP REPEAT ;
|
||||
: DIGIT? 48 58 WITHIN ;
|
||||
: >INT ( -- n) >SPACE 0
|
||||
BEGIN PEEK DIGIT?
|
||||
WHILE GETC [CHAR] 0 - SWAP 10 * + REPEAT ;
|
||||
CREATE A 0 ,
|
||||
: C@A ( -- c) A @ C@ ;
|
||||
: C@A+ ( -- c) C@A 1 CHARS A +! ;
|
||||
: C!A+ ( c --) A @ C! 1 CHARS A +! ;
|
||||
: WORD ( -- c-addr) >SPACE PAD 1+ A !
|
||||
BEGIN PEEK SPACE? INVERT WHILE GETC C!A+ REPEAT
|
||||
>SPACE PAD A @ OVER - 1- PAD C! ;
|
||||
: >STRING ( -- c-addr) >SPACE GETC DROP PAD 1+ A !
|
||||
BEGIN PEEK [CHAR] " <> WHILE GETC C!A+ REPEAT
|
||||
GETC DROP PAD A @ OVER - 1- PAD C! ;
|
||||
: \INTERN ( c-addr -- c-addr) HERE >R A ! C@A+ DUP C,
|
||||
BEGIN DUP WHILE C@A+
|
||||
DUP [CHAR] \ = IF DROP -1 R@ +! C@A+
|
||||
[CHAR] n = IF 10 ELSE [CHAR] \ THEN
|
||||
THEN C, 1-
|
||||
REPEAT DROP R> ;
|
||||
: . 0 .R ;
|
||||
|
||||
CREATE DATA 0 ,
|
||||
CREATE STRINGS 0 ,
|
||||
: >DATA HERE DATA !
|
||||
WORD DROP >INT 4 * BEGIN DUP WHILE 0 C, 1- REPEAT DROP ;
|
||||
: >STRINGS HERE STRINGS !
|
||||
WORD DROP >INT DUP >R CELLS ALLOT
|
||||
0 BEGIN DUP R@ < WHILE
|
||||
DUP CELLS >STRING \INTERN STRINGS @ ROT + ! 1+
|
||||
REPEAT R> DROP DROP ;
|
||||
: >HEADER >DATA >STRINGS ;
|
||||
: i32! ( n addr --)
|
||||
OVER $FF AND OVER C! 1+
|
||||
OVER 8 RSHIFT $FF AND OVER C! 1+
|
||||
OVER 16 RSHIFT $FF AND OVER C! 1+
|
||||
SWAP 24 RSHIFT $FF AND SWAP C! ;
|
||||
: i32@ ( addr -- n) >R \ This is kinda slow... hmm
|
||||
R@ C@
|
||||
R@ 1 + C@ 8 LSHIFT OR
|
||||
R@ 2 + C@ 16 LSHIFT OR
|
||||
R> 3 + C@ 24 LSHIFT OR
|
||||
DUP $7FFFFFFF AND SWAP $80000000 AND - ; \ sign extend
|
||||
: i32, ( n --) HERE 4 ALLOT i32! ;
|
||||
: i32@+ ( -- n) A @ i32@ A @ 4 + A ! ;
|
||||
CREATE BYTECODE 0 ,
|
||||
: @fetch i32@+ 4 * DATA @ + i32@ ;
|
||||
: @store i32@+ 4 * DATA @ + i32! ;
|
||||
: @jmp i32@+ BYTECODE @ + A ! ;
|
||||
: @jz IF 4 A +! ELSE @jmp THEN ;
|
||||
: @prts CELLS STRINGS @ + @ COUNT TYPE ;
|
||||
: @div >R S>D R> SM/REM SWAP DROP ;
|
||||
CREATE OPS
|
||||
' @fetch , ' @store , ' i32@+ , ' @jmp , ' @jz ,
|
||||
' EMIT , ' . , ' @prts , ' NEGATE , ' 0= ,
|
||||
' + , ' - , ' * , ' @div , ' MOD ,
|
||||
' < , ' > , ' <= , ' >= ,
|
||||
' = , ' <> , ' AND , ' OR , ' BYE ,
|
||||
CREATE #OPS 0 ,
|
||||
: OP: CREATE #OPS @ , 1 #OPS +! DOES> @ ;
|
||||
OP: fetch OP: store OP: push OP: jmp OP: jz
|
||||
OP: prtc OP: prti OP: prts OP: neg OP: not
|
||||
OP: add OP: sub OP: mul OP: div OP: mod
|
||||
OP: lt OP: gt OP: le OP: ge
|
||||
OP: eq OP: ne OP: and OP: or OP: halt
|
||||
: >OP WORD FIND
|
||||
0= IF ." Unrecognized opcode" ABORT THEN EXECUTE ;
|
||||
: >i32 >INT i32, ;
|
||||
: >[i32] GETC DROP >i32 GETC DROP ;
|
||||
: >OFFSET WORD DROP ( drop relative offset) >i32 ;
|
||||
CREATE >PARAM ' >[i32] DUP , , ' >i32 , ' >OFFSET DUP , ,
|
||||
: >BYTECODE HERE >R
|
||||
BEGIN >INT DROP >OP >R R@ C,
|
||||
R@ 5 < IF R@ CELLS >PARAM + @ EXECUTE THEN
|
||||
R> halt = UNTIL R> BYTECODE ! ;
|
||||
: RUN BYTECODE @ A !
|
||||
BEGIN C@A+ CELLS OPS + @ EXECUTE AGAIN ;
|
||||
>HEADER >BYTECODE RUN
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,303 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type code = byte
|
||||
|
||||
const (
|
||||
fetch code = iota
|
||||
store
|
||||
push
|
||||
add
|
||||
sub
|
||||
mul
|
||||
div
|
||||
mod
|
||||
lt
|
||||
gt
|
||||
le
|
||||
ge
|
||||
eq
|
||||
ne
|
||||
and
|
||||
or
|
||||
neg
|
||||
not
|
||||
jmp
|
||||
jz
|
||||
prtc
|
||||
prts
|
||||
prti
|
||||
halt
|
||||
)
|
||||
|
||||
var codeMap = map[string]code{
|
||||
"fetch": fetch,
|
||||
"store": store,
|
||||
"push": push,
|
||||
"add": add,
|
||||
"sub": sub,
|
||||
"mul": mul,
|
||||
"div": div,
|
||||
"mod": mod,
|
||||
"lt": lt,
|
||||
"gt": gt,
|
||||
"le": le,
|
||||
"ge": ge,
|
||||
"eq": eq,
|
||||
"ne": ne,
|
||||
"and": and,
|
||||
"or": or,
|
||||
"neg": neg,
|
||||
"not": not,
|
||||
"jmp": jmp,
|
||||
"jz": jz,
|
||||
"prtc": prtc,
|
||||
"prts": prts,
|
||||
"prti": prti,
|
||||
"halt": halt,
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
scanner *bufio.Scanner
|
||||
object []code
|
||||
stringPool []string
|
||||
)
|
||||
|
||||
func reportError(msg string) {
|
||||
log.Fatalf("error : %s\n", msg)
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func btoi(b bool) int32 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func itob(i int32) bool {
|
||||
if i != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func emitByte(c code) {
|
||||
object = append(object, c)
|
||||
}
|
||||
|
||||
func emitWord(n int) {
|
||||
bs := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bs, uint32(n))
|
||||
for _, b := range bs {
|
||||
emitByte(code(b))
|
||||
}
|
||||
}
|
||||
|
||||
/*** Virtual Machine interpreter ***/
|
||||
func runVM(dataSize int) {
|
||||
stack := make([]int32, dataSize+1)
|
||||
pc := int32(0)
|
||||
for {
|
||||
op := object[pc]
|
||||
pc++
|
||||
switch op {
|
||||
case fetch:
|
||||
x := int32(binary.LittleEndian.Uint32(object[pc : pc+4]))
|
||||
stack = append(stack, stack[x])
|
||||
pc += 4
|
||||
case store:
|
||||
x := int32(binary.LittleEndian.Uint32(object[pc : pc+4]))
|
||||
ln := len(stack)
|
||||
stack[x] = stack[ln-1]
|
||||
stack = stack[:ln-1]
|
||||
pc += 4
|
||||
case push:
|
||||
x := int32(binary.LittleEndian.Uint32(object[pc : pc+4]))
|
||||
stack = append(stack, x)
|
||||
pc += 4
|
||||
case add:
|
||||
ln := len(stack)
|
||||
stack[ln-2] += stack[ln-1]
|
||||
stack = stack[:ln-1]
|
||||
case sub:
|
||||
ln := len(stack)
|
||||
stack[ln-2] -= stack[ln-1]
|
||||
stack = stack[:ln-1]
|
||||
case mul:
|
||||
ln := len(stack)
|
||||
stack[ln-2] *= stack[ln-1]
|
||||
stack = stack[:ln-1]
|
||||
case div:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = int32(float64(stack[ln-2]) / float64(stack[ln-1]))
|
||||
stack = stack[:ln-1]
|
||||
case mod:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = int32(math.Mod(float64(stack[ln-2]), float64(stack[ln-1])))
|
||||
stack = stack[:ln-1]
|
||||
case lt:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(stack[ln-2] < stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case gt:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(stack[ln-2] > stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case le:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(stack[ln-2] <= stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case ge:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(stack[ln-2] >= stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case eq:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(stack[ln-2] == stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case ne:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(stack[ln-2] != stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case and:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(itob(stack[ln-2]) && itob(stack[ln-1]))
|
||||
stack = stack[:ln-1]
|
||||
case or:
|
||||
ln := len(stack)
|
||||
stack[ln-2] = btoi(itob(stack[ln-2]) || itob(stack[ln-1]))
|
||||
stack = stack[:ln-1]
|
||||
case neg:
|
||||
ln := len(stack)
|
||||
stack[ln-1] = -stack[ln-1]
|
||||
case not:
|
||||
ln := len(stack)
|
||||
stack[ln-1] = btoi(!itob(stack[ln-1]))
|
||||
case jmp:
|
||||
x := int32(binary.LittleEndian.Uint32(object[pc : pc+4]))
|
||||
pc += x
|
||||
case jz:
|
||||
ln := len(stack)
|
||||
v := stack[ln-1]
|
||||
stack = stack[:ln-1]
|
||||
if v != 0 {
|
||||
pc += 4
|
||||
} else {
|
||||
x := int32(binary.LittleEndian.Uint32(object[pc : pc+4]))
|
||||
pc += x
|
||||
}
|
||||
case prtc:
|
||||
ln := len(stack)
|
||||
fmt.Printf("%c", stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case prts:
|
||||
ln := len(stack)
|
||||
fmt.Printf("%s", stringPool[stack[ln-1]])
|
||||
stack = stack[:ln-1]
|
||||
case prti:
|
||||
ln := len(stack)
|
||||
fmt.Printf("%d", stack[ln-1])
|
||||
stack = stack[:ln-1]
|
||||
case halt:
|
||||
return
|
||||
default:
|
||||
reportError(fmt.Sprintf("Unknown opcode %d\n", op))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func translate(s string) string {
|
||||
var d strings.Builder
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '\\' && (i+1) < len(s) {
|
||||
if s[i+1] == 'n' {
|
||||
d.WriteByte('\n')
|
||||
i++
|
||||
} else if s[i+1] == '\\' {
|
||||
d.WriteByte('\\')
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
d.WriteByte(s[i])
|
||||
}
|
||||
}
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func loadCode() int {
|
||||
var dataSize int
|
||||
firstLine := true
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimRight(scanner.Text(), " \t")
|
||||
if len(line) == 0 {
|
||||
if firstLine {
|
||||
reportError("empty line")
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
lineList := strings.Fields(line)
|
||||
if firstLine {
|
||||
dataSize, err = strconv.Atoi(lineList[1])
|
||||
check(err)
|
||||
nStrings, err := strconv.Atoi(lineList[3])
|
||||
check(err)
|
||||
for i := 0; i < nStrings; i++ {
|
||||
scanner.Scan()
|
||||
s := strings.Trim(scanner.Text(), "\"\n")
|
||||
stringPool = append(stringPool, translate(s))
|
||||
}
|
||||
firstLine = false
|
||||
continue
|
||||
}
|
||||
offset, err := strconv.Atoi(lineList[0])
|
||||
check(err)
|
||||
instr := lineList[1]
|
||||
opCode, ok := codeMap[instr]
|
||||
if !ok {
|
||||
reportError(fmt.Sprintf("Unknown instruction %s at %d", instr, opCode))
|
||||
}
|
||||
emitByte(opCode)
|
||||
switch opCode {
|
||||
case jmp, jz:
|
||||
p, err := strconv.Atoi(lineList[3])
|
||||
check(err)
|
||||
emitWord(p - offset - 1)
|
||||
case push:
|
||||
value, err := strconv.Atoi(lineList[2])
|
||||
check(err)
|
||||
emitWord(value)
|
||||
case fetch, store:
|
||||
value, err := strconv.Atoi(strings.Trim(lineList[2], "[]"))
|
||||
check(err)
|
||||
emitWord(value)
|
||||
}
|
||||
}
|
||||
check(scanner.Err())
|
||||
return dataSize
|
||||
}
|
||||
|
||||
func main() {
|
||||
codeGen, err := os.Open("codegen.txt")
|
||||
check(err)
|
||||
defer codeGen.Close()
|
||||
scanner = bufio.NewScanner(codeGen)
|
||||
runVM(loadCode())
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
# -*- Icon -*-
|
||||
#
|
||||
# The Rosetta Code virtual machine in Icon. Migrated from the
|
||||
# ObjectIcon.
|
||||
#
|
||||
# See https://rosettacode.org/wiki/Compiler/virtual_machine_interpreter
|
||||
#
|
||||
|
||||
record VirtualMachine(code, global_data, strings, stack, pc)
|
||||
|
||||
global opcode_names
|
||||
global opcode_values
|
||||
global op_halt
|
||||
global op_add
|
||||
global op_sub
|
||||
global op_mul
|
||||
global op_div
|
||||
global op_mod
|
||||
global op_lt
|
||||
global op_gt
|
||||
global op_le
|
||||
global op_ge
|
||||
global op_eq
|
||||
global op_ne
|
||||
global op_and
|
||||
global op_or
|
||||
global op_neg
|
||||
global op_not
|
||||
global op_prtc
|
||||
global op_prti
|
||||
global op_prts
|
||||
global op_fetch
|
||||
global op_store
|
||||
global op_push
|
||||
global op_jmp
|
||||
global op_jz
|
||||
|
||||
global whitespace_chars
|
||||
|
||||
procedure main(args)
|
||||
local f_inp, f_out
|
||||
local vm
|
||||
|
||||
whitespace_chars := ' \t\n\r\f\v'
|
||||
initialize_opcodes()
|
||||
|
||||
if 3 <= *args then {
|
||||
write("Usage: ", &progname, " [INPUT_FILE [OUTPUT_FILE]]")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if 1 <= *args then {
|
||||
f_inp := open(args[1], "r") | {
|
||||
write(&errout, "Failed to open ", args[1], " for reading.")
|
||||
exit(1)
|
||||
}
|
||||
} else {
|
||||
f_inp := &input
|
||||
}
|
||||
|
||||
if 2 <= *args then {
|
||||
f_out := open(args[2], "w") | {
|
||||
write(&errout, "Failed to open ", args[2], " for writing.")
|
||||
exit(1)
|
||||
}
|
||||
} else {
|
||||
f_out := &output
|
||||
}
|
||||
|
||||
vm := VirtualMachine()
|
||||
read_assembly_code(f_inp, vm)
|
||||
run_vm(f_out, vm)
|
||||
end
|
||||
|
||||
procedure initialize_opcodes()
|
||||
local i
|
||||
|
||||
opcode_names :=
|
||||
["halt", "add", "sub", "mul", "div",
|
||||
"mod", "lt", "gt", "le", "ge",
|
||||
"eq", "ne", "and", "or", "neg",
|
||||
"not", "prtc", "prti", "prts", "fetch",
|
||||
"store", "push", "jmp", "jz"]
|
||||
|
||||
opcode_values := table()
|
||||
every i := 1 to *opcode_names do
|
||||
opcode_values[opcode_names[i]] := char(i)
|
||||
|
||||
op_halt := opcode_values["halt"]
|
||||
op_add := opcode_values["add"]
|
||||
op_sub := opcode_values["sub"]
|
||||
op_mul := opcode_values["mul"]
|
||||
op_div := opcode_values["div"]
|
||||
op_mod := opcode_values["mod"]
|
||||
op_lt := opcode_values["lt"]
|
||||
op_gt := opcode_values["gt"]
|
||||
op_le := opcode_values["le"]
|
||||
op_ge := opcode_values["ge"]
|
||||
op_eq := opcode_values["eq"]
|
||||
op_ne := opcode_values["ne"]
|
||||
op_and := opcode_values["and"]
|
||||
op_or := opcode_values["or"]
|
||||
op_neg := opcode_values["neg"]
|
||||
op_not := opcode_values["not"]
|
||||
op_prtc := opcode_values["prtc"]
|
||||
op_prti := opcode_values["prti"]
|
||||
op_prts := opcode_values["prts"]
|
||||
op_fetch := opcode_values["fetch"]
|
||||
op_store := opcode_values["store"]
|
||||
op_push := opcode_values["push"]
|
||||
op_jmp := opcode_values["jmp"]
|
||||
op_jz := opcode_values["jz"]
|
||||
end
|
||||
|
||||
procedure int2bytes (n)
|
||||
local bytes
|
||||
|
||||
# The VM is little-endian.
|
||||
|
||||
bytes := "****"
|
||||
bytes[1] := char (iand(n, 16rFF))
|
||||
bytes[2] := char(iand(ishift(n, -8), 16rFF))
|
||||
bytes[3] := char(iand(ishift(n, -16), 16rFF))
|
||||
bytes[4] := char(iand(ishift(n, -24), 16rFF))
|
||||
return bytes
|
||||
end
|
||||
|
||||
procedure bytes2int(bytes, i)
|
||||
local n0, n1, n2, n3, n
|
||||
|
||||
# The VM is little-endian.
|
||||
|
||||
n0 := ord(bytes[i])
|
||||
n1 := ishift(ord(bytes[i + 1]), 8)
|
||||
n2 := ishift(ord(bytes[i + 2]), 16)
|
||||
n3 := ishift(ord(bytes[i + 3]), 24)
|
||||
n := ior (n0, ior (n1, ior (n2, n3)))
|
||||
|
||||
# Do not forget to extend the sign bit.
|
||||
return (if n3 <= 16r7F then n else ior(n, icom(16rFFFFFFFF)))
|
||||
end
|
||||
|
||||
procedure read_assembly_code(f, vm)
|
||||
local data_size, number_of_strings
|
||||
local line, ch
|
||||
local i
|
||||
local address
|
||||
local opcode
|
||||
|
||||
# Read the header line.
|
||||
line := read(f) | bad_vm()
|
||||
line ? {
|
||||
tab(many(whitespace_chars))
|
||||
tab(match("Datasize")) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(':')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
data_size :=
|
||||
integer(tab(many(&digits))) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(match("Strings")) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(':')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
number_of_strings :=
|
||||
integer(tab(many(&digits))) | bad_vm()
|
||||
}
|
||||
|
||||
# Read the strings.
|
||||
vm.strings := list(number_of_strings)
|
||||
every i := 1 to number_of_strings do {
|
||||
vm.strings[i] := ""
|
||||
line := read(f) | bad_vm()
|
||||
line ? {
|
||||
tab(many(whitespace_chars))
|
||||
tab(any('"')) | bad_vm()
|
||||
while ch := tab(any(~'"')) do {
|
||||
if ch == '\\' then {
|
||||
ch := tab(any('n\\')) | bad_vm()
|
||||
vm.strings[i] ||:=
|
||||
(if (ch == "n") then "\n" else "\\")
|
||||
} else {
|
||||
vm.strings[i] ||:= ch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Read the code.
|
||||
vm.code := ""
|
||||
while line := read(f) do {
|
||||
line ? {
|
||||
tab(many(whitespace_chars))
|
||||
address := integer(tab(many(&digits))) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
opcode := tab(many(~whitespace_chars)) | bad_vm()
|
||||
vm.code ||:= opcode_values[opcode]
|
||||
case opcode of {
|
||||
"push": {
|
||||
tab(many(whitespace_chars))
|
||||
vm.code ||:=
|
||||
int2bytes(integer(tab(many(&digits)))) |
|
||||
int2bytes(integer(tab(any('-')) ||
|
||||
tab(many(&digits)))) |
|
||||
bad_vm()
|
||||
}
|
||||
"fetch" | "store": {
|
||||
tab(many(whitespace_chars))
|
||||
tab(any('[')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
vm.code ||:=
|
||||
int2bytes(integer(tab(many(&digits)))) |
|
||||
bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(']')) | bad_vm()
|
||||
}
|
||||
"jmp" | "jz": {
|
||||
tab(many(whitespace_chars))
|
||||
tab(any('(')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
vm.code ||:=
|
||||
int2bytes(integer(tab(many(&digits)))) |
|
||||
int2bytes(integer(tab(any('-')) ||
|
||||
tab(many(&digits)))) |
|
||||
bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(')')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(many(&digits)) | bad_vm()
|
||||
}
|
||||
default: {
|
||||
# Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a global data area.
|
||||
vm.global_data := list(data_size, &null)
|
||||
|
||||
initialize_vm(vm)
|
||||
end
|
||||
|
||||
procedure run_vm(f_out, vm)
|
||||
initialize_vm(vm)
|
||||
continue_vm(f_out, vm)
|
||||
end
|
||||
|
||||
procedure continue_vm(f_out, vm)
|
||||
while vm.code[vm.pc] ~== op_halt do
|
||||
step_vm(f_out, vm)
|
||||
end
|
||||
|
||||
procedure step_vm(f_out, vm)
|
||||
local opcode
|
||||
|
||||
opcode := vm.code[vm.pc]
|
||||
vm.pc +:= 1
|
||||
case opcode of {
|
||||
op_add: binop(vm, "+")
|
||||
op_sub: binop(vm, "-")
|
||||
op_mul: binop(vm, "*")
|
||||
op_div: binop(vm, "/")
|
||||
op_mod: binop(vm, "%")
|
||||
op_lt: comparison(vm, "<")
|
||||
op_gt: comparison(vm, ">")
|
||||
op_le: comparison(vm, "<=")
|
||||
op_ge: comparison(vm, ">=")
|
||||
op_eq: comparison(vm, "=")
|
||||
op_ne: comparison(vm, "~=")
|
||||
op_and: logical_and(vm)
|
||||
op_or: logical_or(vm)
|
||||
op_neg: negate(vm)
|
||||
op_not: logical_not(vm)
|
||||
op_prtc: printc(f_out, vm)
|
||||
op_prti: printi(f_out, vm)
|
||||
op_prts: prints(f_out, vm)
|
||||
op_fetch: fetch_global(vm)
|
||||
op_store: store_global(vm)
|
||||
op_push: push_argument(vm)
|
||||
op_jmp: jump(vm)
|
||||
op_jz: jump_if_zero(vm)
|
||||
default: bad_opcode()
|
||||
}
|
||||
end
|
||||
|
||||
procedure negate(vm)
|
||||
vm.stack[1] := -vm.stack[1]
|
||||
end
|
||||
|
||||
procedure binop(vm, func)
|
||||
vm.stack[2] := func(vm.stack[2], vm.stack[1])
|
||||
pop(vm.stack)
|
||||
end
|
||||
|
||||
procedure comparison(vm, func)
|
||||
vm.stack[2] := (if func(vm.stack[2], vm.stack[1]) then 1 else 0)
|
||||
pop(vm.stack)
|
||||
end
|
||||
|
||||
procedure logical_and(vm)
|
||||
vm.stack[2] :=
|
||||
(if vm.stack[2] ~= 0 & vm.stack[1] ~= 0 then 1 else 0)
|
||||
pop(vm.stack)
|
||||
end
|
||||
|
||||
procedure logical_or(vm)
|
||||
vm.stack[2] :=
|
||||
(if vm.stack[2] ~= 0 | vm.stack[1] ~= 0 then 1 else 0)
|
||||
pop(vm.stack)
|
||||
end
|
||||
|
||||
procedure logical_not(vm)
|
||||
vm.stack[1] := (if vm.stack[1] ~= 0 then 0 else 1)
|
||||
end
|
||||
|
||||
procedure printc(f_out, vm)
|
||||
writes(f_out, char(pop(vm.stack)))
|
||||
end
|
||||
|
||||
procedure printi(f_out, vm)
|
||||
writes(f_out, pop(vm.stack))
|
||||
end
|
||||
|
||||
procedure prints(f_out, vm)
|
||||
writes(f_out, vm.strings[pop(vm.stack) + 1])
|
||||
end
|
||||
|
||||
procedure fetch_global(vm)
|
||||
push(vm.stack, vm.global_data[get_argument(vm) + 1])
|
||||
vm.pc +:= 4
|
||||
end
|
||||
|
||||
procedure store_global(vm)
|
||||
vm.global_data[get_argument(vm) + 1] := pop(vm.stack)
|
||||
vm.pc +:= 4
|
||||
end
|
||||
|
||||
procedure push_argument(vm)
|
||||
push(vm.stack, get_argument(vm))
|
||||
vm.pc +:= 4
|
||||
end
|
||||
|
||||
procedure jump(vm)
|
||||
vm.pc +:= get_argument(vm)
|
||||
end
|
||||
|
||||
procedure jump_if_zero(vm)
|
||||
if pop(vm.stack) = 0 then
|
||||
vm.pc +:= get_argument(vm)
|
||||
else
|
||||
vm.pc +:= 4
|
||||
end
|
||||
|
||||
procedure get_argument(vm)
|
||||
return bytes2int(vm.code, vm.pc)
|
||||
end
|
||||
|
||||
procedure initialize_vm(vm)
|
||||
# The program counter starts at 1, for convenient indexing into
|
||||
# the code[] array. Icon indexing starts at 1 (for a *very* good
|
||||
# reason, but that’s a topic for another day).
|
||||
vm.pc := 1
|
||||
vm.stack := []
|
||||
end
|
||||
|
||||
procedure bad_vm()
|
||||
write(&errout, "Bad VM.")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
procedure bad_opcode()
|
||||
write(&errout, "Bad opcode.")
|
||||
exit(1)
|
||||
end
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
(opcodes)=: opcodes=: ;:{{)n
|
||||
fetch store push add sub mul div mod lt gt le ge
|
||||
eq ne and or neg not jmp jz prtc prts prti halt
|
||||
}}-.LF
|
||||
|
||||
unpack=: {{
|
||||
lines=. <;._2 y
|
||||
'ds0 ds s0 s'=.;:0{::lines
|
||||
assert.'Datasize:Strings:'-:ds0,s0
|
||||
vars=: (".ds)#0
|
||||
strings=: rplc&('\\';'\';'\n';LF)L:0 '"'-.L:0~(1+i.".s){lines
|
||||
object=: ;xlate L:1 (;:'()[]') -.~L:1 ;:L:0 '-_' rplc~L:0 (1+".s)}.lines
|
||||
outbuf=: stack=: i.0
|
||||
}}
|
||||
|
||||
xlate=: {{
|
||||
if.2<#y do.
|
||||
(opcodes i. 1{y),(4#256)#:".2{::y
|
||||
else.
|
||||
opcodes i. 1{y
|
||||
end.
|
||||
}}
|
||||
|
||||
NB. ensure we maintain 32 bit signed int representation
|
||||
signadj=: _2147483648+4294967296|2147483648+]
|
||||
getint=: signadj@(256 #. ])
|
||||
|
||||
PUSH=: {{ stack=:stack,signadj y }}
|
||||
POP=: {{ (stack=: _1 }. stack) ] _1 { stack }}
|
||||
POP2=: {{ (stack=: _2 }. stack) ] _2 {. stack }}
|
||||
emit=:{{
|
||||
outbuf=: outbuf,y
|
||||
if.LF e. outbuf do.
|
||||
ndx=. outbuf i:LF
|
||||
echo ndx{.outbuf
|
||||
outbuf=: }.ndx}.outbuf
|
||||
end.
|
||||
}}
|
||||
|
||||
run_vm=: {{
|
||||
unpack y
|
||||
stack=: i.pc=:0
|
||||
lim=. <:#object
|
||||
while.do.
|
||||
pc=: pc+1 [ op=: (pc { object){opcodes
|
||||
i=. getint (lim<.pc+i.4) { object
|
||||
k=. 0
|
||||
select.op
|
||||
case.fetch do. k=.4 [PUSH i{vars
|
||||
case.store do. k=.4 [vars=: (POP'') i} vars
|
||||
case.push do. k=.4 [PUSH i
|
||||
case.add do. PUSH +/POP2''
|
||||
case.sub do. PUSH -/POP2''
|
||||
case.mul do. PUSH */POP2''
|
||||
case.div do. PUSH<.%/POP2''
|
||||
case.mod do. PUSH |~/POP2''
|
||||
case.lt do. PUSH </POP2''
|
||||
case.le do. PUSH <:/POP2''
|
||||
case.eq do. PUSH =/POP2''
|
||||
case.ne do. PUSH ~:/POP2''
|
||||
case.ge do. PUSH >:/POP2''
|
||||
case.gt do. PUSH >/POP2''
|
||||
case.and do. PUSH */0~:POP2''
|
||||
case.or do. PUSH +./0~:POP2''
|
||||
case.neg do. PUSH -POP''
|
||||
case.not do. PUSH 0=POP''
|
||||
case.jmp do. k=. i
|
||||
case.jz do. k=. (0=POP''){4,i
|
||||
case.prtc do. emit u:POP''
|
||||
case.prts do. emit (POP''){::strings
|
||||
case.prti do. emit rplc&'_-'":POP''
|
||||
case.halt do. if.#outbuf do.echo outbuf end.EMPTY return.
|
||||
end.
|
||||
pc=: pc+k
|
||||
end.
|
||||
}}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
count=:{{)n
|
||||
count = 1;
|
||||
while (count < 10) {
|
||||
print("count is: ", count, "\n");
|
||||
count = count + 1;
|
||||
}
|
||||
}}
|
||||
|
||||
run_vm gen syntax lex count
|
||||
count is: 1
|
||||
count is: 2
|
||||
count is: 3
|
||||
count is: 4
|
||||
count is: 5
|
||||
count is: 6
|
||||
count is: 7
|
||||
count is: 8
|
||||
count is: 9
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
mutable struct VM32
|
||||
code::Vector{UInt8}
|
||||
stack::Vector{Int32}
|
||||
data::Vector{Int32}
|
||||
strings::Vector{String}
|
||||
offsets::Vector{Int32}
|
||||
lastargs::Vector{Int32}
|
||||
ip::Int32
|
||||
VM32() = new(Vector{UInt8}(), Vector{Int32}(), Vector{Int32}(),
|
||||
Vector{String}(), Vector{Int32}(), Vector{Int32}(), 1)
|
||||
end
|
||||
|
||||
halt, add, sub, mul, Div, mod, not, neg, and, or, lt, gt, le, ge, ne, eq, prts,
|
||||
prti, prtc, store, Fetch, push, jmp, jz = UInt8.(collect(1:24))
|
||||
|
||||
function assemble(io)
|
||||
vm = VM32()
|
||||
header = readline(io)
|
||||
datasize, nstrings = match(r"\w+:\s*(\d+)\s+\w+:\s*(\d+)", header).captures
|
||||
vm.data = zeros(Int32, parse(Int, datasize) + 4)
|
||||
for i in 1:parse(Int, nstrings)
|
||||
line = replace(strip(readline(io), ['"', '\n']), r"\\." => x -> x[end] == 'n' ? "\n" : string(x[end]))
|
||||
push!(vm.strings, line)
|
||||
end
|
||||
while !eof(io)
|
||||
line = readline(io)
|
||||
offset, op, arg1, arg2 = match(r"(\d+)\s+(\w+)\s*(\S+)?\s*(\S+)?", line).captures
|
||||
op = op in ["fetch", "div"] ? uppercasefirst(op) : op
|
||||
push!(vm.code, eval(Symbol(op)))
|
||||
if arg1 != nothing
|
||||
v = parse(Int32, strip(arg1, ['[', ']', '(', ')']))
|
||||
foreach(x -> push!(vm.code, x), reinterpret(UInt8, [v]))
|
||||
end
|
||||
if arg2 != nothing
|
||||
push!(vm.lastargs, (x = tryparse(Int32, arg2)) == nothing ? 0 : x)
|
||||
end
|
||||
push!(vm.offsets, parse(Int32, offset))
|
||||
end
|
||||
vm
|
||||
end
|
||||
|
||||
function runvm(vm)
|
||||
value() = (x = vm.ip; vm.ip += 4; reinterpret(Int32, vm.code[x:x+3])[1])
|
||||
tobool(x) = (x != 0)
|
||||
ops = Dict(
|
||||
halt => () -> exit(),
|
||||
add => () -> begin vm.stack[end-1] += vm.stack[end]; pop!(vm.stack); vm.stack[end] end,
|
||||
sub => () -> begin vm.stack[end-1] -= vm.stack[end]; pop!(vm.stack); vm.stack[end] end,
|
||||
mul => () -> begin vm.stack[end-1] *= vm.stack[end]; pop!(vm.stack); vm.stack[end] end,
|
||||
Div => () -> begin vm.stack[end-1] /= vm.stack[end]; pop!(vm.stack); vm.stack[end] end,
|
||||
mod => () -> begin vm.stack[end-1] %= vm.stack[1]; pop!(vm.stack); vm.stack[end] end,
|
||||
not => () -> vm.stack[end] = vm.stack[end] ? 0 : 1,
|
||||
neg => () -> vm.stack[end] = -vm.stack[end],
|
||||
and => () -> begin vm.stack[end-1] = tobool(vm.stack[end-1]) && tobool(vm.stack[end]) ? 1 : 0; pop!(vm.stack); vm.stack[end] end,
|
||||
or => () -> begin vm.stack[end-1] = tobool(vm.stack[end-1]) || tobool(vm.stack[end]) ? 1 : 0; pop!(vm.stack); vm.stack[end] end,
|
||||
lt => () -> begin x = (vm.stack[end-1] < vm.stack[end] ? 1 : 0); pop!(vm.stack); vm.stack[end] = x end,
|
||||
gt => () -> begin x = (vm.stack[end-1] > vm.stack[end] ? 1 : 0); pop!(vm.stack); vm.stack[end] = x end,
|
||||
le => () -> begin x = (vm.stack[end-1] <= vm.stack[end] ? 1 : 0); pop!(vm.stack); vm.stack[end] = x end,
|
||||
ge => () -> begin x = (vm.stack[end-1] >= vm.stack[end] ? 1 : 0); pop!(vm.stack); vm.stack[end] = x end,
|
||||
ne => () -> begin x = (vm.stack[end-1] != vm.stack[end] ? 1 : 0); pop!(vm.stack); vm.stack[end] = x end,
|
||||
eq => () -> begin x = (vm.stack[end-1] == vm.stack[end] ? 1 : 0); pop!(vm.stack); vm.stack[end] = x end,
|
||||
prts => () -> print(vm.strings[pop!(vm.stack) + 1]),
|
||||
prti => () -> print(pop!(vm.stack)),
|
||||
prtc => () -> print(Char(pop!(vm.stack))),
|
||||
store => () -> vm.data[value() + 1] = pop!(vm.stack),
|
||||
Fetch => () -> push!(vm.stack, vm.data[value() + 1]),
|
||||
push => () -> push!(vm.stack, value()),
|
||||
jmp => () -> vm.ip += value(),
|
||||
jz => () -> if pop!(vm.stack) == 0 vm.ip += value() else vm.ip += 4 end)
|
||||
vm.ip = 1
|
||||
while true
|
||||
op = vm.code[vm.ip]
|
||||
vm.ip += 1
|
||||
ops[op]()
|
||||
end
|
||||
end
|
||||
|
||||
const testasm = """
|
||||
Datasize: 1 Strings: 2
|
||||
"count is: "
|
||||
"\\n"
|
||||
0 push 1
|
||||
5 store [0]
|
||||
10 fetch [0]
|
||||
15 push 10
|
||||
20 lt
|
||||
21 jz (43) 65
|
||||
26 push 0
|
||||
31 prts
|
||||
32 fetch [0]
|
||||
37 prti
|
||||
38 push 1
|
||||
43 prts
|
||||
44 fetch [0]
|
||||
49 push 1
|
||||
54 add
|
||||
55 store [0]
|
||||
60 jmp (-51) 10
|
||||
65 halt """
|
||||
|
||||
const iob = IOBuffer(testasm)
|
||||
const vm = assemble(iob)
|
||||
runvm(vm)
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
Module Virtual_Machine_Interpreter (a$){
|
||||
\\ function to extract string, replacing escape codes.
|
||||
Function GetString$(a$) {
|
||||
s=instr(a$, chr$(34))
|
||||
m=rinstr(a$,chr$(34))-s
|
||||
if m>1 then
|
||||
\\ process escape codes
|
||||
=format$(mid$(a$, s+1, m-1))
|
||||
else
|
||||
=""
|
||||
end if
|
||||
}
|
||||
\\ module to print a string to console using codes, 13, 10, 9
|
||||
Module printsrv (a$) {
|
||||
for i=1 to len(a$)
|
||||
select case chrcode(Mid$(a$,i,1))
|
||||
case 13
|
||||
cursor 0
|
||||
case 10
|
||||
cursor 0 : Print
|
||||
case 9
|
||||
cursor ((pos+tab) div tab)*tab
|
||||
else case
|
||||
{
|
||||
m=pos :if pos>=width then Print : m=pos
|
||||
Print Mid$(a$,i,1);
|
||||
if m<=width then cursor m+1
|
||||
}
|
||||
end select
|
||||
next i
|
||||
}
|
||||
const nl$=chr$(13)+chr$(10)
|
||||
\\ we can set starting value to any number n where 0<=n<=232
|
||||
enum op { halt_=232, add_, sub_, mul_, div_, mod_, not_, neg_, and_, or_, lt_,
|
||||
gt_, le_, ge_, ne_, eq_, prts_, prti_, prtc_, store_, fetch_, push_,
|
||||
jmp_, jz_
|
||||
}
|
||||
Rem : Form 120, 60 ' change console width X height to run Ascii Mandlebrot examlpe
|
||||
Report "Virtual Assembly Code:"+{
|
||||
}+a$
|
||||
Print "Prepare Byte Code"
|
||||
|
||||
\\ get datasize
|
||||
a$=rightpart$(a$, "Datasize:")
|
||||
m=0
|
||||
data_size=val(a$, "int", m)
|
||||
a$=mid$(a$, m)
|
||||
\\ make stack
|
||||
if data_size>0 then Buffer Clear stack_ as long*data_size
|
||||
\\ dim or redim buffer append 1000 long as is.
|
||||
Buffer stack_ as long*(1000+data_size)
|
||||
\\ get strings
|
||||
a$=rightpart$(a$, "Strings:")
|
||||
m=0
|
||||
strings=val(a$, "int", m)
|
||||
a$=rightpart$(a$, nl$)
|
||||
|
||||
if strings>0 then
|
||||
Dim strings$(strings)
|
||||
for i=0 to strings-1
|
||||
strings$(i)=GetString$(leftpart$(a$, nl$))
|
||||
a$=rightpart$(a$, nl$)
|
||||
Next i
|
||||
End if
|
||||
buffer clear code_ as byte*1000
|
||||
do
|
||||
m=0
|
||||
offset=val(a$,"int", m)
|
||||
if m<0 then exit
|
||||
a$=mid$(a$,m)
|
||||
line$=trim$(leftpart$(a$,nl$))
|
||||
if line$="" then line$=trim$(a$) else a$=trim$(rightpart$(a$, nl$))
|
||||
op$=if$(instr(line$," ")>0->leftpart$(line$," "), line$)
|
||||
if not valid(eval(op$+"_")) then exit
|
||||
opc=eval(op$+"_")
|
||||
Return code_, offset:=opc
|
||||
if opc>=store_ then
|
||||
line$=rightpart$(line$," ")
|
||||
select case opc
|
||||
case store_, fetch_
|
||||
Return code_, offset+1:=val(rightpart$(leftpart$(line$,"]"),"[")) as long : offset+=4
|
||||
case push_
|
||||
Return code_, offset+1:=uint(val(line$)) as long : offset+=4
|
||||
case jz_, jmp_
|
||||
Return code_, offset+1:=val(rightpart$(line$,")")) as long : offset+=4
|
||||
end select
|
||||
end if
|
||||
Always
|
||||
Print "Press any key" : Push key$ : Drop
|
||||
\\ Prepare VM
|
||||
let pc=0, sp=len(stack_) div 4
|
||||
do {
|
||||
func=eval(code_, pc)
|
||||
pc++
|
||||
select case func
|
||||
case halt_
|
||||
exit
|
||||
case push_
|
||||
sp--:return stack_, sp:=eval(code_, pc as long):pc+=4
|
||||
case jz_
|
||||
sp++: if eval(stack_, sp-1)=0 then pc=eval(code_, pc as long) else pc+=4
|
||||
case jmp_
|
||||
pc=eval(code_, pc as long)
|
||||
case fetch_
|
||||
sp--:Return stack_, sp:=eval(stack_, eval(code_, pc as long)):pc+=4
|
||||
case store_
|
||||
Return stack_, eval(code_, pc as long):=eval(stack_, sp):sp++:pc+=4
|
||||
case add_
|
||||
Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))+sint(eval(stack_, sp))):sp++
|
||||
case sub_
|
||||
Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))-sint(eval(stack_, sp))):sp++
|
||||
case mul_
|
||||
Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))*sint(eval(stack_, sp))):sp++
|
||||
case div_
|
||||
Return stack_, sp+1:=uint(sint(eval(stack_, sp+1)) div sint(eval(stack_, sp))):sp++
|
||||
case mod_
|
||||
Return stack_, sp+1:=uint(sint(eval(stack_, sp+1)) mod sint(eval(stack_, sp))) :sp++
|
||||
case not_
|
||||
Return stack_, sp:=if(eval(stack_, sp)=0->uint(-1),0)
|
||||
case neg_ \\ we can use neg(sint(value))+1 or uint(-sint(value))
|
||||
Return stack_, sp:=uint(-sint(eval(stack_, sp)))
|
||||
case and_
|
||||
Return stack_, sp+1:=binary.and(eval(stack_, sp+1),eval(stack_, sp)):sp++
|
||||
case or_
|
||||
Return stack_, sp+1:=binary.or(eval(stack_, sp+1),eval(stack_, sp)):sp++
|
||||
case lt_
|
||||
Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))<sint(eval(stack_, sp))->-1, 0)):sp++
|
||||
case gt_
|
||||
Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))>sint(eval(stack_, sp))->-1, 0)):sp++
|
||||
case le_
|
||||
Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))<=sint(eval(stack_, sp))->-1, 0)):sp++
|
||||
case ge_
|
||||
Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))>=sint(eval(stack_, sp))->-1, 0)):sp++
|
||||
case ne_
|
||||
Return stack_, sp+1:=uint(if(eval(stack_, sp+1)<>eval(stack_, sp)->-1, 0)):sp++
|
||||
case eq_
|
||||
Return stack_, sp+1:=uint(if(eval(stack_, sp+1)=eval(stack_, sp)->-1, 0)):sp++
|
||||
case prts_
|
||||
printsrv strings$(eval(stack_,sp)):sp++
|
||||
case prti_
|
||||
printsrv str$(sint(eval(stack_,sp)),0):sp++
|
||||
case prtc_
|
||||
printsrv chrcode$(eval(stack_,sp)):sp++
|
||||
else case
|
||||
Error "Unkown op "+str$(func)
|
||||
end select
|
||||
} always
|
||||
Print "done"
|
||||
}
|
||||
Virtual_Machine_Interpreter {
|
||||
Datasize: 1 Strings: 2
|
||||
"count is: "
|
||||
"\n"
|
||||
0 push 1
|
||||
5 store [0]
|
||||
10 fetch [0]
|
||||
15 push 10
|
||||
20 lt
|
||||
21 jz (43) 65
|
||||
26 push 0
|
||||
31 prts
|
||||
32 fetch [0]
|
||||
37 prti
|
||||
38 push 1
|
||||
43 prts
|
||||
44 fetch [0]
|
||||
49 push 1
|
||||
54 add
|
||||
55 store [0]
|
||||
60 jmp (-51) 10
|
||||
65 halt
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
Module Virtual_Machine_Interpreter (a$){
|
||||
\\ function to extract string, replacing escape codes.
|
||||
Function GetString$(a$) {
|
||||
s=instr(a$, chr$(34))
|
||||
m=rinstr(a$,chr$(34))-s
|
||||
if m>1 then
|
||||
\\ process escape codes
|
||||
=format$(mid$(a$, s+1, m-1))
|
||||
else
|
||||
=""
|
||||
end if
|
||||
}
|
||||
\\ module to print a string to console using codes, 13, 10, 9
|
||||
Module printsrv (a$) {
|
||||
for i=1 to len(a$)
|
||||
select case chrcode(Mid$(a$,i,1))
|
||||
case 13
|
||||
cursor 0
|
||||
case 10
|
||||
cursor 0 : Print
|
||||
case 9
|
||||
cursor ((pos+tab) div tab)*tab
|
||||
else case
|
||||
{
|
||||
m=pos :if pos>=width then Print : m=pos
|
||||
Print Mid$(a$,i,1);
|
||||
if m<=width then cursor m+1
|
||||
}
|
||||
end select
|
||||
next i
|
||||
}
|
||||
const nl$=chr$(13)+chr$(10)
|
||||
\\ we can set starting value to any number n where 0<=n<=232
|
||||
enum op { halt_=232, add_, sub_, mul_, div_, mod_, not_, neg_, and_, or_, lt_,
|
||||
gt_, le_, ge_, ne_, eq_, prts_, prti_, prtc_, store_, fetch_, push_,
|
||||
jmp_, jz_
|
||||
}
|
||||
exit_now=false
|
||||
Inventory func=halt_:=lambda->{exit_now=true}
|
||||
Append func, push_:=lambda->{sp--:return stack_, sp:=eval(code_, pc as long):pc+=4}
|
||||
Append func, jz_:=lambda->{
|
||||
sp++: if eval(stack_, sp-1)=0 then pc=eval(code_, pc as long) else pc+=4
|
||||
}
|
||||
Append func, jmp_:=lambda->{pc=eval(code_, pc as long)}
|
||||
Append func, fetch_:=lambda->{sp--:Return stack_, sp:=eval(stack_, eval(code_, pc as long)):pc+=4}
|
||||
Append func, store_:=lambda->{Return stack_, eval(code_, pc as long):=eval(stack_, sp):sp++:pc+=4}
|
||||
Append func, add_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))+sint(eval(stack_, sp))):sp++}
|
||||
Append func, sub_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))-sint(eval(stack_, sp))):sp++}
|
||||
Append func, mul_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1))*sint(eval(stack_, sp))):sp++}
|
||||
Append func, div_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1)) div sint(eval(stack_, sp))):sp++}
|
||||
Append func, mod_:=lambda->{Return stack_, sp+1:=uint(sint(eval(stack_, sp+1)) mod sint(eval(stack_, sp))) :sp++}
|
||||
Append func, not_:=lambda->{Return stack_, sp:=if(eval(stack_, sp)=0->uint(-1),0)}
|
||||
Append func, neg_:=lambda->{Return stack_, sp:=uint(-sint(eval(stack_, sp)))}
|
||||
Append func, and_:=lambda->{Return stack_, sp+1:=binary.and(eval(stack_, sp+1),eval(stack_, sp)):sp++ }
|
||||
Append func, or_:=lambda->{Return stack_, sp+1:=binary.or(eval(stack_, sp+1),eval(stack_, sp)):sp++ }
|
||||
Append func, lt_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))<sint(eval(stack_, sp))->-1, 0)):sp++}
|
||||
Append func, gt_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))>sint(eval(stack_, sp))->-1, 0)):sp++}
|
||||
Append func, le_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))<=sint(eval(stack_, sp))->-1, 0)):sp++}
|
||||
Append func, ge_:=lambda->{Return stack_, sp+1:=uint(if(sint(eval(stack_, sp+1))>=sint(eval(stack_, sp))->-1, 0)):sp++}
|
||||
Append func, ne_:=lambda->{Return stack_, sp+1:=uint(if(eval(stack_, sp+1)<>eval(stack_, sp)->-1, 0)):sp++}
|
||||
Append func, eq_:=lambda->{Return stack_, sp+1:=uint(if(eval(stack_, sp+1)=eval(stack_, sp)->-1, 0)):sp++}
|
||||
Append func, prts_:=lambda->{printsrv strings$(eval(stack_,sp)):sp++}
|
||||
Append func, prti_:=lambda->{printsrv str$(sint(eval(stack_,sp)),0):sp++}
|
||||
Append func, prtc_:=lambda->{printsrv chrcode$(eval(stack_,sp)):sp++}
|
||||
Rem : Form 120, 60 ' change console width X height to run Ascii Mandlebrot examlpe
|
||||
Report "Virtual Assembly Code:"+{
|
||||
}+a$
|
||||
Print "Prepare Byte Code"
|
||||
|
||||
\\ get datasize
|
||||
a$=rightpart$(a$, "Datasize:")
|
||||
m=0
|
||||
data_size=val(a$, "int", m)
|
||||
a$=mid$(a$, m)
|
||||
\\ make stack
|
||||
if data_size>0 then Buffer Clear stack_ as long*data_size
|
||||
\\ dim or redim buffer append 1000 long as is.
|
||||
Buffer stack_ as long*(1000+data_size)
|
||||
\\ get strings
|
||||
a$=rightpart$(a$, "Strings:")
|
||||
m=0
|
||||
strings=val(a$, "int", m)
|
||||
a$=rightpart$(a$, nl$)
|
||||
|
||||
if strings>0 then
|
||||
Dim strings$(strings)
|
||||
for i=0 to strings-1
|
||||
strings$(i)=GetString$(leftpart$(a$, nl$))
|
||||
a$=rightpart$(a$, nl$)
|
||||
Next i
|
||||
End if
|
||||
buffer clear code_ as byte*1000
|
||||
do
|
||||
m=0
|
||||
offset=val(a$,"int", m)
|
||||
if m<0 then exit
|
||||
a$=mid$(a$,m)
|
||||
line$=trim$(leftpart$(a$,nl$))
|
||||
if line$="" then line$=trim$(a$) else a$=trim$(rightpart$(a$, nl$))
|
||||
op$=if$(instr(line$," ")>0->leftpart$(line$," "), line$)
|
||||
if not valid(eval(op$+"_")) then exit
|
||||
opc=eval(op$+"_")
|
||||
Return code_, offset:=opc
|
||||
if opc>=store_ then
|
||||
line$=rightpart$(line$," ")
|
||||
select case opc
|
||||
case store_, fetch_
|
||||
Return code_, offset+1:=val(rightpart$(leftpart$(line$,"]"),"[")) as long : offset+=4
|
||||
case push_
|
||||
Return code_, offset+1:=uint(val(line$)) as long : offset+=4
|
||||
case jz_, jmp_
|
||||
Return code_, offset+1:=val(rightpart$(line$,")")) as long : offset+=4
|
||||
end select
|
||||
end if
|
||||
Always
|
||||
Print "Press any key" : Push key$ : Drop
|
||||
\\ Prepare VM
|
||||
let pc=0, sp=len(stack_) div 4
|
||||
do
|
||||
b=func(eval(code_, pc))
|
||||
pc++
|
||||
call local b()
|
||||
until exit_now
|
||||
Print "done"
|
||||
}
|
||||
Virtual_Machine_Interpreter {
|
||||
Datasize: 1 Strings: 2
|
||||
"count is: "
|
||||
"\n"
|
||||
0 push 1
|
||||
5 store [0]
|
||||
10 fetch [0]
|
||||
15 push 10
|
||||
20 lt
|
||||
21 jz (43) 65
|
||||
26 push 0
|
||||
31 prts
|
||||
32 fetch [0]
|
||||
37 prti
|
||||
38 push 1
|
||||
43 prts
|
||||
44 fetch [0]
|
||||
49 push 1
|
||||
54 add
|
||||
55 store [0]
|
||||
60 jmp (-51) 10
|
||||
65 halt
|
||||
}
|
||||
|
|
@ -0,0 +1,892 @@
|
|||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%
|
||||
%%% The Rosetta Code Virtual Machine, in Mercury.
|
||||
%%%
|
||||
%%% (This particular machine is arbitrarily chosen to be big-endian.)
|
||||
%%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
:- module vm.
|
||||
|
||||
:- interface.
|
||||
:- import_module io.
|
||||
:- pred main(io::di, io::uo) is det.
|
||||
|
||||
:- implementation.
|
||||
:- import_module array.
|
||||
:- import_module bool.
|
||||
:- import_module char.
|
||||
:- import_module exception.
|
||||
:- import_module int.
|
||||
:- import_module int32.
|
||||
:- import_module list.
|
||||
:- import_module string.
|
||||
:- import_module uint.
|
||||
:- import_module uint8.
|
||||
:- import_module uint32.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%
|
||||
%%% uint32 operations.
|
||||
%%%
|
||||
|
||||
:- func twos_cmp(uint32) = uint32.
|
||||
:- mode twos_cmp(in) = out is det.
|
||||
:- pragma inline(twos_cmp/1).
|
||||
twos_cmp(U) = NegU :-
|
||||
(NegU = (\U) + 1_u32).
|
||||
|
||||
:- func unsigned_add(uint32, uint32) = uint32.
|
||||
:- mode unsigned_add(in, in) = out is det.
|
||||
:- pragma inline(unsigned_add/2).
|
||||
unsigned_add(U, V) = U_plus_V :-
|
||||
(U_plus_V = U + V).
|
||||
|
||||
:- func unsigned_sub(uint32, uint32) = uint32.
|
||||
:- mode unsigned_sub(in, in) = out is det.
|
||||
:- pragma inline(unsigned_sub/2).
|
||||
unsigned_sub(U, V) = U_minus_V :-
|
||||
(U_minus_V = U - V).
|
||||
|
||||
:- func signed_mul(uint32, uint32) = uint32.
|
||||
:- mode signed_mul(in, in) = out is det.
|
||||
:- pragma inline(signed_mul/2).
|
||||
signed_mul(U, V) = UV :-
|
||||
UV = cast_from_int32(cast_from_uint32(U) * cast_from_uint32(V)).
|
||||
|
||||
:- func signed_quot(uint32, uint32) = uint32.
|
||||
:- mode signed_quot(in, in) = out is det.
|
||||
:- pragma inline(signed_quot/2).
|
||||
signed_quot(U, V) = U_quot_V :- % Truncation towards zero.
|
||||
U_quot_V = cast_from_int32(cast_from_uint32(U)
|
||||
// cast_from_uint32(V)).
|
||||
|
||||
:- func signed_rem(uint32, uint32) = uint32.
|
||||
:- mode signed_rem(in, in) = out is det.
|
||||
:- pragma inline(signed_rem/2).
|
||||
signed_rem(U, V) = U_rem_V :- % Truncation towards zero, sign of U.
|
||||
U_rem_V = cast_from_int32(cast_from_uint32(U)
|
||||
rem cast_from_uint32(V)).
|
||||
|
||||
:- func signed_lt(uint32, uint32) = uint32.
|
||||
:- mode signed_lt(in, in) = out is det.
|
||||
:- pragma inline(signed_lt/2).
|
||||
signed_lt(U, V) = U_lt_V :-
|
||||
if (int32.cast_from_uint32(U) < int32.cast_from_uint32(V))
|
||||
then (U_lt_V = 1_u32)
|
||||
else (U_lt_V = 0_u32).
|
||||
|
||||
:- func signed_le(uint32, uint32) = uint32.
|
||||
:- mode signed_le(in, in) = out is det.
|
||||
:- pragma inline(signed_le/2).
|
||||
signed_le(U, V) = U_le_V :-
|
||||
if (int32.cast_from_uint32(U) =< int32.cast_from_uint32(V))
|
||||
then (U_le_V = 1_u32)
|
||||
else (U_le_V = 0_u32).
|
||||
|
||||
:- func signed_gt(uint32, uint32) = uint32.
|
||||
:- mode signed_gt(in, in) = out is det.
|
||||
:- pragma inline(signed_gt/2).
|
||||
signed_gt(U, V) = U_gt_V :-
|
||||
U_gt_V = signed_lt(V, U).
|
||||
|
||||
:- func signed_ge(uint32, uint32) = uint32.
|
||||
:- mode signed_ge(in, in) = out is det.
|
||||
:- pragma inline(signed_ge/2).
|
||||
signed_ge(U, V) = U_ge_V :-
|
||||
U_ge_V = signed_le(V, U).
|
||||
|
||||
:- func unsigned_eq(uint32, uint32) = uint32.
|
||||
:- mode unsigned_eq(in, in) = out is det.
|
||||
:- pragma inline(unsigned_eq/2).
|
||||
unsigned_eq(U, V) = U_eq_V :-
|
||||
if (U = V)
|
||||
then (U_eq_V = 1_u32)
|
||||
else (U_eq_V = 0_u32).
|
||||
|
||||
:- func unsigned_ne(uint32, uint32) = uint32.
|
||||
:- mode unsigned_ne(in, in) = out is det.
|
||||
:- pragma inline(unsigned_ne/2).
|
||||
unsigned_ne(U, V) = U_ne_V :-
|
||||
if (U \= V)
|
||||
then (U_ne_V = 1_u32)
|
||||
else (U_ne_V = 0_u32).
|
||||
|
||||
:- func logical_cmp(uint32) = uint32.
|
||||
:- mode logical_cmp(in) = out is det.
|
||||
:- pragma inline(logical_cmp/1).
|
||||
logical_cmp(U) = NotU :-
|
||||
if (U = 0_u32)
|
||||
then (NotU = 1_u32)
|
||||
else (NotU = 0_u32).
|
||||
|
||||
:- func logical_and(uint32, uint32) = uint32.
|
||||
:- mode logical_and(in, in) = out is det.
|
||||
:- pragma inline(logical_and/2).
|
||||
logical_and(U, V) = U_and_V :-
|
||||
if (U \= 0_u32, V \= 0_u32)
|
||||
then (U_and_V = 1_u32)
|
||||
else (U_and_V = 0_u32).
|
||||
|
||||
:- func logical_or(uint32, uint32) = uint32.
|
||||
:- mode logical_or(in, in) = out is det.
|
||||
:- pragma inline(logical_or/2).
|
||||
logical_or(U, V) = U_or_V :-
|
||||
if (U \= 0_u32; V \= 0_u32)
|
||||
then (U_or_V = 1_u32)
|
||||
else (U_or_V = 0_u32).
|
||||
|
||||
:- pred to_bytes(uint32, uint8, uint8, uint8, uint8).
|
||||
:- mode to_bytes(in, out, out, out, out) is det.
|
||||
:- pragma inline(to_bytes/5).
|
||||
to_bytes(U, B3, B2, B1, B0) :-
|
||||
(B0 = cast_from_int(cast_to_int(U /\ 0xFF_u32))),
|
||||
(B1 = cast_from_int(cast_to_int((U >> 8) /\ 0xFF_u32))),
|
||||
(B2 = cast_from_int(cast_to_int((U >> 16) /\ 0xFF_u32))),
|
||||
(B3 = cast_from_int(cast_to_int((U >> 24) /\ 0xFF_u32))).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%
|
||||
%%% String operations.
|
||||
%%%
|
||||
|
||||
:- pred digit_u32(char, uint32).
|
||||
:- mode digit_u32(in, out) is semidet.
|
||||
digit_u32(('0'), 0_u32).
|
||||
digit_u32(('1'), 1_u32).
|
||||
digit_u32(('2'), 2_u32).
|
||||
digit_u32(('3'), 3_u32).
|
||||
digit_u32(('4'), 4_u32).
|
||||
digit_u32(('5'), 5_u32).
|
||||
digit_u32(('6'), 6_u32).
|
||||
digit_u32(('7'), 7_u32).
|
||||
digit_u32(('8'), 8_u32).
|
||||
digit_u32(('9'), 9_u32).
|
||||
|
||||
:- pred is_not_digit(char).
|
||||
:- mode is_not_digit(in) is semidet.
|
||||
is_not_digit(C) :-
|
||||
not is_digit(C).
|
||||
|
||||
:- pred is_not_alnum_nor_minus(char).
|
||||
:- mode is_not_alnum_nor_minus(in) is semidet.
|
||||
is_not_alnum_nor_minus(C) :-
|
||||
not (is_alnum(C); C = ('-')).
|
||||
|
||||
:- pred det_string_to_uint32(string, uint32).
|
||||
:- mode det_string_to_uint32(in, out) is det.
|
||||
det_string_to_uint32(S, U) :-
|
||||
to_char_list(S) = CL,
|
||||
(if (det_string_to_uint32_loop(CL, 0_u32, U1))
|
||||
then (U = U1)
|
||||
else throw("cannot convert string to uint32")).
|
||||
|
||||
:- pred det_string_to_uint32_loop(list(char), uint32, uint32).
|
||||
:- mode det_string_to_uint32_loop(in, in, out) is semidet.
|
||||
det_string_to_uint32_loop([], U0, U1) :- U1 = U0.
|
||||
det_string_to_uint32_loop([C | Tail], U0, U1) :-
|
||||
digit_u32(C, Digit),
|
||||
det_string_to_uint32_loop(Tail, (U0 * 10_u32) + Digit, U1).
|
||||
|
||||
:- pred det_signed_string_to_uint32(string, uint32).
|
||||
:- mode det_signed_string_to_uint32(in, out) is det.
|
||||
det_signed_string_to_uint32(S, U) :-
|
||||
if prefix(S, "-")
|
||||
then (det_remove_prefix("-", S, S1),
|
||||
det_string_to_uint32(S1, U1),
|
||||
U = twos_cmp(U1))
|
||||
else det_string_to_uint32(S, U).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%
|
||||
%%% Parsing the "assembly" language.
|
||||
%%%
|
||||
|
||||
:- func opcode_halt = uint8.
|
||||
:- func opcode_add = uint8.
|
||||
:- func opcode_sub = uint8.
|
||||
:- func opcode_mul = uint8.
|
||||
:- func opcode_div = uint8.
|
||||
:- func opcode_mod = uint8.
|
||||
:- func opcode_lt = uint8.
|
||||
:- func opcode_gt = uint8.
|
||||
:- func opcode_le = uint8.
|
||||
:- func opcode_ge = uint8.
|
||||
:- func opcode_eq = uint8.
|
||||
:- func opcode_ne = uint8.
|
||||
:- func opcode_and = uint8.
|
||||
:- func opcode_or = uint8.
|
||||
:- func opcode_neg = uint8.
|
||||
:- func opcode_not = uint8.
|
||||
:- func opcode_prtc = uint8.
|
||||
:- func opcode_prti = uint8.
|
||||
:- func opcode_prts = uint8.
|
||||
:- func opcode_fetch = uint8.
|
||||
:- func opcode_store = uint8.
|
||||
:- func opcode_push = uint8.
|
||||
:- func opcode_jmp = uint8.
|
||||
:- func opcode_jz = uint8.
|
||||
opcode_halt = 0_u8.
|
||||
opcode_add = 1_u8.
|
||||
opcode_sub = 2_u8.
|
||||
opcode_mul = 3_u8.
|
||||
opcode_div = 4_u8.
|
||||
opcode_mod = 5_u8.
|
||||
opcode_lt = 6_u8.
|
||||
opcode_gt = 7_u8.
|
||||
opcode_le = 8_u8.
|
||||
opcode_ge = 9_u8.
|
||||
opcode_eq = 10_u8.
|
||||
opcode_ne = 11_u8.
|
||||
opcode_and = 12_u8.
|
||||
opcode_or = 13_u8.
|
||||
opcode_neg = 14_u8.
|
||||
opcode_not = 15_u8.
|
||||
opcode_prtc = 16_u8.
|
||||
opcode_prti = 17_u8.
|
||||
opcode_prts = 18_u8.
|
||||
opcode_fetch = 19_u8.
|
||||
opcode_store = 20_u8.
|
||||
opcode_push = 21_u8.
|
||||
opcode_jmp = 22_u8.
|
||||
opcode_jz = 23_u8.
|
||||
|
||||
:- pred opcode(string, uint8).
|
||||
:- mode opcode(in, out) is semidet.
|
||||
%:- mode opcode(out, in) is semidet. <-- Not needed.
|
||||
opcode("halt", opcode_halt).
|
||||
opcode("add", opcode_add).
|
||||
opcode("sub", opcode_sub).
|
||||
opcode("mul", opcode_mul).
|
||||
opcode("div", opcode_div).
|
||||
opcode("mod", opcode_mod).
|
||||
opcode("lt", opcode_lt).
|
||||
opcode("gt", opcode_gt).
|
||||
opcode("le", opcode_le).
|
||||
opcode("ge", opcode_ge).
|
||||
opcode("eq", opcode_eq).
|
||||
opcode("ne", opcode_ne).
|
||||
opcode("and", opcode_and).
|
||||
opcode("or", opcode_or).
|
||||
opcode("neg", opcode_neg).
|
||||
opcode("not", opcode_not).
|
||||
opcode("prtc", opcode_prtc).
|
||||
opcode("prti", opcode_prti).
|
||||
opcode("prts", opcode_prts).
|
||||
opcode("fetch", opcode_fetch).
|
||||
opcode("store", opcode_store).
|
||||
opcode("push", opcode_push).
|
||||
opcode("jmp", opcode_jmp).
|
||||
opcode("jz", opcode_jz).
|
||||
|
||||
:- pred parse_header(string, uint32, uint32).
|
||||
:- mode parse_header(in, out, out) is det.
|
||||
parse_header(S, Datasize, Strings_Count) :-
|
||||
% Split S on any non-digit characters, leaving a list of the two
|
||||
% runs of digits.
|
||||
if (words_separator(is_not_digit, S) = [S_Datasize, S_Strings])
|
||||
% Convert the runs of digits to uint32.
|
||||
then (det_string_to_uint32(S_Datasize, Datasize),
|
||||
det_string_to_uint32(S_Strings, Strings_Count))
|
||||
else throw("cannot parse the header").
|
||||
|
||||
:- pred parse_string_literal(string, string).
|
||||
:- mode parse_string_literal(in, out) is det.
|
||||
parse_string_literal(S0, S) :-
|
||||
% Strip leading/trailing space.
|
||||
S1 = strip(S0),
|
||||
% Remove the " characters.
|
||||
det_remove_prefix("\"", S1, S2),
|
||||
det_remove_suffix(S2, "\"") = S3,
|
||||
% Deal with "\\" and "\n".
|
||||
replace_escapes(S3, S).
|
||||
|
||||
:- pred replace_escapes(string, string).
|
||||
:- mode replace_escapes(in, out) is det.
|
||||
replace_escapes(S0, S) :-
|
||||
CL0 = to_char_list(S0),
|
||||
replace_escapes(CL0, [], CL),
|
||||
S = from_rev_char_list(CL).
|
||||
|
||||
:- pred replace_escapes(list(char), list(char), list(char)).
|
||||
:- mode replace_escapes(in, in, out) is det.
|
||||
replace_escapes([], Dst0, Dst) :-
|
||||
Dst = Dst0.
|
||||
replace_escapes([C | Tail], Dst0, Dst) :-
|
||||
if (C \= ('\\'))
|
||||
then replace_escapes(Tail, [C | Dst0], Dst)
|
||||
else (if (Tail = [C1 | Tail1])
|
||||
then (if (C1 = ('n'))
|
||||
then replace_escapes(Tail1, [('\n') | Dst0], Dst)
|
||||
else if (C1 = ('\\'))
|
||||
then replace_escapes(Tail1, [('\\') | Dst0], Dst)
|
||||
else throw("illegal escape sequence"))
|
||||
else throw("truncated escape sequence")).
|
||||
|
||||
:- pred parse_instruction(string, {uint32, uint8, uint32}).
|
||||
:- mode parse_instruction(in, out) is det.
|
||||
parse_instruction(S, {Address, Opcode, Arg}) :-
|
||||
words_separator(is_not_alnum_nor_minus, S) = Lst,
|
||||
(if parse_instr_lst(Lst, {Addr, Op, A})
|
||||
then (Address = Addr, Opcode = Op, Arg = A)
|
||||
else throw("cannot parse instruction")).
|
||||
|
||||
:- pred parse_instr_lst(list(string), {uint32, uint8, uint32}).
|
||||
:- mode parse_instr_lst(in, out) is semidet.
|
||||
parse_instr_lst([S_Address, S_Opcode],
|
||||
{Address, Opcode, Arg}) :-
|
||||
det_string_to_uint32(S_Address, Address),
|
||||
opcode(S_Opcode, Opcode),
|
||||
Arg = 0_u32.
|
||||
parse_instr_lst([S_Address, S_Opcode, S_Arg | _],
|
||||
{Address, Opcode, Arg}) :-
|
||||
det_string_to_uint32(S_Address, Address),
|
||||
opcode(S_Opcode, Opcode),
|
||||
det_signed_string_to_uint32(S_Arg, Arg).
|
||||
|
||||
:- pred parse_assembly((io.text_input_stream), uint32, uint32,
|
||||
array(string),
|
||||
list({uint32, uint8, uint32}),
|
||||
io, io).
|
||||
:- mode parse_assembly(in, out, out, out, out, di, uo) is det.
|
||||
parse_assembly(InpF, Datasize, Strings_Count, Strings,
|
||||
Instructions, !IO) :-
|
||||
read_line_as_string(InpF, Res, !IO),
|
||||
(if (Res = ok(Line))
|
||||
then (parse_header(Line, Datasize, Strings_Count),
|
||||
read_and_parse_strings(InpF, Strings_Count, Strings, !IO),
|
||||
read_and_parse_instructions(InpF, Instructions, !IO))
|
||||
else if (Res = eof)
|
||||
then throw("empty input")
|
||||
else throw("read error")).
|
||||
|
||||
:- pred read_and_parse_strings((io.text_input_stream), uint32,
|
||||
array(string), io, io).
|
||||
:- mode read_and_parse_strings(in, in, out, di, uo) is det.
|
||||
read_and_parse_strings(InpF, Strings_Count, Strings, !IO) :-
|
||||
read_n_string_literals(InpF, Strings_Count, [], Lst, !IO),
|
||||
Strings = array(Lst).
|
||||
|
||||
:- pred read_n_string_literals((io.text_input_stream), uint32,
|
||||
list(string), list(string),
|
||||
io, io).
|
||||
:- mode read_n_string_literals(in, in, in, out, di, uo) is det.
|
||||
read_n_string_literals(InpF, N, Lst0, Lst, !IO) :-
|
||||
if (N = 0_u32)
|
||||
then (Lst = reverse(Lst0))
|
||||
else (read_line_as_string(InpF, Res, !IO),
|
||||
(if (Res = ok(Line))
|
||||
then (parse_string_literal(Line, S),
|
||||
read_n_string_literals(InpF, N - 1_u32,
|
||||
[S | Lst0], Lst, !IO))
|
||||
else if (Res = eof)
|
||||
then throw("premature end of input")
|
||||
else throw("read error"))).
|
||||
|
||||
:- pred read_and_parse_instructions((io.text_input_stream),
|
||||
list({uint32, uint8, uint32}),
|
||||
io, io).
|
||||
:- mode read_and_parse_instructions(in, out, di, uo) is det.
|
||||
read_and_parse_instructions(InpF, Instructions, !IO) :-
|
||||
read_all_instructions(InpF, [], Instructions, !IO).
|
||||
|
||||
:- pred read_all_instructions((io.text_input_stream),
|
||||
list({uint32, uint8, uint32}),
|
||||
list({uint32, uint8, uint32}),
|
||||
io, io).
|
||||
:- mode read_all_instructions(in, in, out, di, uo) is det.
|
||||
read_all_instructions(InpF, Lst0, Lst, !IO) :-
|
||||
read_line_as_string(InpF, Res, !IO),
|
||||
(if (Res = eof)
|
||||
then (Lst = Lst0) % There is no need to reverse the list.
|
||||
else if (Res = ok(Line))
|
||||
then (strip(Line) = S,
|
||||
(if is_empty(S)
|
||||
then read_all_instructions(InpF, Lst0, Lst, !IO)
|
||||
else (parse_instruction(S, Instr),
|
||||
read_all_instructions(InpF, [Instr | Lst0], Lst,
|
||||
!IO))))
|
||||
else throw("read error")).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%
|
||||
%%% Constructing the executable memory.
|
||||
%%%
|
||||
|
||||
:- func greatest_address(list({uint32, uint8, uint32}),
|
||||
uint32) = uint32.
|
||||
:- mode greatest_address(in, in) = out is det.
|
||||
greatest_address([], Min_Result) = Result :-
|
||||
Result = Min_Result.
|
||||
greatest_address([{Addr, _, _} | Tail], Min_Result) = Result :-
|
||||
if (Min_Result < Addr)
|
||||
then (Result = greatest_address(Tail, Addr))
|
||||
else (Result = greatest_address(Tail, Min_Result)).
|
||||
|
||||
:- pred executable_memory(list({uint32, uint8, uint32}),
|
||||
array(uint8)).
|
||||
:- mode executable_memory(in, out) is det.
|
||||
executable_memory(Instructions, Code) :-
|
||||
greatest_address(Instructions, 0_u32) = Addr,
|
||||
Code_Size = (Addr + 5_u32), % At least enough memory.
|
||||
init(cast_to_int(Code_Size), opcode_halt, Code0),
|
||||
fill_executable_memory(Instructions, Code0, Code).
|
||||
|
||||
:- pred fill_executable_memory(list({uint32, uint8, uint32}),
|
||||
array(uint8), array(uint8)).
|
||||
:- mode fill_executable_memory(in, array_di, array_uo) is det.
|
||||
fill_executable_memory([], !Code) :- true.
|
||||
fill_executable_memory([Instr | Tail], !Code) :-
|
||||
Instr = {Address, Opcode, Arg},
|
||||
Addr = cast_to_int(Address),
|
||||
set(Addr, Opcode, !Code),
|
||||
(if (Opcode = opcode_fetch;
|
||||
Opcode = opcode_store;
|
||||
Opcode = opcode_push;
|
||||
Opcode = opcode_jmp;
|
||||
Opcode = opcode_jz)
|
||||
then (to_bytes(Arg, B3, B2, B1, B0),
|
||||
% Store the argument in big-endian order.
|
||||
set(Addr + 1, B3, !Code),
|
||||
set(Addr + 2, B2, !Code),
|
||||
set(Addr + 3, B1, !Code),
|
||||
set(Addr + 4, B0, !Code))
|
||||
else true),
|
||||
fill_executable_memory(Tail, !Code).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%e
|
||||
%%%
|
||||
%%% Executing the code.
|
||||
%%%
|
||||
|
||||
:- pred machine_add(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_add(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_add/4).
|
||||
machine_add(Stack0, Stack, SP0, SP) :-
|
||||
Result = unsigned_add(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_sub(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_sub(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_sub/4).
|
||||
machine_sub(Stack0, Stack, SP0, SP) :-
|
||||
Result = unsigned_sub(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_mul(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_mul(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_mul/4).
|
||||
machine_mul(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_mul(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_div(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_div(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_div/4).
|
||||
machine_div(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_quot(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_mod(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_mod(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_mod/4).
|
||||
machine_mod(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_rem(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_lt(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_lt(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_lt/4).
|
||||
machine_lt(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_lt(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_le(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_le(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_le/4).
|
||||
machine_le(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_le(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_gt(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_gt(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_gt/4).
|
||||
machine_gt(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_gt(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_ge(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_ge(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_ge/4).
|
||||
machine_ge(Stack0, Stack, SP0, SP) :-
|
||||
Result = signed_ge(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_eq(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_eq(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_eq/4).
|
||||
machine_eq(Stack0, Stack, SP0, SP) :-
|
||||
Result = unsigned_eq(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_ne(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_ne(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_ne/4).
|
||||
machine_ne(Stack0, Stack, SP0, SP) :-
|
||||
Result = unsigned_ne(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_and(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_and(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_and/4).
|
||||
machine_and(Stack0, Stack, SP0, SP) :-
|
||||
Result = logical_and(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_or(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_or(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_or/4).
|
||||
machine_or(Stack0, Stack, SP0, SP) :-
|
||||
Result = logical_or(lookup(Stack0, cast_to_int(SP - 1_u32)),
|
||||
lookup(Stack0, cast_to_int(SP))),
|
||||
set(cast_to_int(SP - 1_u32), Result, Stack0, Stack),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_neg(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_neg(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_neg/4).
|
||||
machine_neg(Stack0, Stack, SP0, SP) :-
|
||||
SP = SP0,
|
||||
(I = uint32.cast_to_int(SP0)),
|
||||
Result = twos_cmp(lookup(Stack0, I - 1)),
|
||||
set(I - 1, Result, Stack0, Stack).
|
||||
|
||||
:- pred machine_not(array(uint32), array(uint32), uint32, uint32).
|
||||
:- mode machine_not(array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_not/4).
|
||||
machine_not(Stack0, Stack, SP0, SP) :-
|
||||
SP = SP0,
|
||||
(I = uint32.cast_to_int(SP0)),
|
||||
Result = logical_cmp(lookup(Stack0, I - 1)),
|
||||
set(I - 1, Result, Stack0, Stack).
|
||||
|
||||
:- pred machine_prtc((io.text_output_stream),
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32, io, io).
|
||||
:- mode machine_prtc(in, array_di, array_uo, in, out,
|
||||
di, uo) is det.
|
||||
machine_prtc(OutF, Stack0, Stack, SP0, SP, !IO) :-
|
||||
Stack = Stack0,
|
||||
(I = uint32.cast_to_int(SP0)),
|
||||
X = lookup(Stack0, I - 1),
|
||||
C = (char.det_from_int(uint32.cast_to_int(X))),
|
||||
(io.write_char(OutF, C, !IO)),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_prti((io.text_output_stream),
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32, io, io).
|
||||
:- mode machine_prti(in, array_di, array_uo, in, out,
|
||||
di, uo) is det.
|
||||
machine_prti(OutF, Stack0, Stack, SP0, SP, !IO) :-
|
||||
Stack = Stack0,
|
||||
(I = uint32.cast_to_int(SP0)),
|
||||
(X = int32.cast_from_uint32(lookup(Stack0, I - 1))),
|
||||
(io.write_int32(OutF, X, !IO)),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- pred machine_prts((io.text_output_stream),
|
||||
array(string),
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32, io, io).
|
||||
:- mode machine_prts(in, in, array_di, array_uo, in, out,
|
||||
di, uo) is det.
|
||||
machine_prts(OutF, Strings, Stack0, Stack, SP0, SP, !IO) :-
|
||||
Stack = Stack0,
|
||||
(I = uint32.cast_to_int(SP0)),
|
||||
(K = uint32.cast_to_int(lookup(Stack0, I - 1))),
|
||||
S = lookup(Strings, K),
|
||||
(io.write_string(OutF, S, !IO)),
|
||||
SP = SP0 - 1_u32.
|
||||
|
||||
:- func get_immediate(array(uint8), uint32) = uint32.
|
||||
:- mode get_immediate(in, in) = out is det.
|
||||
:- pragma inline(get_immediate/2).
|
||||
get_immediate(Code, IP) = Immediate_Value :-
|
||||
% Big-endian order.
|
||||
I = cast_to_int(IP),
|
||||
B3 = lookup(Code, I),
|
||||
B2 = lookup(Code, I + 1),
|
||||
B1 = lookup(Code, I + 2),
|
||||
B0 = lookup(Code, I + 3),
|
||||
Immediate_Value = from_bytes_be(B3, B2, B1, B0).
|
||||
|
||||
:- pred machine_fetch(array(uint32), array(uint32),
|
||||
array(uint8), uint32, uint32,
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32).
|
||||
:- mode machine_fetch(array_di, array_uo, in, in, out,
|
||||
array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_fetch/9).
|
||||
machine_fetch(Data0, Data, Code, IP0, IP, !Stack, SP0, SP) :-
|
||||
Data = Data0,
|
||||
K = get_immediate(Code, IP0),
|
||||
IP = IP0 + 4_u32,
|
||||
X = lookup(Data0, cast_to_int(K)),
|
||||
set(cast_to_int(SP0), X, !Stack),
|
||||
SP = SP0 + 1_u32.
|
||||
|
||||
:- pred machine_store(array(uint32), array(uint32),
|
||||
array(uint8), uint32, uint32,
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32).
|
||||
:- mode machine_store(array_di, array_uo, in, in, out,
|
||||
array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_store/9).
|
||||
machine_store(!Data, Code, IP0, IP, Stack0, Stack, SP0, SP) :-
|
||||
Stack = Stack0,
|
||||
K = get_immediate(Code, IP0),
|
||||
IP = IP0 + 4_u32,
|
||||
SP = SP0 - 1_u32,
|
||||
X = lookup(Stack0, cast_to_int(SP)),
|
||||
set(cast_to_int(K), X, !Data).
|
||||
|
||||
:- pred machine_push(array(uint8), uint32, uint32,
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32).
|
||||
:- mode machine_push(in, in, out, array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_push/7).
|
||||
machine_push(Code, IP0, IP, !Stack, SP0, SP) :-
|
||||
X = get_immediate(Code, IP0),
|
||||
IP = IP0 + 4_u32,
|
||||
set(cast_to_int(SP0), X, !Stack),
|
||||
SP = SP0 + 1_u32.
|
||||
|
||||
:- pred machine_jmp(array(uint8), uint32, uint32).
|
||||
:- mode machine_jmp(in, in, out) is det.
|
||||
:- pragma inline(machine_jmp/3).
|
||||
machine_jmp(Code, IP0, IP) :-
|
||||
Offset = get_immediate(Code, IP0),
|
||||
IP = unsigned_add(IP0, Offset).
|
||||
|
||||
:- pred machine_jz(array(uint8), uint32, uint32,
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32).
|
||||
:- mode machine_jz(in, in, out, array_di, array_uo, in, out) is det.
|
||||
:- pragma inline(machine_jz/7).
|
||||
machine_jz(Code, IP0, IP, Stack0, Stack, SP0, SP) :-
|
||||
Stack = Stack0,
|
||||
SP = SP0 - 1_u32,
|
||||
X = lookup(Stack0, cast_to_int(SP)),
|
||||
(if (X = 0_u32)
|
||||
then (Offset = get_immediate(Code, IP0),
|
||||
IP = unsigned_add(IP0, Offset))
|
||||
else (IP = IP0 + 4_u32)).
|
||||
|
||||
:- pred run_one_instruction((io.text_output_stream),
|
||||
array(string),
|
||||
array(uint32), array(uint32),
|
||||
array(uint8), uint32, uint32,
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32, bool, io, io).
|
||||
:- mode run_one_instruction(in, in, array_di, array_uo,
|
||||
in, in, out, array_di, array_uo,
|
||||
in, out, out, di, uo) is det.
|
||||
run_one_instruction(OutF, Strings, !Data,
|
||||
Code, IP0, IP, !Stack, !SP,
|
||||
Halt, !IO) :-
|
||||
%
|
||||
% In the following implementation, any unrecognized instruction
|
||||
% causes a HALT, just as an actual "halt" opcode would.
|
||||
%
|
||||
Opcode = lookup(Code, cast_to_int(IP0)),
|
||||
IP1 = IP0 + 1_u32,
|
||||
I = (Opcode >> 2),
|
||||
J = (Opcode /\ 0x03_u8),
|
||||
(if (I = 0_u8)
|
||||
then (IP = IP1,
|
||||
(if (J = 0_u8)
|
||||
then (Halt = yes)
|
||||
else if (J = 1_u8)
|
||||
then (machine_add(!Stack, !SP),
|
||||
Halt = no)
|
||||
else if (J = 2_u8)
|
||||
then (machine_sub(!Stack, !SP),
|
||||
Halt = no)
|
||||
else (machine_mul(!Stack, !SP),
|
||||
Halt = no)))
|
||||
else if (I = 1_u8)
|
||||
then (Halt = no,
|
||||
IP = IP1,
|
||||
(if (J = 0_u8)
|
||||
then machine_div(!Stack, !SP)
|
||||
else if (J = 1_u8)
|
||||
then machine_mod(!Stack, !SP)
|
||||
else if (J = 2_u8)
|
||||
then machine_lt(!Stack, !SP)
|
||||
else machine_gt(!Stack, !SP)))
|
||||
else if (I = 2_u8)
|
||||
then (Halt = no,
|
||||
IP = IP1,
|
||||
(if (J = 0_u8)
|
||||
then machine_le(!Stack, !SP)
|
||||
else if (J = 1_u8)
|
||||
then machine_ge(!Stack, !SP)
|
||||
else if (J = 2_u8)
|
||||
then machine_eq(!Stack, !SP)
|
||||
else machine_ne(!Stack, !SP)))
|
||||
else if (I = 3_u8)
|
||||
then (Halt = no,
|
||||
IP = IP1,
|
||||
(if (J = 0_u8)
|
||||
then machine_and(!Stack, !SP)
|
||||
else if (J = 1_u8)
|
||||
then machine_or(!Stack, !SP)
|
||||
else if (J = 2_u8)
|
||||
then machine_neg(!Stack, !SP)
|
||||
else machine_not(!Stack, !SP)))
|
||||
else if (I = 4_u8)
|
||||
then (Halt = no,
|
||||
(if (J = 0_u8)
|
||||
then (machine_prtc(OutF, !Stack, !SP, !IO),
|
||||
IP = IP1)
|
||||
else if (J = 1_u8)
|
||||
then (machine_prti(OutF, !Stack, !SP, !IO),
|
||||
IP = IP1)
|
||||
else if (J = 2_u8)
|
||||
then (machine_prts(OutF, Strings, !Stack, !SP, !IO),
|
||||
IP = IP1)
|
||||
else machine_fetch(!Data, Code, IP1, IP, !Stack, !SP)))
|
||||
else if (I = 5_u8)
|
||||
then (Halt = no,
|
||||
(if (J = 0_u8)
|
||||
then machine_store(!Data, Code, IP1, IP, !Stack, !SP)
|
||||
else if (J = 1_u8)
|
||||
then machine_push(Code, IP1, IP, !Stack, !SP)
|
||||
else if (J = 2_u8)
|
||||
then machine_jmp(Code, IP1, IP)
|
||||
else machine_jz(Code, IP1, IP, !Stack, !SP)))
|
||||
else (Halt = yes, IP = IP1)).
|
||||
|
||||
:- pred run_program((io.text_output_stream), array(string),
|
||||
array(uint32), array(uint32),
|
||||
array(uint8), uint32, uint32,
|
||||
array(uint32), array(uint32),
|
||||
uint32, uint32, io, io).
|
||||
:- mode run_program(in, in, array_di, array_uo,
|
||||
in, in, out, array_di, array_uo,
|
||||
in, out, di, uo) is det.
|
||||
run_program(OutF, Strings, !Data, Code, !IP, !Stack, !SP, !IO) :-
|
||||
run_one_instruction(OutF, Strings, !Data, Code, !IP, !Stack, !SP,
|
||||
Halt, !IO),
|
||||
(if (Halt = yes)
|
||||
then true
|
||||
else run_program(OutF, Strings, !Data, Code, !IP, !Stack,
|
||||
!SP, !IO)).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
:- pred open_InpF(text_input_stream, string, io, io).
|
||||
:- mode open_InpF(out, in, di, uo) is det.
|
||||
open_InpF(InpF, InpF_filename, !IO) :-
|
||||
if (InpF_filename = "-")
|
||||
then (InpF = io.stdin_stream)
|
||||
else (open_input(InpF_filename, InpF_result, !IO),
|
||||
(if (InpF_result = ok(F))
|
||||
then (InpF = F)
|
||||
else throw("Error: cannot open " ++ InpF_filename ++
|
||||
" for input"))).
|
||||
|
||||
:- pred open_OutF(text_output_stream, string, io, io).
|
||||
:- mode open_OutF(out, in, di, uo) is det.
|
||||
open_OutF(OutF, OutF_filename, !IO) :-
|
||||
if (OutF_filename = "-")
|
||||
then (OutF = io.stdout_stream)
|
||||
else (open_output(OutF_filename, OutF_result, !IO),
|
||||
(if (OutF_result = ok(F))
|
||||
then (OutF = F)
|
||||
else throw("Error: cannot open " ++ OutF_filename ++
|
||||
" for output"))).
|
||||
|
||||
:- pred main_program(string, string, io, io).
|
||||
:- mode main_program(in, in, di, uo) is det.
|
||||
main_program(InpF_filename, OutF_filename, !IO) :-
|
||||
open_InpF(InpF, InpF_filename, !IO),
|
||||
open_OutF(OutF, OutF_filename, !IO),
|
||||
parse_assembly(InpF, Datasize, _Strings_Count, Strings,
|
||||
Instructions, !IO),
|
||||
(if (InpF_filename = "-")
|
||||
then true
|
||||
else close_input(InpF, !IO)),
|
||||
executable_memory(Instructions, Code),
|
||||
init(cast_to_int(Datasize), 0_u32, Data0),
|
||||
init(2048, 0_u32, Stack0), % Stack is 2048 words.
|
||||
IP0 = 0_u32,
|
||||
SP0 = 0_u32,
|
||||
run_program(OutF, Strings, Data0, _Data, Code, IP0, _IP,
|
||||
Stack0, _Stack, SP0, _SP, !IO),
|
||||
(if (OutF_filename = "-")
|
||||
then true
|
||||
else close_output(OutF, !IO)).
|
||||
|
||||
:- pred usage_error(io, io).
|
||||
:- mode usage_error(di, uo) is det.
|
||||
usage_error(!IO) :-
|
||||
progname("lex", ProgName, !IO),
|
||||
(io.format("Usage: %s [INPUT_FILE [OUTPUT_FILE]]\n",
|
||||
[s(ProgName)], !IO)),
|
||||
(io.write_string(
|
||||
"If INPUT_FILE is \"-\" or not present then standard input is used.\n",
|
||||
!IO)),
|
||||
(io.write_string(
|
||||
"If OUTPUT_FILE is \"-\" or not present then standard output is used.\n",
|
||||
!IO)),
|
||||
set_exit_status(1, !IO).
|
||||
|
||||
main(!IO) :-
|
||||
command_line_arguments(Args, !IO),
|
||||
(if (Args = [])
|
||||
then (InpF_filename = "-",
|
||||
OutF_filename = "-",
|
||||
main_program(InpF_filename, OutF_filename, !IO))
|
||||
else if (Args = [F1])
|
||||
then (InpF_filename = F1,
|
||||
OutF_filename = "-",
|
||||
main_program(InpF_filename, OutF_filename, !IO))
|
||||
else if (Args = [F1, F2])
|
||||
then (InpF_filename = F1,
|
||||
OutF_filename = F2,
|
||||
main_program(InpF_filename, OutF_filename, !IO))
|
||||
else usage_error(!IO)).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Instructions for GNU Emacs--
|
||||
%%% local variables:
|
||||
%%% mode: mercury
|
||||
%%% prolog-indent-width: 2
|
||||
%%% end:
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
import os, parseutils, strutils, strscans, strformat
|
||||
|
||||
type
|
||||
|
||||
Value = int32
|
||||
BytesValue = array[4, byte]
|
||||
Address = int32
|
||||
|
||||
OpCode = enum
|
||||
opFetch = "fetch"
|
||||
opStore = "store"
|
||||
opPush = "push"
|
||||
opJmp = "jmp"
|
||||
opJz = "jz"
|
||||
opAdd = "add"
|
||||
opSub = "sub"
|
||||
opMul = "mul"
|
||||
opDiv = "div"
|
||||
opMod = "mod"
|
||||
opLt = "lt"
|
||||
opgt = "gt"
|
||||
opLe = "le"
|
||||
opGe = "ge"
|
||||
opEq = "eq"
|
||||
opNe = "ne"
|
||||
opAnd = "and"
|
||||
opOr = "or"
|
||||
opNeg = "neg"
|
||||
opNot = "not"
|
||||
opPrtc = "prtc"
|
||||
opPrti = "prti"
|
||||
opPrts = "prts"
|
||||
opHalt = "halt"
|
||||
opInvalid = "invalid"
|
||||
|
||||
# Virtual machine description.
|
||||
VM = object
|
||||
stack: seq[Value] # Evaluation stack.
|
||||
memory: seq[byte] # Memory to store program.
|
||||
data: seq[Value] # Data storage.
|
||||
strings: seq[string] # String storage.
|
||||
pc: Address # Program counter.
|
||||
|
||||
# Exceptions.
|
||||
LoadingError = object of CatchableError
|
||||
RuntimeError = object of CatchableError
|
||||
|
||||
|
||||
####################################################################################################
|
||||
# Running program.
|
||||
|
||||
proc checkStackLength(vm: VM; minLength: int) {.inline.} =
|
||||
## Check that evaluation stack contains at least "minLength" elements.
|
||||
if vm.stack.len < minLength:
|
||||
raise newException(RuntimeError, &"not enough operands on the stack (pc = {vm.pc}).")
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc getOperand(vm: var VM): Value =
|
||||
## Get a 32 bits operand.
|
||||
|
||||
type Union {.union.} = object
|
||||
value: Value
|
||||
bytes: BytesValue
|
||||
|
||||
if vm.pc + 4 >= vm.memory.len:
|
||||
raise newException(RuntimeError, &"out of memory (pc = {vm.pc}).")
|
||||
|
||||
var aux: Union
|
||||
let address = vm.pc + 1
|
||||
for idx in 0..3:
|
||||
aux.bytes[idx] = vm.memory[address + idx]
|
||||
result = aux.value
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc run(vm: var VM) =
|
||||
## Run a program loaded in VM memory.
|
||||
|
||||
vm.pc = 0
|
||||
|
||||
while true:
|
||||
|
||||
if vm.pc notin 0..vm.memory.high:
|
||||
raise newException(RuntimeError, &"out of memory (pc = {vm.pc}).")
|
||||
|
||||
let opcode = OpCode(vm.memory[vm.pc])
|
||||
case opcode
|
||||
|
||||
of opFetch, opStore:
|
||||
let index = vm.getOperand()
|
||||
if index notin 0..vm.data.high:
|
||||
raise newException(RuntimeError, &"wrong memory index (pc = {vm.pc}).")
|
||||
if opcode == opFetch:
|
||||
vm.stack.add(vm.data[index])
|
||||
else:
|
||||
vm.checkStackLength(1)
|
||||
vm.data[index] = vm.stack.pop()
|
||||
inc vm.pc, 4
|
||||
|
||||
of opPush:
|
||||
let value = vm.getOperand()
|
||||
vm.stack.add(value)
|
||||
inc vm.pc, 4
|
||||
|
||||
of opJmp:
|
||||
let offset = vm.getOperand()
|
||||
inc vm.pc, offset
|
||||
|
||||
of opJz:
|
||||
let offset = vm.getOperand()
|
||||
vm.checkStackLength(1)
|
||||
let value = vm.stack.pop()
|
||||
inc vm.pc, if value == 0: offset else: 4
|
||||
|
||||
of opAdd..opOr:
|
||||
# Two operands instructions.
|
||||
vm.checkStackLength(2)
|
||||
let op2 = vm.stack.pop()
|
||||
let op1 = vm.stack.pop()
|
||||
case range[opAdd..opOr](opcode)
|
||||
of opAdd:
|
||||
vm.stack.add(op1 + op2)
|
||||
of opSub:
|
||||
vm.stack.add(op1 - op2)
|
||||
of opMul:
|
||||
vm.stack.add(op1 * op2)
|
||||
of opDiv:
|
||||
vm.stack.add(op1 div op2)
|
||||
of opMod:
|
||||
vm.stack.add(op1 mod op2)
|
||||
of opLt:
|
||||
vm.stack.add(Value(op1 < op2))
|
||||
of opgt:
|
||||
vm.stack.add(Value(op1 > op2))
|
||||
of opLe:
|
||||
vm.stack.add(Value(op1 <= op2))
|
||||
of opGe:
|
||||
vm.stack.add(Value(op1 >= op2))
|
||||
of opEq:
|
||||
vm.stack.add(Value(op1 == op2))
|
||||
of opNe:
|
||||
vm.stack.add(Value(op1 != op2))
|
||||
of opAnd:
|
||||
vm.stack.add(op1 and op2)
|
||||
of opOr:
|
||||
vm.stack.add(op1 or op2)
|
||||
|
||||
of opNeg..opPrts:
|
||||
# One operand instructions.
|
||||
vm.checkStackLength(1)
|
||||
let op = vm.stack.pop()
|
||||
case range[opNeg..opPrts](opcode)
|
||||
of opNeg:
|
||||
vm.stack.add(-op)
|
||||
of opNot:
|
||||
vm.stack.add(not op)
|
||||
of opPrtc:
|
||||
stdout.write(chr(op))
|
||||
of opPrti:
|
||||
stdout.write(op)
|
||||
of opPrts:
|
||||
if op notin 0..vm.strings.high:
|
||||
raise newException(RuntimeError, &"wrong string index (pc = {vm.pc}).")
|
||||
stdout.write(vm.strings[op])
|
||||
|
||||
of opHalt:
|
||||
break
|
||||
|
||||
of opInvalid:
|
||||
discard # Not possible.
|
||||
|
||||
inc vm.pc
|
||||
|
||||
|
||||
####################################################################################################
|
||||
# Loading assembly file.
|
||||
|
||||
proc parseHeader(line: string): tuple[dataSize, stringCount: int] =
|
||||
## Parse the header.
|
||||
|
||||
if not line.scanf("Datasize: $s$i $sStrings: $i", result.dataSize, result.stringCount):
|
||||
raise newException(LoadingError, "Wrong header in code.")
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
|
||||
proc parseString(line: string; linenum: int): string =
|
||||
## Parse a string.
|
||||
|
||||
if not line.startsWith('"'):
|
||||
raise newException(LoadingError, "Line $1: incorrect string.".format(linenum))
|
||||
# Can't use "unescape" as it is confused by "\\n" and "\n".
|
||||
result = line.replacef(re"([^\\])(\\n)", "$1\n").replace(r"\\", r"\").replace("\"", "")
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc parseValue(line: string; linenum: int; pos: var int; msg: string): int32 =
|
||||
## Parse an int32 value.
|
||||
|
||||
var value: int
|
||||
|
||||
pos += line.skipWhitespace(pos)
|
||||
let parsed = line.parseInt(value, pos)
|
||||
if parsed == 0:
|
||||
raise newException(LoadingError, "Line $1: ".format(linenum) & msg)
|
||||
pos += parsed
|
||||
result = int32(value)
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc parseOpcode(line: string; linenum: int; pos: var int): OpCode =
|
||||
## Parse an opcode.
|
||||
|
||||
var opstring: string
|
||||
|
||||
pos += line.skipWhitespace(pos)
|
||||
let parsed = line.parseIdent(opstring, pos)
|
||||
if parsed == 0:
|
||||
raise newException(LoadingError, "Line $1: opcode expected".format(linenum))
|
||||
pos += parsed
|
||||
|
||||
result = parseEnum[OpCode](opstring, opInvalid)
|
||||
if result == opInvalid:
|
||||
raise newException(LoadingError, "Line $1: invalid opcode encountered".format(linenum))
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc parseMemoryIndex(line: string; linenum: int; pos: var int): int32 =
|
||||
## Parse a memory index (int32 value between brackets).
|
||||
|
||||
var memIndex: int
|
||||
|
||||
pos += line.skipWhitespace(pos)
|
||||
let str = line.captureBetween('[', ']', pos)
|
||||
if str.parseInt(memIndex) == 0 or memIndex < 0:
|
||||
raise newException(LoadingError, "Line $1: invalid memory index".format(lineNum))
|
||||
pos += str.len + 2
|
||||
result = int32(memIndex)
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc parseOffset(line: string; linenum: int; pos: var int): int32 =
|
||||
## Parse an offset (int32 value between parentheses).
|
||||
|
||||
var offset: int
|
||||
|
||||
pos += line.skipWhitespace(pos)
|
||||
let str = line.captureBetween('(', ')', pos)
|
||||
if str.parseInt(offset) == 0:
|
||||
raise newException(LoadingError, "Line $1: invalid offset".format(linenum))
|
||||
pos += str.len + 2
|
||||
result = int32(offset)
|
||||
|
||||
#---------------------------------------------------------------------------------------------------
|
||||
|
||||
proc load(vm: var VM; code: string) =
|
||||
## Load an assembly code into VM memory.
|
||||
|
||||
# Analyze header.
|
||||
let lines = code.splitlines()
|
||||
let (dataSize, stringCount) = parseHeader(lines[0])
|
||||
vm.data.setLen(dataSize)
|
||||
vm.strings.setLen(stringCount)
|
||||
|
||||
# Load strings.
|
||||
for idx in 1..stringCount:
|
||||
vm.strings[idx - 1] = lines[idx].parseString(idx + 1)
|
||||
|
||||
# Load code.
|
||||
var pc: Address = 0
|
||||
for idx in (stringCount + 1)..lines.high:
|
||||
var pos = 0
|
||||
let line = lines[idx]
|
||||
if line.len == 0: continue
|
||||
|
||||
# Process address.
|
||||
let address = line.parseValue(idx + 1, pos, "address expected")
|
||||
if address != pc:
|
||||
raise newException(LoadingError, "Line $1: wrong address".format(idx + 1))
|
||||
|
||||
# Process opcode.
|
||||
let opcode = line.parseOpcode(idx + 1, pos)
|
||||
vm.memory.add(byte(opcode))
|
||||
|
||||
# Process operand.
|
||||
case opcode
|
||||
|
||||
of opFetch, opStore:
|
||||
# Find memory index.
|
||||
let memIndex = line.parseMemoryIndex(idx + 1, pos)
|
||||
vm.memory.add(cast[BytesValue](Value(memIndex)))
|
||||
inc pc, 5
|
||||
|
||||
of opJmp, opJz:
|
||||
# Find offset.
|
||||
let offset = line.parseOffset(idx + 1, pos)
|
||||
vm.memory.add(cast[BytesValue](Value(offset)))
|
||||
# Find and check branch address.
|
||||
let branchAddress = line.parseValue(idx + 1, pos, "branch address expected")
|
||||
if branchAddress != pc + offset + 1:
|
||||
raise newException(LoadingError, "Line $1: wrong branch address".format(idx + 1))
|
||||
inc pc, 5
|
||||
|
||||
of opPush:
|
||||
# Find value.
|
||||
let value = line.parseValue(idx + 1, pos, "value expected")
|
||||
vm.memory.add(cast[BytesValue](Value(value)))
|
||||
inc pc, 5
|
||||
|
||||
else:
|
||||
inc pc
|
||||
|
||||
#———————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
let code = if paramCount() == 0: stdin.readAll() else: paramStr(1).readFile()
|
||||
var vm: VM
|
||||
|
||||
vm.load(code)
|
||||
vm.run()
|
||||
|
|
@ -0,0 +1,408 @@
|
|||
# -*- ObjectIcon -*-
|
||||
#
|
||||
# The Rosetta Code virtual machine in Object Icon.
|
||||
#
|
||||
# See https://rosettacode.org/wiki/Compiler/virtual_machine_interpreter
|
||||
#
|
||||
|
||||
import io
|
||||
|
||||
procedure main(args)
|
||||
local f_inp, f_out
|
||||
local vm
|
||||
|
||||
if 3 <= *args then {
|
||||
write("Usage: ", &progname, " [INPUT_FILE [OUTPUT_FILE]]")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if 1 <= *args then {
|
||||
f_inp := FileStream(args[1], FileOpt.RDONLY) | stop (&why)
|
||||
} else {
|
||||
f_inp := FileStream.stdin
|
||||
}
|
||||
f_inp := BufferStream(f_inp)
|
||||
|
||||
if 2 <= *args then {
|
||||
f_out := FileStream(args[2], ior (FileOpt.WRONLY,
|
||||
FileOpt.TRUNC,
|
||||
FileOpt.CREAT)) | stop (&why)
|
||||
} else {
|
||||
f_out := FileStream.stdout
|
||||
}
|
||||
|
||||
vm := VirtualMachine()
|
||||
vm.read_assembly_code(f_inp)
|
||||
vm.run(f_out)
|
||||
end
|
||||
|
||||
procedure int2bytes (n)
|
||||
local bytes
|
||||
|
||||
# The VM is little-endian.
|
||||
|
||||
bytes := "****"
|
||||
bytes[1] := char (iand(n, 16rFF))
|
||||
bytes[2] := char(iand(ishift(n, -8), 16rFF))
|
||||
bytes[3] := char(iand(ishift(n, -16), 16rFF))
|
||||
bytes[4] := char(iand(ishift(n, -24), 16rFF))
|
||||
return bytes
|
||||
end
|
||||
|
||||
procedure bytes2int(bytes, i)
|
||||
local n0, n1, n2, n3, n
|
||||
|
||||
# The VM is little-endian.
|
||||
|
||||
n0 := ord(bytes[i])
|
||||
n1 := ishift(ord(bytes[i + 1]), 8)
|
||||
n2 := ishift(ord(bytes[i + 2]), 16)
|
||||
n3 := ishift(ord(bytes[i + 3]), 24)
|
||||
n := ior (n0, ior (n1, ior (n2, n3)))
|
||||
|
||||
# Do not forget to extend the sign bit.
|
||||
return (if n3 <= 16r7F then n else ior(n, icom(16rFFFFFFFF)))
|
||||
end
|
||||
|
||||
class OpcodeCollection()
|
||||
|
||||
public static const opcode_names
|
||||
public static const opcode_values
|
||||
|
||||
public static const op_halt
|
||||
public static const op_add
|
||||
public static const op_sub
|
||||
public static const op_mul
|
||||
public static const op_div
|
||||
public static const op_mod
|
||||
public static const op_lt
|
||||
public static const op_gt
|
||||
public static const op_le
|
||||
public static const op_ge
|
||||
public static const op_eq
|
||||
public static const op_ne
|
||||
public static const op_and
|
||||
public static const op_or
|
||||
public static const op_neg
|
||||
public static const op_not
|
||||
public static const op_prtc
|
||||
public static const op_prti
|
||||
public static const op_prts
|
||||
public static const op_fetch
|
||||
public static const op_store
|
||||
public static const op_push
|
||||
public static const op_jmp
|
||||
public static const op_jz
|
||||
|
||||
private static init()
|
||||
local i
|
||||
|
||||
opcode_names :=
|
||||
["halt", "add", "sub", "mul", "div",
|
||||
"mod", "lt", "gt", "le", "ge",
|
||||
"eq", "ne", "and", "or", "neg",
|
||||
"not", "prtc", "prti", "prts", "fetch",
|
||||
"store", "push", "jmp", "jz"]
|
||||
|
||||
opcode_values := table()
|
||||
every i := 1 to *opcode_names do
|
||||
opcode_values[opcode_names[i]] := char(i)
|
||||
|
||||
op_halt := opcode_values["halt"]
|
||||
op_add := opcode_values["add"]
|
||||
op_sub := opcode_values["sub"]
|
||||
op_mul := opcode_values["mul"]
|
||||
op_div := opcode_values["div"]
|
||||
op_mod := opcode_values["mod"]
|
||||
op_lt := opcode_values["lt"]
|
||||
op_gt := opcode_values["gt"]
|
||||
op_le := opcode_values["le"]
|
||||
op_ge := opcode_values["ge"]
|
||||
op_eq := opcode_values["eq"]
|
||||
op_ne := opcode_values["ne"]
|
||||
op_and := opcode_values["and"]
|
||||
op_or := opcode_values["or"]
|
||||
op_neg := opcode_values["neg"]
|
||||
op_not := opcode_values["not"]
|
||||
op_prtc := opcode_values["prtc"]
|
||||
op_prti := opcode_values["prti"]
|
||||
op_prts := opcode_values["prts"]
|
||||
op_fetch := opcode_values["fetch"]
|
||||
op_store := opcode_values["store"]
|
||||
op_push := opcode_values["push"]
|
||||
op_jmp := opcode_values["jmp"]
|
||||
op_jz := opcode_values["jz"]
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class VirtualMachine(OpcodeCollection)
|
||||
|
||||
public code
|
||||
public global_data
|
||||
public strings
|
||||
public stack
|
||||
public pc
|
||||
|
||||
private static const whitespace_chars
|
||||
|
||||
private static init()
|
||||
whitespace_chars := ' \t\n\r\f\v'
|
||||
return
|
||||
end
|
||||
|
||||
public read_assembly_code(f)
|
||||
local data_size, number_of_strings
|
||||
local line, ch
|
||||
local i
|
||||
local address
|
||||
local opcode
|
||||
|
||||
# Read the header line.
|
||||
line := f.read() | bad_vm()
|
||||
line ? {
|
||||
tab(many(whitespace_chars))
|
||||
tab(match("Datasize")) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(':')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
data_size :=
|
||||
integer(tab(many(&digits))) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(match("Strings")) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(':')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
number_of_strings :=
|
||||
integer(tab(many(&digits))) | bad_vm()
|
||||
}
|
||||
|
||||
# Read the strings.
|
||||
strings := list(number_of_strings)
|
||||
every i := 1 to number_of_strings do {
|
||||
strings[i] := ""
|
||||
line := f.read() | bad_vm()
|
||||
line ? {
|
||||
tab(many(whitespace_chars))
|
||||
tab(any('"')) | bad_vm()
|
||||
while ch := tab(any(~'"')) do {
|
||||
if ch == '\\' then {
|
||||
ch := tab(any('n\\')) | bad_vm()
|
||||
strings[i] ||:=
|
||||
(if (ch == "n") then "\n" else "\\")
|
||||
} else {
|
||||
strings[i] ||:= ch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Read the code.
|
||||
code := ""
|
||||
while line := f.read() do {
|
||||
line ? {
|
||||
tab(many(whitespace_chars))
|
||||
address := integer(tab(many(&digits))) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
opcode := tab(many(~whitespace_chars)) | bad_vm()
|
||||
code ||:= opcode_values[opcode]
|
||||
case opcode of {
|
||||
"push": {
|
||||
tab(many(whitespace_chars))
|
||||
code ||:=
|
||||
int2bytes(integer(tab(many(&digits)))) |
|
||||
int2bytes(integer(tab(any('-')) ||
|
||||
tab(many(&digits)))) |
|
||||
bad_vm()
|
||||
}
|
||||
"fetch" | "store": {
|
||||
tab(many(whitespace_chars))
|
||||
tab(any('[')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
code ||:=
|
||||
int2bytes(integer(tab(many(&digits)))) |
|
||||
bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(']')) | bad_vm()
|
||||
}
|
||||
"jmp" | "jz": {
|
||||
tab(many(whitespace_chars))
|
||||
tab(any('(')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
code ||:=
|
||||
int2bytes(integer(tab(many(&digits)))) |
|
||||
int2bytes(integer(tab(any('-')) ||
|
||||
tab(many(&digits)))) |
|
||||
bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(any(')')) | bad_vm()
|
||||
tab(many(whitespace_chars))
|
||||
tab(many(&digits)) | bad_vm()
|
||||
}
|
||||
default: {
|
||||
# Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a global data area.
|
||||
global_data := list(data_size, &null)
|
||||
|
||||
initialize()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
public run(f_out)
|
||||
initialize()
|
||||
continue(f_out)
|
||||
return
|
||||
end
|
||||
|
||||
public continue(f_out)
|
||||
while code[pc] ~== op_halt do
|
||||
step(f_out)
|
||||
end
|
||||
|
||||
public step(f_out)
|
||||
local opcode
|
||||
|
||||
opcode := code[pc]
|
||||
pc +:= 1
|
||||
case opcode of {
|
||||
op_add: binop("+")
|
||||
op_sub: binop("-")
|
||||
op_mul: binop("*")
|
||||
op_div: binop("/")
|
||||
op_mod: binop("%")
|
||||
op_lt: comparison("<")
|
||||
op_gt: comparison(">")
|
||||
op_le: comparison("<=")
|
||||
op_ge: comparison(">=")
|
||||
op_eq: comparison("=")
|
||||
op_ne: comparison("~=")
|
||||
op_and: logical_and()
|
||||
op_or: logical_or()
|
||||
op_neg: negate()
|
||||
op_not: logical_not()
|
||||
op_prtc: printc(f_out)
|
||||
op_prti: printi(f_out)
|
||||
op_prts: prints(f_out)
|
||||
op_fetch: fetch_global()
|
||||
op_store: store_global()
|
||||
op_push: push_argument()
|
||||
op_jmp: jump()
|
||||
op_jz: jump_if_zero()
|
||||
default: bad_opcode()
|
||||
}
|
||||
end
|
||||
|
||||
private negate()
|
||||
stack[1] := -stack[1]
|
||||
return
|
||||
end
|
||||
|
||||
private binop(func)
|
||||
stack[2] := func(stack[2], stack[1])
|
||||
pop(stack)
|
||||
return
|
||||
end
|
||||
|
||||
private comparison(func)
|
||||
stack[2] := (if func(stack[2], stack[1]) then 1 else 0)
|
||||
pop(stack)
|
||||
return
|
||||
end
|
||||
|
||||
private logical_and()
|
||||
stack[2] := (if stack[2] ~= 0 & stack[1] ~= 0 then 1 else 0)
|
||||
pop(stack)
|
||||
return
|
||||
end
|
||||
|
||||
private logical_or()
|
||||
stack[2] := (if stack[2] ~= 0 | stack[1] ~= 0 then 1 else 0)
|
||||
pop(stack)
|
||||
return
|
||||
end
|
||||
|
||||
private logical_not()
|
||||
stack[1] := (if stack[1] ~= 0 then 0 else 1)
|
||||
return
|
||||
end
|
||||
|
||||
private printc(f_out)
|
||||
/f_out := FileStream.stdout
|
||||
f_out.writes(char(pop(stack)))
|
||||
return
|
||||
end
|
||||
|
||||
private printi(f_out)
|
||||
/f_out := FileStream.stdout
|
||||
f_out.writes(pop(stack))
|
||||
return
|
||||
end
|
||||
|
||||
private prints(f_out)
|
||||
/f_out := FileStream.stdout
|
||||
f_out.writes(strings[pop(stack) + 1])
|
||||
return
|
||||
end
|
||||
|
||||
private fetch_global()
|
||||
push(stack, global_data[get_argument() + 1])
|
||||
pc +:= 4
|
||||
return
|
||||
end
|
||||
|
||||
private store_global()
|
||||
global_data[get_argument() + 1] := pop(stack)
|
||||
pc +:= 4
|
||||
return
|
||||
end
|
||||
|
||||
private push_argument()
|
||||
push(stack, get_argument())
|
||||
pc +:= 4
|
||||
return
|
||||
end
|
||||
|
||||
private jump()
|
||||
pc +:= get_argument()
|
||||
return
|
||||
end
|
||||
|
||||
private jump_if_zero()
|
||||
if pop(stack) = 0 then
|
||||
pc +:= get_argument()
|
||||
else
|
||||
pc +:= 4
|
||||
return
|
||||
end
|
||||
|
||||
private get_argument()
|
||||
return bytes2int(code, pc)
|
||||
end
|
||||
|
||||
public initialize()
|
||||
# The program counter starts at 1, for convenient indexing into
|
||||
# the code[] array. Icon indexing starts at 1 (for a *very* good
|
||||
# reason, but that’s a topic for another day).
|
||||
pc := 1
|
||||
stack := []
|
||||
return
|
||||
end
|
||||
|
||||
private bad_vm()
|
||||
write(FileStream.stderr, "Bad VM.")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
private bad_opcode()
|
||||
write(FileStream.stderr, "Bad opcode.")
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# http://www.rosettacode.org/wiki/Compiler/virtual_machine_interpreter
|
||||
use strict; # vm.pl - run rosetta code
|
||||
use warnings;
|
||||
use integer;
|
||||
|
||||
my ($binary, $pc, @stack, @data) = ('', 0);
|
||||
|
||||
<> =~ /Strings: (\d+)/ or die "bad header";
|
||||
my @strings = map <> =~ tr/\n""//dr =~ s/\\(.)/$1 eq 'n' ? "\n" : $1/ger, 1..$1;
|
||||
|
||||
sub value { unpack 'l', substr $binary, ($pc += 4) - 4, 4 }
|
||||
|
||||
my @ops = (
|
||||
[ halt => sub { exit } ],
|
||||
[ add => sub { $stack[-2] += pop @stack } ],
|
||||
[ sub => sub { $stack[-2] -= pop @stack } ],
|
||||
[ mul => sub { $stack[-2] *= pop @stack } ],
|
||||
[ div => sub { $stack[-2] /= pop @stack } ],
|
||||
[ mod => sub { $stack[-2] %= pop @stack } ],
|
||||
[ not => sub { $stack[-1] = $stack[-1] ? 0 : 1 } ],
|
||||
[ neg => sub { $stack[-1] = - $stack[-1] } ],
|
||||
[ and => sub { $stack[-2] &&= $stack[-1]; pop @stack } ],
|
||||
[ or => sub { $stack[-2] ||= $stack[-1]; pop @stack } ],
|
||||
[ lt => sub { $stack[-1] = $stack[-2] < pop @stack ? 1 : 0 } ],
|
||||
[ gt => sub { $stack[-1] = $stack[-2] > pop @stack ? 1 : 0 } ],
|
||||
[ le => sub { $stack[-1] = $stack[-2] <= pop @stack ? 1 : 0 } ],
|
||||
[ ge => sub { $stack[-1] = $stack[-2] >= pop @stack ? 1 : 0 } ],
|
||||
[ ne => sub { $stack[-1] = $stack[-2] != pop @stack ? 1 : 0 } ],
|
||||
[ eq => sub { $stack[-1] = $stack[-2] == pop @stack ? 1 : 0 } ],
|
||||
[ prts => sub { print $strings[pop @stack] } ],
|
||||
[ prti => sub { print pop @stack } ],
|
||||
[ prtc => sub { print chr pop @stack } ],
|
||||
[ store => sub { $data[value()] = pop @stack } ],
|
||||
[ fetch => sub { push @stack, $data[value()] // 0 } ],
|
||||
[ push => sub { push @stack, value() } ],
|
||||
[ jmp => sub { $pc += value() - 4 } ],
|
||||
[ jz => sub { $pc += pop @stack ? 4 : value() - 4 } ],
|
||||
);
|
||||
my %op2n = map { $ops[$_][0], $_ } 0..$#ops; # map name to op number
|
||||
|
||||
while(<>)
|
||||
{
|
||||
/^ *\d+ +(\w+)/ or die "bad line $_"; # format error
|
||||
$binary .= chr( $op2n{$1} // die "$1 not defined" ) . # op code
|
||||
(/\((-?\d+)\)|(\d+)]?$/ and pack 'l', $+); # 4 byte value
|
||||
}
|
||||
|
||||
$ops[vec($binary, $pc++, 8)][1]->() while 1; # run it
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
(notonline)-->
|
||||
<span style="color: #000080;font-style:italic;">--
|
||||
-- demo\rosetta\Compiler\vm.exw
|
||||
-- ============================
|
||||
--
|
||||
-- Since we have generated executable machine code, the virtual machine, such as it is, is just
|
||||
-- the higher level implementations of printc/i/s, see setbuiltins() in cgen.e
|
||||
-- Otherwise the only difference between this and cgen.exw is call(code_mem) instead of decode().
|
||||
--
|
||||
-- A quick test (calculating fib(44) 10^6 times) suggests ~500 times faster than interp.exw -
|
||||
-- which is to be expected given that a single add instruction (1 clock) here is implemented as
|
||||
-- at least three (and quite possibly five!) resursive calls to interp() in the other.</span>
|
||||
|
||||
<span style="color: #000000;">format</span> <span style="color: #000000;">PE32</span>
|
||||
<span style="color: #000080;font-style:italic;">--format ELF32
|
||||
-- Note: cgen generates 32-bit machine code, which cannot be executed directly from a 64-bit interpreter.
|
||||
-- You can however, via the magic of either the above format directives, use a 64-bit version of
|
||||
-- Phix to compile this (just add a -c command line option) to a 32-bit executable, which can.
|
||||
-- It would not be particularly difficult to emit 32 or 64 bit code, but some source code files
|
||||
-- would, fairly obviously, then be very nearly twice as long, and a fair bit harder to read.</span>
|
||||
|
||||
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span> <span style="color: #000080;font-style:italic;">-- (machine code!)</span>
|
||||
<span style="color: #008080;">include</span> <span style="color: #000000;">cgen</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span>
|
||||
|
||||
<span style="color: #008080;">procedure</span> <span style="color: #000000;">main</span><span style="color: #0000FF;">(</span><span style="color: #004080;">sequence</span> <span style="color: #000000;">cl</span><span style="color: #0000FF;">)</span>
|
||||
<span style="color: #000000;">open_files</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cl</span><span style="color: #0000FF;">)</span>
|
||||
<span style="color: #000000;">toks</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">lex</span><span style="color: #0000FF;">()</span>
|
||||
<span style="color: #004080;">object</span> <span style="color: #000000;">t</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">parse</span><span style="color: #0000FF;">()</span>
|
||||
<span style="color: #000000;">code_gen</span><span style="color: #0000FF;">(</span><span style="color: #000000;">t</span><span style="color: #0000FF;">)</span>
|
||||
<span style="color: #000000;">fixup</span><span style="color: #0000FF;">()</span>
|
||||
<span style="color: #008080;">if</span> <span style="color: #7060A8;">machine_bits</span><span style="color: #0000FF;">()=</span><span style="color: #000000;">32</span> <span style="color: #008080;">then</span>
|
||||
<span style="color: #000080;font-style:italic;">-- ^ as per note above</span>
|
||||
<span style="color: #000000;">call</span><span style="color: #0000FF;">(</span><span style="color: #000000;">code_mem</span><span style="color: #0000FF;">)</span>
|
||||
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
|
||||
<span style="color: #7060A8;">free</span><span style="color: #0000FF;">({</span><span style="color: #000000;">var_mem</span><span style="color: #0000FF;">,</span><span style="color: #000000;">code_mem</span><span style="color: #0000FF;">})</span>
|
||||
<span style="color: #000000;">close_files</span><span style="color: #0000FF;">()</span>
|
||||
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
|
||||
|
||||
<span style="color: #000080;font-style:italic;">--main(command_line())</span>
|
||||
<span style="color: #000000;">main</span><span style="color: #0000FF;">({</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"count.c"</span><span style="color: #0000FF;">})</span>
|
||||
<!--
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%%
|
||||
%%% The Rosetta Code Virtual Machine, for GNU Prolog.
|
||||
%%%
|
||||
%%% The following code uses GNU Prolog's extensions for global
|
||||
%%% variables.
|
||||
%%%
|
||||
%%% Usage: vm [INPUTFILE [OUTPUTFILE]]
|
||||
%%% The notation "-" means to use standard input or standard output.
|
||||
%%% Leaving out an argument is equivalent to specifying "-".
|
||||
%%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
make_and_run_machine(Input, Output) :-
|
||||
make_machine(Input),
|
||||
run_machine(Output).
|
||||
|
||||
run_machine(Output) :-
|
||||
repeat,
|
||||
next_instruction(Opcode, Arg),
|
||||
(Opcode == ('halt')
|
||||
-> true
|
||||
; (run_instruction(Output, Opcode, Arg),
|
||||
fail % Backtracks to the 'repeat'.
|
||||
)).
|
||||
|
||||
run_instruction(Output, Opcode, Arg) :-
|
||||
(
|
||||
(Opcode == ('add'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
is(Z, X + Y),
|
||||
push_value(Z))
|
||||
; (Opcode == ('sub'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
is(Z, X - Y),
|
||||
push_value(Z))
|
||||
; (Opcode == ('mul'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
is(Z, X * Y),
|
||||
push_value(Z))
|
||||
; (Opcode == ('div'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
is(Z, X // Y),
|
||||
push_value(Z))
|
||||
; (Opcode == ('mod'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
is(Z, X rem Y),
|
||||
push_value(Z))
|
||||
; (Opcode == ('lt'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
(X < Y -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('le'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
(X =< Y -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('gt'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
(X > Y -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('ge'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
(X >= Y -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('eq'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
(X =:= Y -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('ne'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
(X =\= Y -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('and'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
((X =\= 0, Y =\= 0) -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('or'),
|
||||
pop_value(Y),
|
||||
pop_value(X),
|
||||
((X =\= 0; Y =\= 0) -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('neg'),
|
||||
pop_value(X),
|
||||
is(Z, -X),
|
||||
push_value(Z))
|
||||
; (Opcode == ('not'),
|
||||
pop_value(X),
|
||||
(X =:= 0 -> Z = 1; Z = 0),
|
||||
push_value(Z))
|
||||
; (Opcode == ('prtc'),
|
||||
pop_value(X),
|
||||
char_code(C, X),
|
||||
write(Output, C))
|
||||
; (Opcode == ('prti'),
|
||||
pop_value(X),
|
||||
write(Output, X))
|
||||
; (Opcode == ('prts'),
|
||||
pop_value(K),
|
||||
g_read(the_strings(K), S),
|
||||
write(Output, S))
|
||||
; (Opcode == ('fetch'),
|
||||
g_read(the_data(Arg), X),
|
||||
push_value(X),
|
||||
skip_argument)
|
||||
; (Opcode == ('store'),
|
||||
pop_value(X),
|
||||
g_assign(the_data(Arg), X),
|
||||
skip_argument)
|
||||
; (Opcode == ('push'),
|
||||
push_value(Arg),
|
||||
skip_argument)
|
||||
; (Opcode == ('jmp'),
|
||||
relative_jump(Arg))
|
||||
; (Opcode == ('jz'),
|
||||
pop_value(X),
|
||||
(X =:= 0
|
||||
-> relative_jump(Arg)
|
||||
; skip_argument))
|
||||
).
|
||||
|
||||
relative_jump(Offset) :-
|
||||
g_read(the_program_counter, PC),
|
||||
is(PC1, PC + Offset),
|
||||
g_assign(the_program_counter, PC1).
|
||||
|
||||
skip_argument :-
|
||||
g_read(the_program_counter, PC),
|
||||
is(PC1, PC + 4),
|
||||
g_assign(the_program_counter, PC1).
|
||||
|
||||
next_instruction(Opcode, Arg) :-
|
||||
g_read(the_program_counter, PC),
|
||||
is(PC1, PC + 1),
|
||||
g_assign(the_program_counter, PC1),
|
||||
g_read(the_code(PC), {Opcode, Arg}).
|
||||
|
||||
push_value(X) :-
|
||||
g_read(the_stack_pointer, SP),
|
||||
is(SP1, SP + 1),
|
||||
g_assign(the_stack_pointer, SP1),
|
||||
g_assign(the_stack(SP), X).
|
||||
|
||||
pop_value(X) :-
|
||||
g_read(the_stack_pointer, SP),
|
||||
is(SP1, SP - 1),
|
||||
g_assign(the_stack_pointer, SP1),
|
||||
g_read(the_stack(SP1), X).
|
||||
|
||||
make_machine(Input) :-
|
||||
get_and_parse_the_header(Input, Datasize, Strings_Count),
|
||||
(Strings_Count =:= 0
|
||||
-> true
|
||||
; get_and_parse_the_strings(Input, Strings_Count)),
|
||||
get_and_parse_the_instructions(Input),
|
||||
(Datasize =:= 0
|
||||
-> true
|
||||
; g_assign(the_data, g_array(Datasize))),
|
||||
g_assign(the_stack, g_array(2048)),
|
||||
g_assign(the_stack_pointer, 0),
|
||||
g_assign(the_program_counter, 0).
|
||||
|
||||
get_and_parse_the_header(Stream, Datasize, Strings_Count) :-
|
||||
get_line(Stream, Line, ('\n')),
|
||||
parse_header(Line, Datasize, Strings_Count).
|
||||
|
||||
get_and_parse_the_strings(Stream, Strings_Count) :-
|
||||
% Make 'the_strings' an array of the string literals.
|
||||
get_and_parse_the_strings(Stream, Strings_Count, Lst),
|
||||
g_assign(the_strings, g_array(Lst)).
|
||||
get_and_parse_the_strings(Stream, I, Lst) :-
|
||||
% Note: this implementation is non-tail recursive.
|
||||
(I == 0
|
||||
-> Lst = []
|
||||
; (get_line(Stream, Line, ('\n')),
|
||||
parse_string_literal(Line, S),
|
||||
is(I1, I - 1),
|
||||
get_and_parse_the_strings(Stream, I1, Lst1),
|
||||
Lst = [S | Lst1])).
|
||||
|
||||
get_and_parse_the_instructions(Stream) :-
|
||||
get_and_parse_the_instructions(Stream, Lst),
|
||||
keysort(Lst, Lst1),
|
||||
last(Lst1, Addr_Max-_),
|
||||
is(Code_Size, Addr_Max + 5),
|
||||
g_assign(the_code, g_array(Code_Size, {('halt'), 0})),
|
||||
maplist(fill_instruction, Lst1).
|
||||
get_and_parse_the_instructions(Stream, Lst) :-
|
||||
get_and_parse_the_instructions(Stream, [], Lst).
|
||||
get_and_parse_the_instructions(Stream, Lst0, Lst) :-
|
||||
% This implementation is tail recursive. We consider the order of
|
||||
% the resulting list to be arbitrary.
|
||||
(get_line(Stream, Line, Terminal),
|
||||
drop_spaces(Line, S),
|
||||
(S = []
|
||||
-> (Terminal = end_of_file
|
||||
-> Lst = Lst0
|
||||
; get_and_parse_the_instructions(Stream, Lst0, Lst))
|
||||
; (parse_instruction(S, Address, Opcode, Arg),
|
||||
Instr = Address-{Opcode, Arg},
|
||||
(Terminal = end_of_file
|
||||
-> reverse([Instr | Lst0], Lst)
|
||||
; get_and_parse_the_instructions(Stream, [Instr | Lst0],
|
||||
Lst))))).
|
||||
|
||||
fill_instruction(Addr-Instr) :-
|
||||
g_assign(the_code(Addr), Instr).
|
||||
|
||||
parse_header(Line, Datasize, Strings_Count) :-
|
||||
drop_nondigits(Line, Lst1),
|
||||
split_digits(Lst1, Datasize_Digits, Rest1),
|
||||
drop_nondigits(Rest1, Lst2),
|
||||
split_digits(Lst2, Strings_Digits, _Rest2),
|
||||
number_chars(Datasize, Datasize_Digits),
|
||||
number_chars(Strings_Count, Strings_Digits).
|
||||
|
||||
parse_string_literal(Line, S) :-
|
||||
drop_spaces(Line, Lst1),
|
||||
Lst1 = ['"' | Lst2],
|
||||
rework_escape_sequences(Lst2, Lst3),
|
||||
atom_chars(S, Lst3).
|
||||
|
||||
rework_escape_sequences(Lst0, Lst) :-
|
||||
(Lst0 = [('"') | _]
|
||||
-> Lst = []
|
||||
; (Lst0 = [('\\'), ('n') | Tail1]
|
||||
-> (rework_escape_sequences(Tail1, Lst1),
|
||||
Lst = [('\n') | Lst1])
|
||||
; (Lst0 = [('\\'), ('\\') | Tail1]
|
||||
-> (rework_escape_sequences(Tail1, Lst1),
|
||||
Lst = [('\\') | Lst1])
|
||||
; (Lst0 = [C | Tail1],
|
||||
rework_escape_sequences(Tail1, Lst1),
|
||||
Lst = [C | Lst1])))).
|
||||
|
||||
parse_instruction(Line, Address, Opcode, Arg) :-
|
||||
drop_spaces(Line, Lst1),
|
||||
split_digits(Lst1, Address_Digits, Rest1),
|
||||
number_chars(Address, Address_Digits),
|
||||
drop_spaces(Rest1, Lst2),
|
||||
split_nonspaces(Lst2, Opcode_Chars, Rest2),
|
||||
atom_chars(Opcode, Opcode_Chars),
|
||||
drop_spaces(Rest2, Lst3),
|
||||
(Lst3 = []
|
||||
-> Arg = 0
|
||||
; (Lst3 = [C | Rest3],
|
||||
(is_digit(C)
|
||||
-> (split_digits(Lst3, Arg_Chars, _),
|
||||
number_chars(Arg, Arg_Chars))
|
||||
; (C = ('(')
|
||||
-> (split_before_char((')'), Rest3, Arg_Chars, _),
|
||||
number_chars(Arg, Arg_Chars))
|
||||
; (C = ('['),
|
||||
split_before_char((']'), Rest3, Arg_Chars, _),
|
||||
number_chars(Arg, Arg_Chars)))))).
|
||||
|
||||
is_space(C) :-
|
||||
(C = (' '); C = ('\t'); C = ('\n');
|
||||
C = ('\v'); C = ('\f'); C = ('\r')).
|
||||
|
||||
is_digit(C) :-
|
||||
(C = ('0'); C = ('1'); C = ('2'); C = ('3'); C = ('4');
|
||||
C = ('5'); C = ('6'); C = ('7'); C = ('8'); C = ('9')).
|
||||
|
||||
drop_spaces([], Lst) :-
|
||||
Lst = [].
|
||||
drop_spaces([C | Tail], Lst) :-
|
||||
(is_space(C)
|
||||
-> drop_spaces(Tail, Lst)
|
||||
; Lst = [C | Tail]).
|
||||
|
||||
drop_nondigits([], Lst) :-
|
||||
Lst = [].
|
||||
drop_nondigits([C | Tail], Lst) :-
|
||||
(is_digit(C)
|
||||
-> Lst = [C | Tail]
|
||||
; drop_nondigits(Tail, Lst)).
|
||||
|
||||
split_nonspaces([], Word, Rest) :-
|
||||
(Word = [], Rest = []).
|
||||
split_nonspaces([C | Tail], Word, Rest) :-
|
||||
(is_space(C)
|
||||
-> (Word = [], Rest = [C | Tail])
|
||||
; (split_nonspaces(Tail, Word1, Rest),
|
||||
Word = [C | Word1])).
|
||||
|
||||
split_digits([], Digits, Rest) :-
|
||||
(Digits = [], Rest = []).
|
||||
split_digits([C | Tail], Digits, Rest) :-
|
||||
(is_digit(C)
|
||||
-> (split_digits(Tail, Digits1, Rest),
|
||||
Digits = [C | Digits1])
|
||||
; (Digits = [], Rest = [C | Tail])).
|
||||
|
||||
split_before_char(_, [], Before, After) :-
|
||||
(Before = [], After = []).
|
||||
split_before_char(C, [C1 | Rest], Before, After) :-
|
||||
(C = C1
|
||||
-> (Before = [], After = [C1 | Rest])
|
||||
; (split_before_char(C, Rest, Before1, After),
|
||||
Before = [C1 | Before1])).
|
||||
|
||||
get_line(Stream, Line, Terminal) :-
|
||||
% Reads a line of input as a list of characters. The character that
|
||||
% terminates the line is returned separately; it may be either '\n'
|
||||
% or end_of_file.
|
||||
get_line_chars(Stream, [], Line, Terminal).
|
||||
|
||||
get_line_chars(Stream, Chars0, Chars, Terminal) :-
|
||||
% Helper predicate for get_line.
|
||||
get_char(Stream, C),
|
||||
((C = end_of_file; C = ('\n'))
|
||||
-> (reverse(Chars0, Chars), Terminal = C)
|
||||
; get_line_chars(Stream, [C | Chars0], Chars, Terminal)).
|
||||
|
||||
main(Args) :-
|
||||
(Args = []
|
||||
-> current_input(Input),
|
||||
current_output(Output),
|
||||
make_and_run_machine(Input, Output)
|
||||
; (Args = [Inp_Name]
|
||||
-> (Inp_Name = ('-')
|
||||
-> main([])
|
||||
; (open(Inp_Name, 'read', Input),
|
||||
current_output(Output),
|
||||
make_and_run_machine(Input, Output),
|
||||
close(Input)))
|
||||
; (Args = [Inp_Name, Out_Name | _],
|
||||
(Inp_Name = ('-')
|
||||
-> (Out_Name = ('-')
|
||||
-> main([])
|
||||
; (current_input(Input),
|
||||
open(Out_Name, 'write', Output),
|
||||
make_and_run_machine(Input, Output),
|
||||
close(Output)))
|
||||
; (Out_Name = ('-')
|
||||
-> main([Inp_Name])
|
||||
; (open(Inp_Name, 'read', Input),
|
||||
open(Out_Name, 'write', Output),
|
||||
make_and_run_machine(Input, Output),
|
||||
close(Input),
|
||||
close(Output))))))).
|
||||
|
||||
main :-
|
||||
argument_list(Args),
|
||||
main(Args).
|
||||
|
||||
:- initialization(main).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% Instructions for GNU Emacs--
|
||||
%%% local variables:
|
||||
%%% mode: prolog
|
||||
%%% prolog-indent-width: 2
|
||||
%%% end:
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
from __future__ import print_function
|
||||
import sys, struct
|
||||
|
||||
FETCH, STORE, PUSH, ADD, SUB, MUL, DIV, MOD, LT, GT, LE, GE, EQ, NE, AND, OR, NEG, NOT, \
|
||||
JMP, JZ, PRTC, PRTS, PRTI, HALT = range(24)
|
||||
|
||||
code_map = {
|
||||
"fetch": FETCH,
|
||||
"store": STORE,
|
||||
"push": PUSH,
|
||||
"add": ADD,
|
||||
"sub": SUB,
|
||||
"mul": MUL,
|
||||
"div": DIV,
|
||||
"mod": MOD,
|
||||
"lt": LT,
|
||||
"gt": GT,
|
||||
"le": LE,
|
||||
"ge": GE,
|
||||
"eq": EQ,
|
||||
"ne": NE,
|
||||
"and": AND,
|
||||
"or": OR,
|
||||
"not": NOT,
|
||||
"neg": NEG,
|
||||
"jmp": JMP,
|
||||
"jz": JZ,
|
||||
"prtc": PRTC,
|
||||
"prts": PRTS,
|
||||
"prti": PRTI,
|
||||
"halt": HALT
|
||||
}
|
||||
|
||||
input_file = None
|
||||
code = bytearray()
|
||||
string_pool = []
|
||||
word_size = 4
|
||||
|
||||
#*** show error and exit
|
||||
def error(msg):
|
||||
print("%s" % (msg))
|
||||
exit(1)
|
||||
|
||||
def int_to_bytes(val):
|
||||
return struct.pack("<i", val)
|
||||
|
||||
def bytes_to_int(bstr):
|
||||
return struct.unpack("<i", bstr)
|
||||
|
||||
#***
|
||||
def emit_byte(x):
|
||||
code.append(x)
|
||||
|
||||
#***
|
||||
def emit_word(x):
|
||||
s = int_to_bytes(x)
|
||||
for x in s:
|
||||
code.append(x)
|
||||
|
||||
#***
|
||||
def run_vm(data_size):
|
||||
stack = [0 for i in range(data_size + 1)]
|
||||
pc = 0
|
||||
while True:
|
||||
op = code[pc]
|
||||
pc += 1
|
||||
|
||||
if op == FETCH:
|
||||
stack.append(stack[bytes_to_int(code[pc:pc+word_size])[0]]);
|
||||
pc += word_size
|
||||
elif op == STORE:
|
||||
stack[bytes_to_int(code[pc:pc+word_size])[0]] = stack.pop();
|
||||
pc += word_size
|
||||
elif op == PUSH:
|
||||
stack.append(bytes_to_int(code[pc:pc+word_size])[0]);
|
||||
pc += word_size
|
||||
elif op == ADD: stack[-2] += stack[-1]; stack.pop()
|
||||
elif op == SUB: stack[-2] -= stack[-1]; stack.pop()
|
||||
elif op == MUL: stack[-2] *= stack[-1]; stack.pop()
|
||||
# use C like division semantics
|
||||
elif op == DIV: stack[-2] = int(float(stack[-2]) / stack[-1]); stack.pop()
|
||||
elif op == MOD: stack[-2] = int(float(stack[-2]) % stack[-1]); stack.pop()
|
||||
elif op == LT: stack[-2] = stack[-2] < stack[-1]; stack.pop()
|
||||
elif op == GT: stack[-2] = stack[-2] > stack[-1]; stack.pop()
|
||||
elif op == LE: stack[-2] = stack[-2] <= stack[-1]; stack.pop()
|
||||
elif op == GE: stack[-2] = stack[-2] >= stack[-1]; stack.pop()
|
||||
elif op == EQ: stack[-2] = stack[-2] == stack[-1]; stack.pop()
|
||||
elif op == NE: stack[-2] = stack[-2] != stack[-1]; stack.pop()
|
||||
elif op == AND: stack[-2] = stack[-2] and stack[-1]; stack.pop()
|
||||
elif op == OR: stack[-2] = stack[-2] or stack[-1]; stack.pop()
|
||||
elif op == NEG: stack[-1] = -stack[-1]
|
||||
elif op == NOT: stack[-1] = not stack[-1]
|
||||
elif op == JMP: pc += bytes_to_int(code[pc:pc+word_size])[0]
|
||||
elif op == JZ:
|
||||
if stack.pop():
|
||||
pc += word_size
|
||||
else:
|
||||
pc += bytes_to_int(code[pc:pc+word_size])[0]
|
||||
elif op == PRTC: print("%c" % (stack[-1]), end=''); stack.pop()
|
||||
elif op == PRTS: print("%s" % (string_pool[stack[-1]]), end=''); stack.pop()
|
||||
elif op == PRTI: print("%d" % (stack[-1]), end=''); stack.pop()
|
||||
elif op == HALT: break
|
||||
|
||||
def str_trans(srce):
|
||||
dest = ""
|
||||
i = 0
|
||||
while i < len(srce):
|
||||
if srce[i] == '\\' and i + 1 < len(srce):
|
||||
if srce[i + 1] == 'n':
|
||||
dest += '\n'
|
||||
i += 2
|
||||
elif srce[i + 1] == '\\':
|
||||
dest += '\\'
|
||||
i += 2
|
||||
else:
|
||||
dest += srce[i]
|
||||
i += 1
|
||||
|
||||
return dest
|
||||
|
||||
#***
|
||||
def load_code():
|
||||
global string_pool
|
||||
|
||||
line = input_file.readline()
|
||||
if len(line) == 0:
|
||||
error("empty line")
|
||||
|
||||
line_list = line.split()
|
||||
data_size = int(line_list[1])
|
||||
n_strings = int(line_list[3])
|
||||
|
||||
for i in range(n_strings):
|
||||
string_pool.append(str_trans(input_file.readline().strip('"\n')))
|
||||
|
||||
while True:
|
||||
line = input_file.readline()
|
||||
if len(line) == 0:
|
||||
break
|
||||
line_list = line.split()
|
||||
offset = int(line_list[0])
|
||||
instr = line_list[1]
|
||||
opcode = code_map.get(instr)
|
||||
if opcode == None:
|
||||
error("Unknown instruction %s at %d" % (instr, offset))
|
||||
emit_byte(opcode)
|
||||
if opcode in [JMP, JZ]:
|
||||
p = int(line_list[3])
|
||||
emit_word(p - (offset + 1))
|
||||
elif opcode == PUSH:
|
||||
value = int(line_list[2])
|
||||
emit_word(value)
|
||||
elif opcode in [FETCH, STORE]:
|
||||
value = int(line_list[2].strip('[]'))
|
||||
emit_word(value)
|
||||
|
||||
return data_size
|
||||
|
||||
#*** main driver
|
||||
input_file = sys.stdin
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
input_file = open(sys.argv[1], "r", 4096)
|
||||
except IOError as e:
|
||||
error(0, 0, "Can't open %s" % sys.argv[1])
|
||||
|
||||
data_size = load_code()
|
||||
run_vm(data_size)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,748 @@
|
|||
#lang typed/racket
|
||||
;;;
|
||||
;;; The Rosetta Code Virtual Machine, in Typed Racket.
|
||||
;;;
|
||||
;;; Migrated from the Common Lisp.
|
||||
;;;
|
||||
|
||||
;;; Yes, I could compute how much memory is needed, or I could assume
|
||||
;;; that the instructions are in address order. However, for *this*
|
||||
;;; implementation I am going to use a large fixed-size memory and use
|
||||
;;; the address fields of instructions to place the instructions.
|
||||
(: executable-memory-size Positive-Fixnum)
|
||||
(define executable-memory-size 65536)
|
||||
|
||||
;;; Similarly, I am going to have fixed size data and stack memory.
|
||||
(: data-memory-size Positive-Fixnum)
|
||||
(define data-memory-size 2048)
|
||||
(: stack-memory-size Positive-Fixnum)
|
||||
(define stack-memory-size 2048)
|
||||
|
||||
;;; And so I am going to have specialized types for the different
|
||||
;;; kinds of memory the platform contains. Also for its "word" and
|
||||
;;; register types.
|
||||
(define-type Word Nonnegative-Fixnum)
|
||||
(define-type Register (Boxof Word))
|
||||
(define-type Executable-Memory (Mutable-Vectorof Byte))
|
||||
(define-type Data-Memory (Mutable-Vectorof Word))
|
||||
(define-type Stack-Memory (Mutable-Vectorof Word))
|
||||
|
||||
(define re-blank-line #px"^\\s*$")
|
||||
(define re-parse-instr-1 #px"^\\s*(\\d+)\\s*(.*\\S)")
|
||||
(define re-parse-instr-2 #px"(?i:^(\\S+)\\s*(.*))")
|
||||
(define re-parse-instr-3 #px"^[[(]?([0-9-]+)")
|
||||
(define re-header
|
||||
#px"(?i:^\\s*Datasize\\s*:\\s*(\\d+)\\s*Strings\\s*:\\s*(\\d+))")
|
||||
(define re-leading-spaces #px"^\\s*")
|
||||
|
||||
(define opcode-names
|
||||
'("halt"
|
||||
"add"
|
||||
"sub"
|
||||
"mul"
|
||||
"div"
|
||||
"mod"
|
||||
"lt"
|
||||
"gt"
|
||||
"le"
|
||||
"ge"
|
||||
"eq"
|
||||
"ne"
|
||||
"and"
|
||||
"or"
|
||||
"neg"
|
||||
"not"
|
||||
"prtc"
|
||||
"prti"
|
||||
"prts"
|
||||
"fetch"
|
||||
"store"
|
||||
"push"
|
||||
"jmp"
|
||||
"jz"))
|
||||
|
||||
(: blank-line? (String -> Boolean))
|
||||
(define (blank-line? s)
|
||||
(not (not (regexp-match re-blank-line s))))
|
||||
|
||||
(: opcode-from-name (String -> Byte))
|
||||
(define (opcode-from-name s)
|
||||
(let ((i (index-of opcode-names s)))
|
||||
(assert i)
|
||||
(cast i Byte)))
|
||||
|
||||
(: create-executable-memory (-> Executable-Memory))
|
||||
(define (create-executable-memory)
|
||||
(make-vector executable-memory-size (opcode-from-name "halt")))
|
||||
|
||||
(: create-data-memory (-> Data-Memory))
|
||||
(define (create-data-memory)
|
||||
(make-vector data-memory-size 0))
|
||||
|
||||
(: create-stack-memory (-> Stack-Memory))
|
||||
(define (create-stack-memory)
|
||||
(make-vector stack-memory-size 0))
|
||||
|
||||
(: create-register (-> Register))
|
||||
(define (create-register)
|
||||
(box 0))
|
||||
|
||||
(struct machine
|
||||
((sp : Register) ; Stack pointer.
|
||||
(ip : Register) ; Instruction pointer (that is, program counter).
|
||||
(code : Executable-Memory)
|
||||
(data : Data-Memory)
|
||||
(stack : Stack-Memory)
|
||||
(strings : (Immutable-Vectorof String))
|
||||
(output : Output-Port))
|
||||
#:type-name Machine
|
||||
#:constructor-name %make-machine)
|
||||
|
||||
(: make-machine ((Immutable-Vectorof String) Output-Port -> Machine))
|
||||
(define (make-machine strings outf)
|
||||
(%make-machine (create-register)
|
||||
(create-register)
|
||||
(create-executable-memory)
|
||||
(create-data-memory)
|
||||
(create-stack-memory)
|
||||
strings
|
||||
outf))
|
||||
|
||||
(define-type Instruction-Data (List Word Byte (U False Word)))
|
||||
|
||||
(: insert-instruction (Executable-Memory Instruction-Data -> Void))
|
||||
(define (insert-instruction memory instr)
|
||||
(void
|
||||
(match instr
|
||||
((list address opcode arg)
|
||||
(let ((instr-size (if arg 5 1)))
|
||||
(unless (<= (+ address instr-size) executable-memory-size)
|
||||
(raise-user-error
|
||||
"the VM's executable memory size is exceeded"))
|
||||
(vector-set! memory address opcode)
|
||||
(when arg
|
||||
;; Big-endian order.
|
||||
(vector-set! memory (+ address 1)
|
||||
(bitwise-and (arithmetic-shift arg -24) #xFF))
|
||||
(vector-set! memory (+ address 2)
|
||||
(bitwise-and (arithmetic-shift arg -16) #xFF))
|
||||
(vector-set! memory (+ address 3)
|
||||
(bitwise-and (arithmetic-shift arg -8) #xFF))
|
||||
(vector-set! memory (+ address 4)
|
||||
(bitwise-and arg #xFF))))))))
|
||||
|
||||
(: load-executable-memory (Executable-Memory
|
||||
(Listof Instruction-Data) ->
|
||||
Void))
|
||||
(define (load-executable-memory memory instr-lst)
|
||||
(let loop ((p instr-lst))
|
||||
(if (null? p)
|
||||
(void)
|
||||
(let ((instr (car p)))
|
||||
(insert-instruction memory (car p))
|
||||
(loop (cdr p))))))
|
||||
|
||||
(: number->word (Number -> Word))
|
||||
(define (number->word n)
|
||||
(cast (bitwise-and (cast n Integer) #xFFFFFFFF) Word))
|
||||
|
||||
(: string->word (String -> Word))
|
||||
(define (string->word s)
|
||||
(let ((n (string->number s)))
|
||||
(assert (number? n))
|
||||
(number->word n)))
|
||||
|
||||
(: parse-instruction (String -> (U False Instruction-Data)))
|
||||
(define (parse-instruction s)
|
||||
(and (not (blank-line? s))
|
||||
(let* ((strings (cast (regexp-match re-parse-instr-1 s)
|
||||
(Listof String)))
|
||||
(address (cast (string->number (second strings))
|
||||
Word))
|
||||
(split (cast (regexp-match re-parse-instr-2
|
||||
(third strings))
|
||||
(Listof String)))
|
||||
(opcode-name (string-downcase (second split)))
|
||||
(opcode (opcode-from-name opcode-name))
|
||||
(arguments (third split))
|
||||
(has-arg? (match opcode-name
|
||||
((or "fetch" "store" "push" "jmp" "jz") #t)
|
||||
(_ #f))))
|
||||
(if has-arg?
|
||||
(let* ((argstr-lst
|
||||
(cast (regexp-match re-parse-instr-3 arguments)
|
||||
(Listof String)))
|
||||
(argstr (second argstr-lst))
|
||||
(arg (string->word argstr)))
|
||||
`(,address ,opcode ,arg))
|
||||
`(,address ,opcode #f)))))
|
||||
|
||||
(: read-instructions (Input-Port -> (Listof Instruction-Data)))
|
||||
(define (read-instructions inpf)
|
||||
(let loop ((line (read-line inpf))
|
||||
(lst (cast '() (Listof Instruction-Data))))
|
||||
(if (eof-object? line)
|
||||
(reverse lst)
|
||||
(let ((instr (parse-instruction line)))
|
||||
(loop (read-line inpf)
|
||||
(if instr
|
||||
(cons instr lst)
|
||||
lst))))))
|
||||
|
||||
(: read-datasize-and-strings-count (Input-Port -> (Values Word Word)))
|
||||
(define (read-datasize-and-strings-count inpf)
|
||||
(let ((line (read-line inpf)))
|
||||
(unless (string? line)
|
||||
(raise-user-error "empty input"))
|
||||
;; This is a permissive implementation.
|
||||
(let* ((strings (cast (regexp-match re-header line)
|
||||
(Listof String)))
|
||||
(datasize (string->word (second strings)))
|
||||
(strings-count (string->word (third strings))))
|
||||
(values datasize strings-count))))
|
||||
|
||||
(: parse-string-literal (String -> String))
|
||||
(define (parse-string-literal s)
|
||||
;; This is a permissive implementation, but only in that it skips
|
||||
;; any leading space. It does not check carefully for outright
|
||||
;; mistakes.
|
||||
(let* ((s (regexp-replace re-leading-spaces s ""))
|
||||
(quote-mark (string-ref s 0)))
|
||||
(let loop ((i 1)
|
||||
(lst (cast '() (Listof Char))))
|
||||
(if (char=? (string-ref s i) quote-mark)
|
||||
(list->string (reverse lst))
|
||||
(let ((c (string-ref s i)))
|
||||
(if (char=? c #\\)
|
||||
(let ((c0 (match (string-ref s (+ i 1))
|
||||
(#\n #\newline)
|
||||
(c1 c1))))
|
||||
(loop (+ i 2) (cons c0 lst)))
|
||||
(loop (+ i 1) (cons c lst))))))))
|
||||
|
||||
(: read-string-literals (Input-Port Word -> (Listof String)))
|
||||
(define (read-string-literals inpf strings-count)
|
||||
(for/list ((i (in-range strings-count)))
|
||||
(let ((line (read-line inpf)))
|
||||
(begin (assert (string? line))
|
||||
(parse-string-literal line)))))
|
||||
|
||||
(: open-inpf (String -> Input-Port))
|
||||
(define (open-inpf inpf-filename)
|
||||
(if (string=? inpf-filename "-")
|
||||
(current-input-port)
|
||||
(open-input-file inpf-filename)))
|
||||
|
||||
(: open-outf (String -> Output-Port))
|
||||
(define (open-outf outf-filename)
|
||||
(if (string=? outf-filename "-")
|
||||
(current-output-port)
|
||||
(open-output-file outf-filename #:exists 'truncate)))
|
||||
|
||||
(: word-signbit? (Word -> Boolean))
|
||||
(define (word-signbit? x)
|
||||
;; True if and only if the sign bit is set.
|
||||
(not (zero? (bitwise-and x #x80000000))))
|
||||
|
||||
(: word-add (Word Word -> Word))
|
||||
(define (word-add x y)
|
||||
;; Addition with overflow freely allowed.
|
||||
(cast (bitwise-and (+ x y) #xFFFFFFFF) Word))
|
||||
|
||||
(: word-neg (Word -> Word))
|
||||
(define (word-neg x)
|
||||
;; The two's complement.
|
||||
(word-add (cast (bitwise-xor x #xFFFFFFFF) Word) 1))
|
||||
|
||||
(: word-sub (Word Word -> Word))
|
||||
(define (word-sub x y)
|
||||
;; Subtraction with overflow freely allowed.
|
||||
(word-add x (word-neg y)))
|
||||
|
||||
(: word-mul (Word Word -> Word))
|
||||
(define (word-mul x y)
|
||||
;; Signed multiplication.
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(let ((abs-x (if x<0 (word-neg x) x))
|
||||
(abs-y (if y<0 (word-neg y) y)))
|
||||
(let* ((abs-xy (cast (bitwise-and (* abs-x abs-y) #xFFFFFFFF)
|
||||
Word)))
|
||||
(if x<0
|
||||
(if y<0 abs-xy (word-neg abs-xy))
|
||||
(if y<0 (word-neg abs-xy) abs-xy))))))
|
||||
|
||||
(: word-div (Word Word -> Word))
|
||||
(define (word-div x y)
|
||||
;; The quotient after signed integer division with truncation
|
||||
;; towards zero.
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(let ((abs-x (if x<0 (word-neg x) x))
|
||||
(abs-y (if y<0 (word-neg y) y)))
|
||||
(let* ((abs-x/y (cast (bitwise-and (quotient abs-x abs-y)
|
||||
#xFFFFFFFF)
|
||||
Word)))
|
||||
(if x<0
|
||||
(if y<0 abs-x/y (word-neg abs-x/y))
|
||||
(if y<0 (word-neg abs-x/y) abs-x/y))))))
|
||||
|
||||
(: word-mod (Word Word -> Word))
|
||||
(define (word-mod x y)
|
||||
;; The remainder after signed integer division with truncation
|
||||
;; towards zero.
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(let ((abs-x (if x<0 (word-neg x) x))
|
||||
(abs-y (if y<0 (word-neg y) y)))
|
||||
(let* ((abs-x/y (cast (bitwise-and (remainder abs-x abs-y)
|
||||
#xFFFFFFFF)
|
||||
Word)))
|
||||
(if x<0
|
||||
(if y<0 abs-x/y (word-neg abs-x/y))
|
||||
(if y<0 (word-neg abs-x/y) abs-x/y))))))
|
||||
|
||||
(: b2i (Boolean -> (U Zero One)))
|
||||
(define (b2i b)
|
||||
(if b 1 0))
|
||||
|
||||
(: word-lt (Word Word -> Word))
|
||||
(define (word-lt x y)
|
||||
;; Signed comparison: is x less than y?
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (< x y) #t)
|
||||
(if y<0 #f (< x y))))))
|
||||
|
||||
(: word-le (Word Word -> Word))
|
||||
(define (word-le x y)
|
||||
;; Signed comparison: is x less than or equal to y?
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (<= x y) #t)
|
||||
(if y<0 #f (<= x y))))))
|
||||
|
||||
(: word-gt (Word Word -> Word))
|
||||
(define (word-gt x y)
|
||||
;; Signed comparison: is x greater than y?
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (> x y) #f)
|
||||
(if y<0 #t (> x y))))))
|
||||
|
||||
(: word-ge (Word Word -> Word))
|
||||
(define (word-ge x y)
|
||||
;; Signed comparison: is x greater than or equal to y?
|
||||
(let ((x<0 (word-signbit? x))
|
||||
(y<0 (word-signbit? y)))
|
||||
(b2i (if x<0
|
||||
(if y<0 (>= x y) #f)
|
||||
(if y<0 #t (>= x y))))))
|
||||
|
||||
(: word-eq (Word Word -> Word))
|
||||
(define (word-eq x y)
|
||||
;; Is x equal to y?
|
||||
(b2i (= x y)))
|
||||
|
||||
(: word-ne (Word Word -> Word))
|
||||
(define (word-ne x y)
|
||||
;; Is x not equal to y?
|
||||
(b2i (not (= x y))))
|
||||
|
||||
(: word-cmp (Word -> Word))
|
||||
(define (word-cmp x)
|
||||
;; The logical complement.
|
||||
(b2i (zero? x)))
|
||||
|
||||
(: word-and (Word Word -> Word))
|
||||
(define (word-and x y)
|
||||
;; The logical conjunction.
|
||||
(b2i (and (not (zero? x)) (not (zero? y)))))
|
||||
|
||||
(: word-or (Word Word -> Word))
|
||||
(define (word-or x y)
|
||||
;; The logical disjunction.
|
||||
(b2i (or (not (zero? x)) (not (zero? y)))))
|
||||
|
||||
(: unop (Stack-Memory Register (Word -> Word) -> Void))
|
||||
(define (unop stack sp operation)
|
||||
;; Perform a unary operation on the stack.
|
||||
(let ((i (unbox sp)))
|
||||
(unless (<= 1 i)
|
||||
(raise-user-error "stack underflow"))
|
||||
(let ((x (vector-ref stack (- i 1))))
|
||||
;; Note how, in contrast to Common Lisp, "operation" is not in a
|
||||
;; namespace separate from that of "ordinary" values, such as
|
||||
;; numbers and strings. (Which way is "better" is a matter of
|
||||
;; taste, and probably depends mostly on what "functional"
|
||||
;; language one learnt first. Mine was Caml Light, so I prefer
|
||||
;; the Scheme way. :) )
|
||||
(vector-set! stack (- i 1) (operation x)))))
|
||||
|
||||
(: binop (Stack-Memory Register (Word Word -> Word) -> Void))
|
||||
(define (binop stack sp operation)
|
||||
;; Perform a binary operation on the stack.
|
||||
(let ((i (unbox sp)))
|
||||
(unless (<= 2 i)
|
||||
(raise-user-error "stack underflow"))
|
||||
(let ((x (vector-ref stack (- i 2)))
|
||||
(y (vector-ref stack (- i 1))))
|
||||
(vector-set! stack (- i 2) (operation x y)))
|
||||
(set-box! sp (cast (- i 1) Word))))
|
||||
|
||||
(: jri (Executable-Memory Register -> Void))
|
||||
(define (jri code ip)
|
||||
;; Jump relative immediate.
|
||||
(let ((j (unbox ip)))
|
||||
(unless (<= (+ j 4) executable-memory-size)
|
||||
(raise-user-error "address past end of executable memory"))
|
||||
;; Big-endian order.
|
||||
(let* ((offset (vector-ref code (+ j 3)))
|
||||
(offset (bitwise-ior
|
||||
(arithmetic-shift (vector-ref code (+ j 2)) 8)
|
||||
offset))
|
||||
(offset (bitwise-ior
|
||||
(arithmetic-shift (vector-ref code (+ j 1)) 16)
|
||||
offset))
|
||||
(offset (bitwise-ior
|
||||
(arithmetic-shift (vector-ref code j) 24)
|
||||
offset)))
|
||||
(set-box! ip (word-add j (cast offset Word))))))
|
||||
|
||||
(: jriz (Stack-Memory Register Executable-Memory Register -> Void))
|
||||
(define (jriz stack sp code ip)
|
||||
;; Jump relative immediate, if zero.
|
||||
(let ((i (unbox sp)))
|
||||
(unless (<= 1 i)
|
||||
(raise-user-error "stack underflow"))
|
||||
(let ((x (vector-ref stack (- i 1))))
|
||||
(set-box! sp (- i 1))
|
||||
(if (zero? x)
|
||||
(jri code ip)
|
||||
(let ((j (unbox ip)))
|
||||
(set-box! ip (cast (+ j 4) Word)))))))
|
||||
|
||||
(: get-immediate-value (Executable-Memory Register -> Word))
|
||||
(define (get-immediate-value code ip)
|
||||
(let ((j (unbox ip)))
|
||||
(unless (<= (+ j 4) executable-memory-size)
|
||||
(raise-user-error "address past end of executable memory"))
|
||||
;; Big-endian order.
|
||||
(let* ((x (vector-ref code (+ j 3)))
|
||||
(x (bitwise-ior
|
||||
(arithmetic-shift (vector-ref code (+ j 2)) 8)
|
||||
x))
|
||||
(x (bitwise-ior
|
||||
(arithmetic-shift (vector-ref code (+ j 1)) 16)
|
||||
x))
|
||||
(x (bitwise-ior
|
||||
(arithmetic-shift (vector-ref code j) 24)
|
||||
x)))
|
||||
(set-box! ip (cast (+ j 4) Word))
|
||||
(cast x Word))))
|
||||
|
||||
(: pushi (Stack-Memory Register Executable-Memory Register -> Void))
|
||||
(define (pushi stack sp code ip)
|
||||
;; Push-immediate a value from executable memory onto the stack.
|
||||
(let ((i (unbox sp)))
|
||||
(unless (< i stack-memory-size)
|
||||
(raise-user-error "stack overflow"))
|
||||
(vector-set! stack i (get-immediate-value code ip))
|
||||
(set-box! sp (cast (+ i 1) Word))))
|
||||
|
||||
(: fetch (Stack-Memory
|
||||
Register Executable-Memory Register
|
||||
Data-Memory -> Void))
|
||||
(define (fetch stack sp code ip data)
|
||||
;; Fetch data to the stack, using the storage location given in
|
||||
;; executable memory.
|
||||
(let ((i (unbox sp)))
|
||||
(unless (< i stack-memory-size)
|
||||
(raise-user-error "stack overflow"))
|
||||
(let* ((k (get-immediate-value code ip))
|
||||
(x (vector-ref data k)))
|
||||
(vector-set! stack i x)
|
||||
(set-box! sp (cast (+ i 1) Word)))))
|
||||
|
||||
(: pop-one (Stack-Memory Register -> Word))
|
||||
(define (pop-one stack sp)
|
||||
(let ((i (unbox sp)))
|
||||
(unless (<= 1 i)
|
||||
(raise-user-error "stack underflow"))
|
||||
(let* ((x (vector-ref stack (- i 1))))
|
||||
(set-box! sp (- i 1))
|
||||
x)))
|
||||
|
||||
(: store (Stack-Memory
|
||||
Register Executable-Memory Register
|
||||
Data-Memory -> Void))
|
||||
(define (store stack sp code ip data)
|
||||
;; Store data from the stack, using the storage location given in
|
||||
;; executable memory.
|
||||
(let ((i (unbox sp)))
|
||||
(unless (<= 1 i)
|
||||
(raise-user-error "stack underflow"))
|
||||
(let ((k (get-immediate-value code ip))
|
||||
(x (pop-one stack sp)))
|
||||
(vector-set! data k x))))
|
||||
|
||||
(: prti (Stack-Memory Register Output-Port -> Void))
|
||||
(define (prti stack sp outf)
|
||||
;; Print the top value of the stack, as a signed decimal value.
|
||||
(let* ((n (pop-one stack sp))
|
||||
(n<0 (word-signbit? n)))
|
||||
(if n<0
|
||||
(begin (display "-" outf)
|
||||
(display (word-neg n) outf))
|
||||
(display n outf))))
|
||||
|
||||
(: prtc (Stack-Memory Register Output-Port -> Void))
|
||||
(define (prtc stack sp outf)
|
||||
;; Print the top value of the stack, as a character.
|
||||
(let ((c (pop-one stack sp)))
|
||||
(display (integer->char c) outf)))
|
||||
|
||||
(: prts (Stack-Memory
|
||||
Register (Immutable-Vectorof String) Output-Port -> Void))
|
||||
(define (prts stack sp strings outf)
|
||||
;; Print the string specified by the top of the stack.
|
||||
(let* ((k (pop-one stack sp))
|
||||
(s (vector-ref strings k)))
|
||||
(display s outf)))
|
||||
|
||||
;;
|
||||
;; I have written macros in the standard R6RS fashion, with a lambda
|
||||
;; and syntax-case, so the examples may be widely illustrative. Racket
|
||||
;; supports this style, despite (purposely) not adhering to any Scheme
|
||||
;; standard.
|
||||
;;
|
||||
;; Some Schemes that do not provide syntax-case (CHICKEN, for
|
||||
;; instance) provide alternatives that may be quite different.
|
||||
;;
|
||||
;; R5RS and R7RS require only syntax-rules, which cannot do what we
|
||||
;; are doing here. (What we are doing is similar to using ## in a
|
||||
;; modern C macro, except that the pieces are not merely raw text, and
|
||||
;; they must be properly typed at every stage.)
|
||||
;;
|
||||
(define-syntax define-machine-binop
|
||||
(lambda (stx)
|
||||
(syntax-case stx ()
|
||||
((_ op)
|
||||
(let* ((op^ (syntax->datum #'op))
|
||||
(machine-op (string-append "machine-" op^))
|
||||
(machine-op (string->symbol machine-op))
|
||||
(machine-op (datum->syntax stx machine-op))
|
||||
(word-op (string-append "word-" op^))
|
||||
(word-op (string->symbol word-op))
|
||||
(word-op (datum->syntax stx word-op)))
|
||||
#`(begin
|
||||
(: #,machine-op (Machine -> Void))
|
||||
(define (#,machine-op mach)
|
||||
(binop (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
#,word-op))))))))
|
||||
|
||||
(define-syntax define-machine-unop
|
||||
(lambda (stx)
|
||||
(syntax-case stx ()
|
||||
((_ op)
|
||||
(let* ((op^ (syntax->datum #'op))
|
||||
(machine-op (string-append "machine-" op^))
|
||||
(machine-op (string->symbol machine-op))
|
||||
(machine-op (datum->syntax stx machine-op))
|
||||
(word-op (string-append "word-" op^))
|
||||
(word-op (string->symbol word-op))
|
||||
(word-op (datum->syntax stx word-op)))
|
||||
#`(begin
|
||||
(: #,machine-op (Machine -> Void))
|
||||
(define (#,machine-op mach)
|
||||
(unop (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
#,word-op))))))))
|
||||
|
||||
(define-machine-binop "add")
|
||||
(define-machine-binop "sub")
|
||||
(define-machine-binop "mul")
|
||||
(define-machine-binop "div")
|
||||
(define-machine-binop "mod")
|
||||
(define-machine-binop "lt")
|
||||
(define-machine-binop "gt")
|
||||
(define-machine-binop "le")
|
||||
(define-machine-binop "ge")
|
||||
(define-machine-binop "eq")
|
||||
(define-machine-binop "ne")
|
||||
(define-machine-binop "and")
|
||||
(define-machine-binop "or")
|
||||
|
||||
(define-machine-unop "neg")
|
||||
|
||||
(: machine-not (Machine -> Void))
|
||||
(define (machine-not mach)
|
||||
(unop (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
word-cmp))
|
||||
|
||||
(: machine-prtc (Machine -> Void))
|
||||
(define (machine-prtc mach)
|
||||
(prtc (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-output mach)))
|
||||
|
||||
(: machine-prti (Machine -> Void))
|
||||
(define (machine-prti mach)
|
||||
(prti (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-output mach)))
|
||||
|
||||
(: machine-prts (Machine -> Void))
|
||||
(define (machine-prts mach)
|
||||
(prts (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-strings mach)
|
||||
(machine-output mach)))
|
||||
|
||||
(: machine-fetch (Machine -> Void))
|
||||
(define (machine-fetch mach)
|
||||
(fetch (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)
|
||||
(machine-data mach)))
|
||||
|
||||
(: machine-store (Machine -> Void))
|
||||
(define (machine-store mach)
|
||||
(store (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)
|
||||
(machine-data mach)))
|
||||
|
||||
(: machine-push (Machine -> Void))
|
||||
(define (machine-push mach)
|
||||
(pushi (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)))
|
||||
|
||||
(: machine-jmp (Machine -> Void))
|
||||
(define (machine-jmp mach)
|
||||
(jri (machine-code mach)
|
||||
(machine-ip mach)))
|
||||
|
||||
(: machine-jz (Machine -> Void))
|
||||
(define (machine-jz mach)
|
||||
(jriz (machine-stack mach)
|
||||
(machine-sp mach)
|
||||
(machine-code mach)
|
||||
(machine-ip mach)))
|
||||
|
||||
(: get-opcode (Machine -> Byte))
|
||||
(define (get-opcode mach)
|
||||
(let ((code (machine-code mach))
|
||||
(ip (machine-ip mach)))
|
||||
(let ((j (unbox ip)))
|
||||
(unless (< j executable-memory-size)
|
||||
(raise-user-error "address past end of executable memory"))
|
||||
(let ((opcode (vector-ref code j)))
|
||||
(set-box! ip (cast (+ j 1) Word))
|
||||
opcode))))
|
||||
|
||||
(: run-instruction (Machine Byte -> Void))
|
||||
(define (run-instruction mach opcode)
|
||||
(let ((op-mod-4 (bitwise-and opcode #x3))
|
||||
(op-div-4 (arithmetic-shift opcode -2)))
|
||||
(match op-div-4
|
||||
(0 (match op-mod-4
|
||||
(1 (machine-add mach))
|
||||
(2 (machine-sub mach))
|
||||
(3 (machine-mul mach))))
|
||||
(1 (match op-mod-4
|
||||
(0 (machine-div mach))
|
||||
(1 (machine-mod mach))
|
||||
(2 (machine-lt mach))
|
||||
(3 (machine-gt mach))))
|
||||
(2 (match op-mod-4
|
||||
(0 (machine-le mach))
|
||||
(1 (machine-ge mach))
|
||||
(2 (machine-eq mach))
|
||||
(3 (machine-ne mach))))
|
||||
(3 (match op-mod-4
|
||||
(0 (machine-and mach))
|
||||
(1 (machine-or mach))
|
||||
(2 (machine-neg mach))
|
||||
(3 (machine-not mach))))
|
||||
(4 (match op-mod-4
|
||||
(0 (machine-prtc mach))
|
||||
(1 (machine-prti mach))
|
||||
(2 (machine-prts mach))
|
||||
(3 (machine-fetch mach))))
|
||||
(5 (match op-mod-4
|
||||
(0 (machine-store mach))
|
||||
(1 (machine-push mach))
|
||||
(2 (machine-jmp mach))
|
||||
(3 (machine-jz mach)))))))
|
||||
|
||||
(: run-vm (Machine -> Void))
|
||||
(define (run-vm mach)
|
||||
(let ((opcode-for-halt (cast (opcode-from-name "halt") Byte))
|
||||
(opcode-for-add (cast (opcode-from-name "add") Byte))
|
||||
(opcode-for-jz (cast (opcode-from-name "jz") Byte)))
|
||||
(let loop ((opcode (get-opcode mach)))
|
||||
(unless (= opcode opcode-for-halt)
|
||||
(begin
|
||||
(when (or (< opcode opcode-for-add)
|
||||
(< opcode-for-jz opcode))
|
||||
(raise-user-error "unsupported opcode"))
|
||||
(run-instruction mach opcode)
|
||||
(loop (get-opcode mach)))))))
|
||||
|
||||
(define (usage-error)
|
||||
(display "Usage: vm [INPUTFILE [OUTPUTFILE]]" (current-error-port))
|
||||
(newline (current-error-port))
|
||||
(display "If either INPUTFILE or OUTPUTFILE is \"-\", the respective"
|
||||
(current-error-port))
|
||||
(display " standard I/O is used." (current-error-port))
|
||||
(newline (current-error-port))
|
||||
(exit 1))
|
||||
|
||||
(: get-filenames (-> (Values String String)))
|
||||
(define (get-filenames)
|
||||
(match (current-command-line-arguments)
|
||||
((vector) (values "-" "-"))
|
||||
((vector inpf-filename)
|
||||
(values (cast inpf-filename String) "-"))
|
||||
((vector inpf-filename outf-filename)
|
||||
(values (cast inpf-filename String)
|
||||
(cast outf-filename String)))
|
||||
(_ (usage-error)
|
||||
(values "" ""))))
|
||||
|
||||
(let-values (((inpf-filename outf-filename) (get-filenames)))
|
||||
(let* ((inpf (open-inpf inpf-filename))
|
||||
(outf (open-outf outf-filename)))
|
||||
(let-values (((datasize strings-count)
|
||||
(read-datasize-and-strings-count inpf)))
|
||||
(let* ((strings
|
||||
(vector->immutable-vector
|
||||
(list->vector
|
||||
(read-string-literals inpf strings-count))))
|
||||
(instructions (read-instructions inpf))
|
||||
|
||||
(mach (make-machine strings outf)))
|
||||
|
||||
(unless (<= datasize data-memory-size)
|
||||
(raise-user-error
|
||||
"the VM's data memory size is exceeded"))
|
||||
|
||||
(load-executable-memory (machine-code mach) instructions)
|
||||
(run-vm mach)
|
||||
|
||||
(unless (string=? inpf-filename "-")
|
||||
(close-input-port inpf))
|
||||
(unless (string=? outf-filename "-")
|
||||
(close-output-port outf))
|
||||
|
||||
(exit 0)))))
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
my @CODE = q:to/END/.lines;
|
||||
Datasize: 3 Strings: 2
|
||||
"count is: "
|
||||
"\n"
|
||||
0 push 1
|
||||
5 store [0]
|
||||
10 fetch [0]
|
||||
15 push 10
|
||||
20 lt
|
||||
21 jz (68) 65 # jump value adjusted
|
||||
26 push 0
|
||||
31 prts
|
||||
32 fetch [0]
|
||||
37 prti
|
||||
38 push 1
|
||||
43 prts
|
||||
44 fetch [0]
|
||||
49 push 1
|
||||
54 add
|
||||
55 store [0]
|
||||
60 jmp (-87) 10 # jump value adjusted
|
||||
65 halt
|
||||
END
|
||||
|
||||
my (@stack, @strings, @data, $memory);
|
||||
my $pc = 0;
|
||||
|
||||
(@CODE.shift) ~~ /'Datasize:' \s+ (\d+) \s+ 'Strings:' \s+ (\d+)/ or die "bad header";
|
||||
my $w = $0; # 'wordsize' of op-codes and 'width' of data values
|
||||
@strings.push: (my $s = @CODE.shift) eq '"\n"' ?? "\n" !! $s.subst(/'"'/, '', :g) for 1..$1;
|
||||
|
||||
sub value { substr($memory, ($pc += $w) - $w, $w).trim }
|
||||
|
||||
my %ops = (
|
||||
'no-op' => sub { },
|
||||
'add' => sub { @stack[*-2] += @stack.pop },
|
||||
'sub' => sub { @stack[*-2] -= @stack.pop },
|
||||
'mul' => sub { @stack[*-2] *= @stack.pop },
|
||||
'div' => sub { @stack[*-2] /= @stack.pop },
|
||||
'mod' => sub { @stack[*-2] %= @stack.pop },
|
||||
'neg' => sub { @stack[*-1] = - @stack[*-1] },
|
||||
'and' => sub { @stack[*-2] &&= @stack[*-1]; @stack.pop },
|
||||
'or' => sub { @stack[*-2] ||= @stack[*-1]; @stack.pop },
|
||||
'not' => sub { @stack[*-1] = @stack[*-1] ?? 0 !! 1 },
|
||||
'lt' => sub { @stack[*-1] = @stack[*-2] < @stack.pop ?? 1 !! 0 },
|
||||
'gt' => sub { @stack[*-1] = @stack[*-2] > @stack.pop ?? 1 !! 0 },
|
||||
'le' => sub { @stack[*-1] = @stack[*-2] <= @stack.pop ?? 1 !! 0 },
|
||||
'ge' => sub { @stack[*-1] = @stack[*-2] >= @stack.pop ?? 1 !! 0 },
|
||||
'ne' => sub { @stack[*-1] = @stack[*-2] != @stack.pop ?? 1 !! 0 },
|
||||
'eq' => sub { @stack[*-1] = @stack[*-2] == @stack.pop ?? 1 !! 0 },
|
||||
'store' => sub { @data[&value] = @stack.pop },
|
||||
'fetch' => sub { @stack.push: @data[&value] // 0 },
|
||||
'push' => sub { @stack.push: value() },
|
||||
'jmp' => sub { $pc += value() - $w },
|
||||
'jz' => sub { $pc += @stack.pop ?? $w !! value() - $w },
|
||||
'prts' => sub { print @strings[@stack.pop] },
|
||||
'prti' => sub { print @stack.pop },
|
||||
'prtc' => sub { print chr @stack.pop },
|
||||
'halt' => sub { exit }
|
||||
);
|
||||
|
||||
my %op2n = %ops.keys.sort Z=> 0..*;
|
||||
my %n2op = %op2n.invert;
|
||||
%n2op{''} = 'no-op';
|
||||
|
||||
for @CODE -> $_ {
|
||||
next unless /\w/;
|
||||
/^ \s* \d+ \s+ (\w+)/ or die "bad line $_";
|
||||
$memory ~= %op2n{$0}.fmt("%{$w}d");
|
||||
/'(' ('-'?\d+) ')' | (\d+) ']'? $/;
|
||||
$memory ~= $0 ?? $0.fmt("%{$w}d") !! ' ' x $w;
|
||||
}
|
||||
|
||||
loop {
|
||||
my $opcode = substr($memory, $pc, $w).trim;
|
||||
$pc += $w;
|
||||
%ops{%n2op{ $opcode }}();
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
package xyz.hyperreal.rosettacodeCompiler
|
||||
|
||||
import java.io.{BufferedReader, FileReader, Reader, StringReader}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
object VirtualMachine {
|
||||
|
||||
private object Opcodes {
|
||||
val FETCH: Byte = 0
|
||||
val STORE: Byte = 1
|
||||
val PUSH: Byte = 2
|
||||
val JMP: Byte = 3
|
||||
val JZ: Byte = 4
|
||||
val ADD: Byte = 5
|
||||
val SUB: Byte = 6
|
||||
val MUL: Byte = 7
|
||||
val DIV: Byte = 8
|
||||
val MOD: Byte = 9
|
||||
val LT: Byte = 10
|
||||
val GT: Byte = 11
|
||||
val LE: Byte = 12
|
||||
val GE: Byte = 13
|
||||
val EQ: Byte = 14
|
||||
val NE: Byte = 15
|
||||
val AND: Byte = 16
|
||||
val OR: Byte = 17
|
||||
val NEG: Byte = 18
|
||||
val NOT: Byte = 19
|
||||
val PRTC: Byte = 20
|
||||
val PRTI: Byte = 21
|
||||
val PRTS: Byte = 22
|
||||
val HALT: Byte = 23
|
||||
}
|
||||
|
||||
import Opcodes._
|
||||
|
||||
private val HEADER_REGEX = "Datasize: ([0-9]+) Strings: ([0-9]+)" r
|
||||
private val STRING_REGEX = "\"([^\"]*)\"" r
|
||||
private val PUSH_REGEX = " *[0-9]+ push +([0-9]+|'(?:[^'\\n]|\\\\n|\\\\\\\\)')" r
|
||||
private val PRTS_REGEX = " *[0-9]+ prts" r
|
||||
private val PRTI_REGEX = " *[0-9]+ prti" r
|
||||
private val PRTC_REGEX = " *[0-9]+ prtc" r
|
||||
private val HALT_REGEX = " *[0-9]+ halt" r
|
||||
private val STORE_REGEX = " *[0-9]+ store +\\[([0-9]+)\\]" r
|
||||
private val FETCH_REGEX = " *[0-9]+ fetch +\\[([0-9]+)\\]" r
|
||||
private val LT_REGEX = " *[0-9]+ lt" r
|
||||
private val GT_REGEX = " *[0-9]+ gt" r
|
||||
private val LE_REGEX = " *[0-9]+ le" r
|
||||
private val GE_REGEX = " *[0-9]+ ge" r
|
||||
private val NE_REGEX = " *[0-9]+ ne" r
|
||||
private val EQ_REGEX = " *[0-9]+ eq" r
|
||||
private val JZ_REGEX = " *[0-9]+ jz +\\((-?[0-9]+)\\) [0-9]+" r
|
||||
private val ADD_REGEX = " *[0-9]+ add" r
|
||||
private val SUB_REGEX = " *[0-9]+ sub" r
|
||||
private val MUL_REGEX = " *[0-9]+ mul" r
|
||||
private val DIV_REGEX = " *[0-9]+ div" r
|
||||
private val MOD_REGEX = " *[0-9]+ mod" r
|
||||
private val AND_REGEX = " *[0-9]+ and" r
|
||||
private val OR_REGEX = " *[0-9]+ or" r
|
||||
private val NOT_REGEX = " *[0-9]+ not" r
|
||||
private val NEG_REGEX = " *[0-9]+ neg" r
|
||||
private val JMP_REGEX = " *[0-9]+ jmp +\\((-?[0-9]+)\\) [0-9]+" r
|
||||
|
||||
def fromStdin = fromReader(Console.in)
|
||||
|
||||
def fromFile(file: String) = fromReader(new FileReader(file))
|
||||
|
||||
def fromString(src: String) = fromReader(new StringReader(src))
|
||||
|
||||
def fromReader(r: Reader) = {
|
||||
val in = new BufferedReader(r)
|
||||
val vm =
|
||||
in.readLine match {
|
||||
case HEADER_REGEX(datasize, stringsize) =>
|
||||
val strings =
|
||||
for (_ <- 1 to stringsize.toInt)
|
||||
yield
|
||||
in.readLine match {
|
||||
case STRING_REGEX(s) => unescape(s)
|
||||
case null => sys.error("expected string constant but encountered end of input")
|
||||
case s => sys.error(s"expected string constant: $s")
|
||||
}
|
||||
var line: String = null
|
||||
val code = new ArrayBuffer[Byte]
|
||||
|
||||
def addShort(a: Int) = {
|
||||
code += (a >> 8).toByte
|
||||
code += a.toByte
|
||||
}
|
||||
|
||||
def addInstIntOperand(opcode: Byte, operand: Int) = {
|
||||
code += opcode
|
||||
addShort(operand >> 16)
|
||||
addShort(operand)
|
||||
}
|
||||
|
||||
def addInst(opcode: Byte, operand: String) = addInstIntOperand(opcode, operand.toInt)
|
||||
|
||||
while ({ line = in.readLine; line ne null }) line match {
|
||||
case PUSH_REGEX(n) if n startsWith "'" =>
|
||||
addInstIntOperand(PUSH, unescape(n.substring(1, n.length - 1)).head)
|
||||
case PUSH_REGEX(n) => addInst(PUSH, n)
|
||||
case PRTS_REGEX() => code += PRTS
|
||||
case PRTI_REGEX() => code += PRTI
|
||||
case PRTC_REGEX() => code += PRTC
|
||||
case HALT_REGEX() => code += HALT
|
||||
case STORE_REGEX(idx) => addInst(STORE, idx)
|
||||
case FETCH_REGEX(idx) => addInst(FETCH, idx)
|
||||
case LT_REGEX() => code += LT
|
||||
case GT_REGEX() => code += GT
|
||||
case LE_REGEX() => code += LE
|
||||
case GE_REGEX() => code += GE
|
||||
case NE_REGEX() => code += NE
|
||||
case EQ_REGEX() => code += EQ
|
||||
case JZ_REGEX(disp) => addInst(JZ, disp)
|
||||
case ADD_REGEX() => code += ADD
|
||||
case SUB_REGEX() => code += SUB
|
||||
case MUL_REGEX() => code += MUL
|
||||
case DIV_REGEX() => code += DIV
|
||||
case MOD_REGEX() => code += MOD
|
||||
case AND_REGEX() => code += AND
|
||||
case OR_REGEX() => code += OR
|
||||
case NOT_REGEX() => code += NOT
|
||||
case NEG_REGEX() => code += NEG
|
||||
case JMP_REGEX(disp) => addInst(JMP, disp)
|
||||
}
|
||||
|
||||
new VirtualMachine(code, datasize.toInt, strings)
|
||||
case _ => sys.error("expected header")
|
||||
}
|
||||
|
||||
in.close
|
||||
vm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class VirtualMachine(code: IndexedSeq[Byte], datasize: Int, strings: IndexedSeq[String]) {
|
||||
|
||||
import VirtualMachine.Opcodes._
|
||||
|
||||
var pc = 0
|
||||
val stack = new mutable.ArrayStack[Int]
|
||||
val data = new Array[Int](datasize)
|
||||
var running = false
|
||||
|
||||
def getByte = {
|
||||
val byte = code(pc) & 0xFF
|
||||
|
||||
pc += 1
|
||||
byte
|
||||
}
|
||||
|
||||
def getShort = getByte << 8 | getByte
|
||||
|
||||
def getInt = getShort << 16 | getShort
|
||||
|
||||
def pushBoolean(b: Boolean) = stack push (if (b) 1 else 0)
|
||||
|
||||
def popBoolean = if (stack.pop != 0) true else false
|
||||
|
||||
def operator(f: (Int, Int) => Int) = {
|
||||
val y = stack.pop
|
||||
|
||||
stack.push(f(stack.pop, y))
|
||||
}
|
||||
|
||||
def relation(r: (Int, Int) => Boolean) = {
|
||||
val y = stack.pop
|
||||
|
||||
pushBoolean(r(stack.pop, y))
|
||||
}
|
||||
|
||||
def connective(c: (Boolean, Boolean) => Boolean) = pushBoolean(c(popBoolean, popBoolean))
|
||||
|
||||
def execute: Unit =
|
||||
getByte match {
|
||||
case FETCH => stack push data(getInt)
|
||||
case STORE => data(getInt) = stack.pop
|
||||
case PUSH => stack push getInt
|
||||
case JMP => pc = pc + getInt
|
||||
case JZ => if (stack.pop == 0) pc = pc + getInt else pc += 4
|
||||
case ADD => operator(_ + _)
|
||||
case SUB => operator(_ - _)
|
||||
case MUL => operator(_ * _)
|
||||
case DIV => operator(_ / _)
|
||||
case MOD => operator(_ % _)
|
||||
case LT => relation(_ < _)
|
||||
case GT => relation(_ > _)
|
||||
case LE => relation(_ <= _)
|
||||
case GE => relation(_ >= _)
|
||||
case EQ => relation(_ == _)
|
||||
case NE => relation(_ != _)
|
||||
case AND => connective(_ && _)
|
||||
case OR => connective(_ || _)
|
||||
case NEG => stack push -stack.pop
|
||||
case NOT => pushBoolean(!popBoolean)
|
||||
case PRTC => print(stack.pop.toChar)
|
||||
case PRTI => print(stack.pop)
|
||||
case PRTS => print(strings(stack.pop))
|
||||
case HALT => running = false
|
||||
}
|
||||
|
||||
def run = {
|
||||
pc = 0
|
||||
stack.clear
|
||||
running = true
|
||||
|
||||
for (i <- data.indices) data(i) = 0
|
||||
|
||||
while (running) execute
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package xyz.hyperreal
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
package object rosettacodeCompiler {
|
||||
|
||||
val escapes = "\\\\b|\\\\f|\\\\t|\\\\r|\\\\n|\\\\\\\\|\\\\\"" r
|
||||
|
||||
def unescape(s: String) =
|
||||
escapes.replaceAllIn(s, _.matched match {
|
||||
case "\\b" => "\b"
|
||||
case "\\f" => "\f"
|
||||
case "\\t" => "\t"
|
||||
case "\\r" => "\r"
|
||||
case "\\n" => "\n"
|
||||
case "\\\\" => "\\"
|
||||
case "\\\"" => "\""
|
||||
})
|
||||
|
||||
def capture(thunk: => Unit) = {
|
||||
val buf = new ByteArrayOutputStream
|
||||
|
||||
Console.withOut(buf)(thunk)
|
||||
buf.toString
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
(import (scheme base)
|
||||
(scheme char)
|
||||
(scheme file)
|
||||
(scheme process-context)
|
||||
(scheme write)
|
||||
(only (srfi 13) string-contains string-delete string-filter
|
||||
string-replace string-tokenize))
|
||||
|
||||
(define *word-size* 4)
|
||||
|
||||
;; Mappings from operation symbols to internal procedures.
|
||||
;; We define operations appropriate to virtual machine:
|
||||
;; e.g. division must return an int, not a rational
|
||||
;; boolean values are treated as numbers: 0 is false, other is true
|
||||
(define *unary-ops*
|
||||
(list (cons 'neg (lambda (a) (- a)))
|
||||
(cons 'not (lambda (a) (if (zero? a) 1 0)))))
|
||||
(define *binary-ops*
|
||||
(let ((number-comp (lambda (op) (lambda (a b) (if (op a b) 1 0)))))
|
||||
(list (cons 'add +)
|
||||
(cons 'sub -)
|
||||
(cons 'mul *)
|
||||
(cons 'div (lambda (a b) (truncate (/ a b)))) ; int division
|
||||
(cons 'mod modulo)
|
||||
(cons 'lt (number-comp <))
|
||||
(cons 'gt (number-comp >))
|
||||
(cons 'le (number-comp <=))
|
||||
(cons 'ge (number-comp >=))
|
||||
(cons 'eq (lambda (a b) (if (= a b) 1 0)))
|
||||
(cons 'ne (lambda (a b) (if (= a b) 0 1)))
|
||||
(cons 'and (lambda (a b) ; make "and" work on numbers
|
||||
(if (and (not (zero? a)) (not (zero? b))) 1 0)))
|
||||
(cons 'or (lambda (a b) ; make "or" work on numbers
|
||||
(if (or (not (zero? a)) (not (zero? b))) 1 0))))))
|
||||
|
||||
;; read information from file, returning vectors for data and strings
|
||||
;; and a list of the code instructions
|
||||
(define (read-code filename)
|
||||
(define (setup-definitions str)
|
||||
(values ; return vectors for (data strings) of required size
|
||||
(make-vector (string->number (list-ref str 1)) #f)
|
||||
(make-vector (string->number (list-ref str 3)) #f)))
|
||||
(define (read-strings strings) ; read constant strings into data structure
|
||||
(define (replace-newlines chars) ; replace newlines, obeying \\n
|
||||
(cond ((< (length chars) 2) ; finished list
|
||||
chars)
|
||||
((and (>= (length chars) 3) ; preserve \\n
|
||||
(char=? #\\ (car chars))
|
||||
(char=? #\\ (cadr chars))
|
||||
(char=? #\n (cadr (cdr chars))))
|
||||
(cons (car chars)
|
||||
(cons (cadr chars)
|
||||
(cons (cadr (cdr chars))
|
||||
(replace-newlines (cdr (cdr (cdr chars))))))))
|
||||
((and (char=? #\\ (car chars)) ; replace \n with newline
|
||||
(char=? #\n (cadr chars)))
|
||||
(cons #\newline (replace-newlines (cdr (cdr chars)))))
|
||||
(else ; keep char and look further
|
||||
(cons (car chars) (replace-newlines (cdr chars))))))
|
||||
(define (tidy-string str) ; remove quotes, map newlines to actual newlines
|
||||
(list->string
|
||||
(replace-newlines
|
||||
(string->list
|
||||
(string-delete #\" str))))) ; " (needed to satisfy rosettacode's scheme syntax highlighter)
|
||||
;
|
||||
(do ((i 0 (+ i 1)))
|
||||
((= i (vector-length strings)) )
|
||||
(vector-set! strings i (tidy-string (read-line)))))
|
||||
(define (read-code)
|
||||
(define (cleanup-code opn) ; tidy instructions, parsing numbers
|
||||
(let ((addr (string->number (car opn)))
|
||||
(instr (string->symbol (cadr opn))))
|
||||
(cond ((= 2 (length opn))
|
||||
(list addr instr))
|
||||
((= 3 (length opn))
|
||||
(list addr
|
||||
instr
|
||||
(string->number
|
||||
(string-filter char-numeric? (list-ref opn 2)))))
|
||||
(else ; assume length 4, jump instructions
|
||||
(list addr instr (string->number (list-ref opn 3)))))))
|
||||
;
|
||||
(let loop ((result '()))
|
||||
(let ((line (read-line)))
|
||||
(if (eof-object? line)
|
||||
(reverse (map cleanup-code result))
|
||||
(loop (cons (string-tokenize line) result))))))
|
||||
;
|
||||
(with-input-from-file
|
||||
filename
|
||||
(lambda ()
|
||||
(let-values (((data strings)
|
||||
(setup-definitions (string-tokenize (read-line)))))
|
||||
(read-strings strings)
|
||||
(values data
|
||||
strings
|
||||
(read-code))))))
|
||||
|
||||
;; run the virtual machine
|
||||
(define (run-program data strings code)
|
||||
(define (get-instruction n)
|
||||
(if (assq n code)
|
||||
(cdr (assq n code))
|
||||
(error "Could not find instruction")))
|
||||
;
|
||||
(let loop ((stack '())
|
||||
(pc 0))
|
||||
(let ((op (get-instruction pc)))
|
||||
(case (car op)
|
||||
((fetch)
|
||||
(loop (cons (vector-ref data (cadr op)) stack)
|
||||
(+ pc 1 *word-size*)))
|
||||
((store)
|
||||
(vector-set! data (cadr op) (car stack))
|
||||
(loop (cdr stack)
|
||||
(+ pc 1 *word-size*)))
|
||||
((push)
|
||||
(loop (cons (cadr op) stack)
|
||||
(+ pc 1 *word-size*)))
|
||||
((add sub mul div mod lt gt le eq ne and or)
|
||||
(let ((instr (assq (car op) *binary-ops*)))
|
||||
(if instr
|
||||
(loop (cons ((cdr instr) (cadr stack) ; replace top two with result
|
||||
(car stack))
|
||||
(cdr (cdr stack)))
|
||||
(+ pc 1))
|
||||
(error "Unknown binary operation"))))
|
||||
((neg not)
|
||||
(let ((instr (assq (car op) *unary-ops*)))
|
||||
(if instr
|
||||
(loop (cons ((cdr instr) (car stack)) ; replace top with result
|
||||
(cdr stack))
|
||||
(+ pc 1))
|
||||
(error "Unknown unary operation"))))
|
||||
((jmp)
|
||||
(loop stack
|
||||
(cadr op)))
|
||||
((jz)
|
||||
(loop (cdr stack)
|
||||
(if (zero? (car stack))
|
||||
(cadr op)
|
||||
(+ pc 1 *word-size*))))
|
||||
((prtc)
|
||||
(display (integer->char (car stack)))
|
||||
(loop (cdr stack)
|
||||
(+ pc 1)))
|
||||
((prti)
|
||||
(display (car stack))
|
||||
(loop (cdr stack)
|
||||
(+ pc 1)))
|
||||
((prts)
|
||||
(display (vector-ref strings (car stack)))
|
||||
(loop (cdr stack)
|
||||
(+ pc 1)))
|
||||
((halt)
|
||||
#t)))))
|
||||
|
||||
;; create and run virtual machine from filename passed on command line
|
||||
(if (= 2 (length (command-line)))
|
||||
(let-values (((data strings code) (read-code (cadr (command-line)))))
|
||||
(run-program data strings code))
|
||||
(display "Error: pass a .asm filename\n"))
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
import "/dynamic" for Enum
|
||||
import "/crypto" for Bytes
|
||||
import "/fmt" for Conv
|
||||
import "/ioutil" for FileUtil
|
||||
|
||||
var codes = [
|
||||
"fetch",
|
||||
"store",
|
||||
"push",
|
||||
"add",
|
||||
"sub",
|
||||
"mul",
|
||||
"div",
|
||||
"mod",
|
||||
"lt",
|
||||
"gt",
|
||||
"le",
|
||||
"ge",
|
||||
"eq",
|
||||
"ne",
|
||||
"and",
|
||||
"or",
|
||||
"neg",
|
||||
"not",
|
||||
"jmp",
|
||||
"jz",
|
||||
"prtc",
|
||||
"prts",
|
||||
"prti",
|
||||
"halt"
|
||||
]
|
||||
|
||||
var Code = Enum.create("Code", codes)
|
||||
|
||||
var codeMap = {
|
||||
"fetch": Code.fetch,
|
||||
"store": Code.store,
|
||||
"push": Code.push,
|
||||
"add": Code.add,
|
||||
"sub": Code.sub,
|
||||
"mul": Code.mul,
|
||||
"div": Code.div,
|
||||
"mod": Code.mod,
|
||||
"lt": Code.lt,
|
||||
"gt": Code.gt,
|
||||
"le": Code.le,
|
||||
"ge": Code.ge,
|
||||
"eq": Code.eq,
|
||||
"ne": Code.ne,
|
||||
"and": Code.and,
|
||||
"or": Code.or,
|
||||
"neg": Code.neg,
|
||||
"not": Code.not,
|
||||
"jmp": Code.jmp,
|
||||
"jz": Code.jz,
|
||||
"prtc": Code.prtc,
|
||||
"prts": Code.prts,
|
||||
"prti": Code.prti,
|
||||
"halt": Code.halt
|
||||
}
|
||||
|
||||
var object = []
|
||||
var stringPool = []
|
||||
|
||||
var reportError = Fn.new { |msg| Fiber.abort("error : %(msg)") }
|
||||
|
||||
var emitByte = Fn.new { |c| object.add(c) }
|
||||
|
||||
var emitWord = Fn.new { |n|
|
||||
var bs = Bytes.fromIntLE(n)
|
||||
for (b in bs) emitByte.call(b)
|
||||
}
|
||||
|
||||
// Converts the 4 bytes starting at object[pc] to an unsigned 32 bit integer
|
||||
// and thence to a signed 32 bit integer
|
||||
var toInt32LE = Fn.new { |pc|
|
||||
var x = Bytes.toIntLE(object[pc...pc+4])
|
||||
if (x >= 2.pow(31)) x = x - 2.pow(32)
|
||||
return x
|
||||
}
|
||||
|
||||
/* Virtual Machine interpreter */
|
||||
|
||||
var runVM = Fn.new { |dataSize|
|
||||
var stack = List.filled(dataSize + 1, 0)
|
||||
var pc = 0
|
||||
while (true) {
|
||||
var op = object[pc]
|
||||
pc = pc + 1
|
||||
if (op == Code.fetch) {
|
||||
var x = toInt32LE.call(pc)
|
||||
stack.add(stack[x])
|
||||
pc = pc + 4
|
||||
} else if (op == Code.store) {
|
||||
var x = toInt32LE.call(pc)
|
||||
var ln = stack.count
|
||||
stack[x] = stack[ln-1]
|
||||
stack = stack[0...ln-1]
|
||||
pc = pc + 4
|
||||
} else if (op == Code.push) {
|
||||
var x = toInt32LE.call(pc)
|
||||
stack.add(x)
|
||||
pc = pc + 4
|
||||
} else if (op == Code.add) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = stack[ln-2] + stack[ln-1]
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.sub) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = stack[ln-2] - stack[ln-1]
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.mul) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = stack[ln-2] * stack[ln-1]
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.div) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = (stack[ln-2] / stack[ln-1]).truncate
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.mod) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = stack[ln-2] % stack[ln-1]
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.lt) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(stack[ln-2] < stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.gt) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(stack[ln-2] > stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.le) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(stack[ln-2] <= stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.ge) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(stack[ln-2] >= stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.eq) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(stack[ln-2] == stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.ne) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(stack[ln-2] != stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.and) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(Conv.itob(stack[ln-2]) && Conv.itob(stack[ln-1]))
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.or) {
|
||||
var ln = stack.count
|
||||
stack[ln-2] = Conv.btoi(Conv.itob(stack[ln-2]) || Conv.itob(stack[ln-1]))
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.neg) {
|
||||
var ln = stack.count
|
||||
stack[ln-1] = -stack[ln-1]
|
||||
} else if (op == Code.not) {
|
||||
var ln = stack.count
|
||||
stack[ln-1] = Conv.btoi(!Conv.itob(stack[ln-1]))
|
||||
} else if (op == Code.jmp) {
|
||||
var x = toInt32LE.call(pc)
|
||||
pc = pc + x
|
||||
} else if (op == Code.jz) {
|
||||
var ln = stack.count
|
||||
var v = stack[ln-1]
|
||||
stack = stack[0...ln-1]
|
||||
if (v != 0) {
|
||||
pc = pc + 4
|
||||
} else {
|
||||
var x = toInt32LE.call(pc)
|
||||
pc = pc + x
|
||||
}
|
||||
} else if (op == Code.prtc) {
|
||||
var ln = stack.count
|
||||
System.write(String.fromByte(stack[ln-1]))
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.prts) {
|
||||
var ln = stack.count
|
||||
System.write(stringPool[stack[ln-1]])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.prti) {
|
||||
var ln = stack.count
|
||||
System.write(stack[ln-1])
|
||||
stack = stack[0...ln-1]
|
||||
} else if (op == Code.halt) {
|
||||
return
|
||||
} else {
|
||||
reportError.call("Unknown opcode %(op)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var translate = Fn.new { |s|
|
||||
var d = ""
|
||||
var i = 0
|
||||
while (i < s.count) {
|
||||
if (s[i] == "\\" && (i+1) < s.count) {
|
||||
if (s[i+1] == "n") {
|
||||
d = d + "\n"
|
||||
i = i + 1
|
||||
} else if (s[i+1] == "\\") {
|
||||
d = d + "\\"
|
||||
i = i + 1
|
||||
}
|
||||
} else {
|
||||
d = d + s[i]
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
var lines = []
|
||||
var lineCount = 0
|
||||
var lineNum = 0
|
||||
|
||||
var loadCode = Fn.new {
|
||||
var dataSize
|
||||
var firstLine = true
|
||||
while (lineNum < lineCount) {
|
||||
var line = lines[lineNum].trimEnd(" \t")
|
||||
lineNum = lineNum + 1
|
||||
if (line.count == 0) {
|
||||
if (firstLine) {
|
||||
reportError.call("empty line")
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
var lineList = line.split(" ").where { |s| s != "" }.toList
|
||||
if (firstLine) {
|
||||
dataSize = Num.fromString(lineList[1])
|
||||
var nStrings = Num.fromString(lineList[3])
|
||||
for (i in 0...nStrings) {
|
||||
var s = lines[lineNum].trim("\"\n")
|
||||
lineNum = lineNum + 1
|
||||
stringPool.add(translate.call(s))
|
||||
}
|
||||
firstLine = false
|
||||
continue
|
||||
}
|
||||
var offset = Num.fromString(lineList[0])
|
||||
var instr = lineList[1]
|
||||
var opCode = codeMap[instr]
|
||||
if (!opCode) {
|
||||
reportError.call("Unknown instruction %(instr) at %(opCode)")
|
||||
}
|
||||
emitByte.call(opCode)
|
||||
if (opCode == Code.jmp || opCode == Code.jz) {
|
||||
var p = Num.fromString(lineList[3])
|
||||
emitWord.call(p - offset - 1)
|
||||
} else if (opCode == Code.push) {
|
||||
var value = Num.fromString(lineList[2])
|
||||
emitWord.call(value)
|
||||
} else if (opCode == Code.fetch || opCode == Code.store) {
|
||||
var value = Num.fromString(lineList[2].trim("[]"))
|
||||
emitWord.call(value)
|
||||
}
|
||||
}
|
||||
return dataSize
|
||||
}
|
||||
|
||||
lines = FileUtil.readLines("codegen.txt")
|
||||
lineCount = lines.count
|
||||
runVM.call(loadCode.call())
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const VirtualMachineError = error{OutOfMemory};
|
||||
|
||||
pub const VirtualMachine = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
stack: [stack_size]i32,
|
||||
program: std.ArrayList(u8),
|
||||
sp: usize, // stack pointer
|
||||
pc: usize, // program counter
|
||||
string_pool: std.ArrayList([]const u8), // all the strings in the program
|
||||
globals: std.ArrayList(i32), // all the variables in the program, they are global
|
||||
output: std.ArrayList(u8), // Instead of outputting to stdout, we do it here for better testing.
|
||||
|
||||
const Self = @This();
|
||||
const stack_size = 32; // Can be arbitrarily increased/decreased as long as we have enough.
|
||||
const word_size = @sizeOf(i32);
|
||||
|
||||
pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
program: std.ArrayList(u8),
|
||||
string_pool: std.ArrayList([]const u8),
|
||||
globals: std.ArrayList(i32),
|
||||
) Self {
|
||||
return VirtualMachine{
|
||||
.allocator = allocator,
|
||||
.stack = [_]i32{std.math.maxInt(i32)} ** stack_size,
|
||||
.program = program,
|
||||
.sp = 0,
|
||||
.pc = 0,
|
||||
.string_pool = string_pool,
|
||||
.globals = globals,
|
||||
.output = std.ArrayList(u8).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn interp(self: *Self) VirtualMachineError!void {
|
||||
while (true) : (self.pc += 1) {
|
||||
switch (@intToEnum(Op, self.program.items[self.pc])) {
|
||||
.push => self.push(self.unpackInt()),
|
||||
.store => self.globals.items[@intCast(usize, self.unpackInt())] = self.pop(),
|
||||
.fetch => self.push(self.globals.items[@intCast(usize, self.unpackInt())]),
|
||||
.jmp => self.pc = @intCast(usize, self.unpackInt() - 1),
|
||||
.jz => {
|
||||
if (self.pop() == 0) {
|
||||
// -1 because `while` increases it with every iteration.
|
||||
// This doesn't allow to jump to location 0 because we use `usize` for `pc`,
|
||||
// just arbitrary implementation limitation.
|
||||
self.pc = @intCast(usize, self.unpackInt() - 1);
|
||||
} else {
|
||||
self.pc += word_size;
|
||||
}
|
||||
},
|
||||
.prts => try self.out("{s}", .{self.string_pool.items[@intCast(usize, self.pop())]}),
|
||||
.prti => try self.out("{d}", .{self.pop()}),
|
||||
.prtc => try self.out("{c}", .{@intCast(u8, self.pop())}),
|
||||
.lt => self.binOp(lt),
|
||||
.le => self.binOp(le),
|
||||
.gt => self.binOp(gt),
|
||||
.ge => self.binOp(ge),
|
||||
.eq => self.binOp(eq),
|
||||
.ne => self.binOp(ne),
|
||||
.add => self.binOp(add),
|
||||
.mul => self.binOp(mul),
|
||||
.sub => self.binOp(sub),
|
||||
.div => self.binOp(div),
|
||||
.mod => self.binOp(mod),
|
||||
.@"and" => self.binOp(@"and"),
|
||||
.@"or" => self.binOp(@"or"),
|
||||
.not => self.push(@boolToInt(self.pop() == 0)),
|
||||
.neg => self.push(-self.pop()),
|
||||
.halt => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push(self: *Self, n: i32) void {
|
||||
self.sp += 1;
|
||||
self.stack[self.sp] = n;
|
||||
}
|
||||
|
||||
fn pop(self: *Self) i32 {
|
||||
std.debug.assert(self.sp != 0);
|
||||
self.sp -= 1;
|
||||
return self.stack[self.sp + 1];
|
||||
}
|
||||
|
||||
fn unpackInt(self: *Self) i32 {
|
||||
const arg_ptr = @ptrCast(*[4]u8, self.program.items[self.pc + 1 .. self.pc + 1 + word_size]);
|
||||
self.pc += word_size;
|
||||
var arg_array = arg_ptr.*;
|
||||
const arg = @ptrCast(*i32, @alignCast(@alignOf(i32), &arg_array));
|
||||
return arg.*;
|
||||
}
|
||||
|
||||
pub fn out(self: *Self, comptime format: []const u8, args: anytype) VirtualMachineError!void {
|
||||
try self.output.writer().print(format, args);
|
||||
}
|
||||
|
||||
fn binOp(self: *Self, func: fn (a: i32, b: i32) i32) void {
|
||||
const a = self.pop();
|
||||
const b = self.pop();
|
||||
// Note that arguments are in reversed order because this is how we interact with
|
||||
// push/pop operations of the stack.
|
||||
const result = func(b, a);
|
||||
self.push(result);
|
||||
}
|
||||
|
||||
fn lt(a: i32, b: i32) i32 {
|
||||
return @boolToInt(a < b);
|
||||
}
|
||||
fn le(a: i32, b: i32) i32 {
|
||||
return @boolToInt(a <= b);
|
||||
}
|
||||
fn gt(a: i32, b: i32) i32 {
|
||||
return @boolToInt(a > b);
|
||||
}
|
||||
fn ge(a: i32, b: i32) i32 {
|
||||
return @boolToInt(a >= b);
|
||||
}
|
||||
fn eq(a: i32, b: i32) i32 {
|
||||
return @boolToInt(a == b);
|
||||
}
|
||||
fn ne(a: i32, b: i32) i32 {
|
||||
return @boolToInt(a != b);
|
||||
}
|
||||
fn add(a: i32, b: i32) i32 {
|
||||
return a + b;
|
||||
}
|
||||
fn sub(a: i32, b: i32) i32 {
|
||||
return a - b;
|
||||
}
|
||||
fn mul(a: i32, b: i32) i32 {
|
||||
return a * b;
|
||||
}
|
||||
fn div(a: i32, b: i32) i32 {
|
||||
return @divTrunc(a, b);
|
||||
}
|
||||
fn mod(a: i32, b: i32) i32 {
|
||||
return @mod(a, b);
|
||||
}
|
||||
fn @"or"(a: i32, b: i32) i32 {
|
||||
return @boolToInt((a != 0) or (b != 0));
|
||||
}
|
||||
fn @"and"(a: i32, b: i32) i32 {
|
||||
return @boolToInt((a != 0) and (b != 0));
|
||||
}
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
var arg_it = std.process.args();
|
||||
_ = try arg_it.next(allocator) orelse unreachable; // program name
|
||||
const file_name = arg_it.next(allocator);
|
||||
// We accept both files and standard input.
|
||||
var file_handle = blk: {
|
||||
if (file_name) |file_name_delimited| {
|
||||
const fname: []const u8 = try file_name_delimited;
|
||||
break :blk try std.fs.cwd().openFile(fname, .{});
|
||||
} else {
|
||||
break :blk std.io.getStdIn();
|
||||
}
|
||||
};
|
||||
defer file_handle.close();
|
||||
const input_content = try file_handle.readToEndAlloc(allocator, std.math.maxInt(usize));
|
||||
|
||||
var string_pool = std.ArrayList([]const u8).init(allocator);
|
||||
var globals = std.ArrayList(i32).init(allocator);
|
||||
const bytecode = try loadBytecode(allocator, input_content, &string_pool, &globals);
|
||||
var vm = VirtualMachine.init(allocator, bytecode, string_pool, globals);
|
||||
try vm.interp();
|
||||
const result: []const u8 = vm.output.items;
|
||||
_ = try std.io.getStdOut().write(result);
|
||||
}
|
||||
|
||||
pub const Op = enum(u8) {
|
||||
fetch,
|
||||
store,
|
||||
push,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
mod,
|
||||
lt,
|
||||
gt,
|
||||
le,
|
||||
ge,
|
||||
eq,
|
||||
ne,
|
||||
@"and",
|
||||
@"or",
|
||||
neg,
|
||||
not,
|
||||
jmp,
|
||||
jz,
|
||||
prtc,
|
||||
prts,
|
||||
prti,
|
||||
halt,
|
||||
|
||||
const from_string = std.ComptimeStringMap(Op, .{
|
||||
.{ "fetch", .fetch },
|
||||
.{ "store", .store },
|
||||
.{ "push", .push },
|
||||
.{ "add", .add },
|
||||
.{ "sub", .sub },
|
||||
.{ "mul", .mul },
|
||||
.{ "div", .div },
|
||||
.{ "mod", .mod },
|
||||
.{ "lt", .lt },
|
||||
.{ "gt", .gt },
|
||||
.{ "le", .le },
|
||||
.{ "ge", .ge },
|
||||
.{ "eq", .eq },
|
||||
.{ "ne", .ne },
|
||||
.{ "and", .@"and" },
|
||||
.{ "or", .@"or" },
|
||||
.{ "neg", .neg },
|
||||
.{ "not", .not },
|
||||
.{ "jmp", .jmp },
|
||||
.{ "jz", .jz },
|
||||
.{ "prtc", .prtc },
|
||||
.{ "prts", .prts },
|
||||
.{ "prti", .prti },
|
||||
.{ "halt", .halt },
|
||||
});
|
||||
|
||||
pub fn fromString(str: []const u8) Op {
|
||||
return from_string.get(str).?;
|
||||
}
|
||||
};
|
||||
|
||||
// 100 lines of code to load serialized bytecode, eh
|
||||
fn loadBytecode(
|
||||
allocator: std.mem.Allocator,
|
||||
str: []const u8,
|
||||
string_pool: *std.ArrayList([]const u8),
|
||||
globals: *std.ArrayList(i32),
|
||||
) !std.ArrayList(u8) {
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
var line_it = std.mem.split(u8, str, "\n");
|
||||
while (line_it.next()) |line| {
|
||||
if (std.mem.indexOf(u8, line, "halt")) |_| {
|
||||
var tok_it = std.mem.tokenize(u8, line, " ");
|
||||
const size = try std.fmt.parseInt(usize, tok_it.next().?, 10);
|
||||
try result.resize(size + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
line_it.index = 0;
|
||||
const first_line = line_it.next().?;
|
||||
const strings_index = std.mem.indexOf(u8, first_line, " Strings: ").?;
|
||||
const globals_size = try std.fmt.parseInt(usize, first_line["Datasize: ".len..strings_index], 10);
|
||||
const string_pool_size = try std.fmt.parseInt(usize, first_line[strings_index + " Strings: ".len ..], 10);
|
||||
try globals.resize(globals_size);
|
||||
try string_pool.ensureTotalCapacity(string_pool_size);
|
||||
var string_cnt: usize = 0;
|
||||
while (string_cnt < string_pool_size) : (string_cnt += 1) {
|
||||
const line = line_it.next().?;
|
||||
var program_string = try std.ArrayList(u8).initCapacity(allocator, line.len);
|
||||
var escaped = false;
|
||||
// Skip double quotes
|
||||
for (line[1 .. line.len - 1]) |ch| {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
switch (ch) {
|
||||
'\\' => try program_string.append('\\'),
|
||||
'n' => try program_string.append('\n'),
|
||||
else => {
|
||||
std.debug.print("unknown escape sequence: {c}\n", .{ch});
|
||||
std.os.exit(1);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
switch (ch) {
|
||||
'\\' => escaped = true,
|
||||
else => try program_string.append(ch),
|
||||
}
|
||||
}
|
||||
}
|
||||
try string_pool.append(program_string.items);
|
||||
}
|
||||
while (line_it.next()) |line| {
|
||||
if (line.len == 0) break;
|
||||
|
||||
var tok_it = std.mem.tokenize(u8, line, " ");
|
||||
const address = try std.fmt.parseInt(usize, tok_it.next().?, 10);
|
||||
const op = Op.fromString(tok_it.next().?);
|
||||
result.items[address] = @enumToInt(op);
|
||||
switch (op) {
|
||||
.fetch, .store => {
|
||||
const index_bracketed = tok_it.rest();
|
||||
const index = try std.fmt.parseInt(i32, index_bracketed[1 .. index_bracketed.len - 1], 10);
|
||||
insertInt(&result, address + 1, index);
|
||||
},
|
||||
.push => {
|
||||
insertInt(&result, address + 1, try std.fmt.parseInt(i32, tok_it.rest(), 10));
|
||||
},
|
||||
.jmp, .jz => {
|
||||
_ = tok_it.next();
|
||||
insertInt(&result, address + 1, try std.fmt.parseInt(i32, tok_it.rest(), 10));
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn insertInt(array: *std.ArrayList(u8), address: usize, n: i32) void {
|
||||
const word_size = @sizeOf(i32);
|
||||
var i: usize = 0;
|
||||
var n_var = n;
|
||||
var n_bytes = @ptrCast(*[4]u8, &n_var);
|
||||
while (i < word_size) : (i += 1) {
|
||||
array.items[@intCast(usize, address + i)] = n_bytes[@intCast(usize, i)];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// This is a little endian machine
|
||||
const WORD_SIZE=4;
|
||||
const{ var _n=-1; var[proxy]N=fcn{ _n+=1 } } // enumerator
|
||||
const FETCH=N, STORE=N, PUSH=N, ADD=N, SUB=N, MUL=N, DIV=N, MOD=N,
|
||||
LT=N, GT=N, LE=N, GE=N, EQ=N, NE=N, AND=N, OR=N, NEG=N, NOT=N,
|
||||
JMP=N, JZ=N, PRTC=N, PRTS=N, PRTI=N, HALT=N;
|
||||
|
||||
var [const]
|
||||
bops=Dictionary(ADD,'+, SUB,'-, MUL,'*, DIV,'/, MOD,'%,
|
||||
LT,'<, GT,'>, LE,'<=, GE,'>=, NE,'!=, EQ,'==, NE,'!=),
|
||||
strings=List(); // filled in by the loader
|
||||
;
|
||||
|
||||
// do a binary op
|
||||
fcn bop(stack,op){ a,b:=stack.pop(),stack.pop(); stack.append(bops[op](b,a)) }
|
||||
|
||||
fcn run_vm(code,stackSz){
|
||||
stack,pc := List.createLong(stackSz,0), 0;
|
||||
while(True){
|
||||
op:=code[pc]; pc+=1;
|
||||
switch(op){
|
||||
case(FETCH){
|
||||
stack.append(stack[code.toLittleEndian(pc,WORD_SIZE,False)]);
|
||||
pc+=WORD_SIZE;
|
||||
}
|
||||
case(STORE){
|
||||
stack[code.toLittleEndian(pc,WORD_SIZE)]=stack.pop();
|
||||
pc+=WORD_SIZE;
|
||||
}
|
||||
case(PUSH){
|
||||
stack.append(code.toLittleEndian(pc,WORD_SIZE,False)); // signed
|
||||
pc+=WORD_SIZE;
|
||||
}
|
||||
case(ADD,SUB,MUL,DIV,MOD,LT,GT,LE,GE,EQ,NE) { bop(stack,op) }
|
||||
case(AND){ stack[-2] = stack[-2] and stack[-1]; stack.pop() }
|
||||
case(OR) { stack[-2] = stack[-2] or stack[-1]; stack.pop() }
|
||||
case(NEG){ stack[-1] = -stack[-1] }
|
||||
case(NOT){ stack[-1] = not stack[-1] }
|
||||
case(JMP){ pc+=code.toLittleEndian(pc,WORD_SIZE,False); } // signed
|
||||
case(JZ) {
|
||||
if(stack.pop()) pc+=WORD_SIZE;
|
||||
else pc+=code.toLittleEndian(pc,WORD_SIZE,False);
|
||||
}
|
||||
case(PRTC){ } // not implemented
|
||||
case(PRTS){ print(strings[stack.pop()]) }
|
||||
case(PRTI){ print(stack.pop()) }
|
||||
case(HALT){ break }
|
||||
else{ throw(Exception.AssertionError(
|
||||
"Bad op code (%d) @%d".fmt(op,pc-1))) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code:=File(vm.nthArg(0)).read(); // binary code file
|
||||
// the string table is prepended to the code:
|
||||
// 66,1 byte len,text, no trailing '\0' needed
|
||||
while(code[0]==66){ // read the string table
|
||||
sz:=code[1];
|
||||
strings.append(code[2,sz].text);
|
||||
code.del(0,sz+2);
|
||||
}
|
||||
run_vm(code,1000);
|
||||
Loading…
Add table
Add a link
Reference in a new issue