%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% 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: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%