let /* Booleans */ type bool = int var true := 1 var false := 0 /* Settings */ var IS_NIL_TRUTHY : bool := false var HAS_NIL_SYMBOL : bool := false var DEBUG_PRINT_STACK : bool := false /* Basic utility */ function concatm ( s1: string , s2: string , s3: string , s4: string , s5: string ): string = concat(s1, concat(s2, concat(s3, concat(s4, s5)))) function string_to_unsigned_int (str: string): int = let var value := 0 in for i := 0 to size(str) - 1 do value := value * 10 + (ord(substring(str, i, 1))-ord("0")) ; value end function string_to_int (str: string): int = if size(str) > 1 & substring(str, 0, 1) = "-" then - string_to_unsigned_int(substring(str, 1, size(str)-1)) else string_to_unsigned_int(str) function i2s (i: int): string = if i = 0 /* TAKEN FROM FREKSEN LISP. TODO REPLACE */ then "0" else if i < 0 then concat("-", i2s(-i)) else if i < 10 then chr(ord("0") + i) else concat(i2s(i / 10), chr(ord("0") + i - (i / 10) * 10)) function int_to_string (i: int): string = i2s(i) function is_integer_string (s: string): int = s = int_to_string(string_to_int(s)) function max (a: int, b: int): int = if a > b then a else b function min (a: int, b: int): int = if a < b then a else b function safe_substring (str: string, i_start: int, i_end: int): string = ( i_start := max(0, i_start) ; i_end := min(size(str) - 1, i_end) ; substring(str, i_start, i_end - i_start + 1) ) /* Scheme value system */ type type_type = int type scheme_value = { typ : type_type , val_i : int , val_s : string , val_e : scheme_environment , val_car : scheme_value , val_cdr : scheme_value , pos_l: int , pos_r: int } type vm_env_key = string type vm_env_elem = scheme_value type vm_env = { key: vm_env_key, value: vm_env_elem, next: vm_env } type scheme_environment = vm_env var type_integer := 0 var type_string := 1 var type_symbol := 2 var type_closure := 3 var type_nil := 4 var type_false := 5 var type_true := 6 var type_pair := 7 function value_to_string (base_v: scheme_value): string = let function val_rec (v : scheme_value , req_paren : bool): string = if v = nil then ( print("Error in value_to_string: Got nil value.\n") ; "" ) else if v.typ = type_integer then int_to_string(v.val_i) else if v.typ = type_string then concat("\"", concat(v.val_s, "\"")) else if v.typ = type_symbol then v.val_s else if v.typ = type_closure then concat("")) else if v.typ = type_nil then "()" else if v.typ = type_false then "#f" else if v.typ = type_true then "#t" else if v.typ = type_pair & v.val_cdr.typ = type_nil then let in concatm ( if req_paren then "(" else "" , val_rec(v.val_car, true) , if req_paren then ")" else "" , "" , "" ) end else if v.typ = type_pair then let var is_real := v.val_cdr.typ = type_pair in concatm ( if req_paren then "(" else "" , val_rec(v.val_car, true) , if is_real then " " else " . " , val_rec(v.val_cdr, not(is_real)) , if req_paren then ")" else "" ) end else ( print("Error in value_to_string: Unknown type.\n") ; "" ) in val_rec(base_v, true) end function is_truthy (e: scheme_value): bool = e <> nil & e.typ <> type_false & (IS_NIL_TRUTHY | e.typ <> type_nil) function is_integer (e: scheme_value): bool = e <> nil & e.typ = type_integer function is_string (e: scheme_value): bool = e <> nil &e.typ = type_string function is_function (e: scheme_value): bool = e <> nil &e.typ = type_closure function is_pair (e: scheme_value): bool = e <> nil &e.typ = type_pair function is_symbol (e: scheme_value): bool = e <> nil &e.typ = type_symbol function int_val (i: int): scheme_value = scheme_value { typ = type_integer , val_i = i , val_s = "" , val_e = nil , val_car = nil , val_cdr = nil , pos_l = -1 , pos_r = -1 } function str_val (s: string): scheme_value = scheme_value { typ = type_string , val_i = 0 , val_s = s , val_e = nil , val_car = nil , val_cdr = nil , pos_l = -1 , pos_r = -1 } function fun_val (i: int, env: scheme_environment): scheme_value = scheme_value { typ = type_closure , val_i = i , val_s = "" , val_e = env , val_car = nil , val_cdr = nil , pos_l = -1 , pos_r = -1 } function bool_val (i: int): scheme_value = scheme_value { typ = if (i <> 0) then type_true else type_false , val_i = i <> 0 , val_s = "" , val_e = nil , val_car = nil , val_cdr = nil , pos_l = -1 , pos_r = -1 } function pair_val (car: scheme_value, cdr: scheme_value): scheme_value = scheme_value { typ = type_pair , val_i = 0 , val_s = "" , val_e = nil , val_car = car , val_cdr = cdr , pos_l = -1 , pos_r = -1 } function sym_val (sym: string): scheme_value = scheme_value { typ = type_symbol , val_i = 0 , val_s = sym , val_e = nil , val_car = nil , val_cdr = nil , pos_l = -1 , pos_r = -1 } function nil_val (): scheme_value = scheme_value { typ = type_nil , val_i = 0 , val_s = "" , val_e = nil , val_car = nil , val_cdr = nil , pos_l = -1 , pos_r = -1 } var VALUE_UNSPECIFIED := nil_val() /* Parsing */ type sexp_ast = scheme_value function parse_string (str: string): sexp_ast = let var index := 0 function is_ws (index: int): int = let var ascii := ord(substring(str, index, 1)) in ascii = 32 | ascii = 9 | ascii = 10 end function is_left_paren (index: int): int = let var ascii := ord(substring(str, index, 1)) in ascii = 40 end function is_right_paren (index: int): int = let var ascii := ord(substring(str, index, 1)) in ascii = 41 end function is_comment_start (index: int): int = let var ascii := ord(substring(str, index, 1)) in ascii = ord(";") end function is_tick (index: int): int = let var ascii := ord(substring(str, index, 1)) in ascii = ord("'") end function is_symbol (index: int): int = let var ascii := ord(substring(str, index, 1)) in not ( ascii = 9 | ascii = 10 | ascii = 32 | ascii = 40 | ascii = 41 | ascii = ord("'") ) end function sexp_nil (pos_l: int, pos_r: int): sexp_ast = sexp_ast { typ = type_nil , val_i = 0 , val_s = "" , val_e = nil , val_car = nil , val_cdr = nil , pos_l = pos_l , pos_r = pos_r } function sexp_atom (sym: string, pos_l: int, pos_r: int): sexp_ast = let var val := if sym = "#f" then bool_val(0) else if sym = "#t" then bool_val(1) else if is_integer_string(sym) then int_val(string_to_int(sym)) else sym_val(sym) in val.pos_l := pos_l ; val.pos_r := pos_r ; val end function sexp_quote (datum: sexp_ast, pos_l: int, pos_r: int): sexp_ast = sexp_pair( sexp_atom( "quote", pos_l, pos_r) , sexp_pair( datum, sexp_nil(pos_l, pos_r) , pos_l, pos_r) , pos_l, pos_r ) function sexp_pair ( car: sexp_ast , cdr: sexp_ast , pos_l: int , pos_r: int): sexp_ast = sexp_ast { typ = type_pair , val_i = 0 , val_s = "" , val_e = nil , val_car = car , val_cdr = cdr , pos_l = pos_l , pos_r = pos_r } function ignore_ws () = /* Ignore whitespace */ if index < size(str) & is_ws(index) then (index := index + 1; ignore_ws()) /* Handle comments, ignore until newline */ else if is_comment_start(index) then ( while ord(substring(str, index, 1)) <> ord("\n") do index := index + 1 ; ignore_ws()) function parse_rec (): sexp_ast = ( ignore_ws() ; if is_symbol(index) then let var index_start := index in while is_symbol(index) do index := index + 1 ; sexp_atom( substring(str, index_start, index - index_start) , index_start , index - 1 ) end else if is_tick(index) then let var index_start := index var datum := ( index := index + 1 ; parse_rec() ) var exp := sexp_quote(datum, index_start, index) in exp end else if is_left_paren(index) then let var sexp : sexp_ast := nil var sexp_last : sexp_ast := nil var index_start := index var most_right := index in index := index + 1 /* Position after index */ ; while index < size(str) & not(is_right_paren(index)) do let var parsed := parse_rec() var link := sexp_pair( parsed , sexp_nil(index, index) , parsed.pos_l , parsed.pos_r ) /* Initial link */ in if sexp = nil then ( sexp_last := link ; sexp := sexp_last ) /* In case of . syntax. For * example: (a . b) */ /* TODO: Below is ugly! */ else if parsed.typ = type_symbol & parsed.val_s = "." then ( index := index + 1 ; sexp_last.val_cdr := parse_rec() ; link.pos_r := sexp_last.val_cdr.pos_r ) /* Continue along with sexp * parsing */ else ( sexp_last.val_cdr := link ; sexp_last := sexp_last.val_cdr ) ; most_right := max(link.pos_r, most_right) ; ignore_ws() end /* Error handling */ ; if index >= size(str) then parse_error("Misaligned parenthesis") /*else if index = index_start + 1 then parse_error("Encountered unit expression (); is not allowed Scheme.")*/ /* Give correct positions */ ; sexp_last := sexp ; while sexp_last <> nil do ( sexp_last.pos_r := most_right ; sexp_last := sexp_last.val_cdr ) ; if sexp <> nil then ( sexp.pos_l := sexp.pos_l - 1 ; sexp.pos_r := sexp.pos_r + 1 ) /* Continue with stuff */ ; index := index + 1 ; if sexp = nil then sexp_nil(index_start, most_right) else sexp end else (parse_error("Error: I don't even!"); nil)) function parse_error(errmsg: string) = let in print("Tiger-scheme parse error\n ") ; print(errmsg) ; print("\n At index: ") ; print(int_to_string(index)) ; print("\n Nearby: ") ; print(safe_substring(str, index-10, index+10)) ; print("\n") end in parse_rec() end function sexp_ast_to_string (ast: sexp_ast): string = let function tostr_rec (ast: sexp_ast, nil_implicit: int): string = if ast = nil then "SOMEBODY FUCKED UP" else if ast.typ = type_nil & nil_implicit then "" else if ast.typ = type_pair & ast.val_car.typ <> type_pair then let var s_left := tostr_rec(ast.val_car, 0) var s_right := tostr_rec(ast.val_cdr, 1) in concat(s_left, if s_right <> "" then concat(" ", s_right) else "") end else if ast.typ = type_pair then let var s_left := tostr_rec(ast.val_car, 0) var s_right := tostr_rec(ast.val_cdr, 1) in concat("(", concat(s_left, concat(")", if s_right <> "" then concat(" ", s_right) else ""))) end else ast.val_s in concat("(", concat(tostr_rec(ast, 0), ")")) end /**** Instructions ****/ type vm_insn = { opcode: int , arg1: int , arg2: string , arg3: scheme_value , pos_l: int , pos_r: int } type vm_tape_tape = array of vm_insn type vm_tape = { length: int, tape: vm_tape_tape } type vm_insn_info = { opcode : int , mnemonic : string , uses_arg1 : int , uses_arg2 : int , uses_arg3 : int } type vm_insn_info_l = array of vm_insn_info var OPCODE_ADD := 0 var OPCODE_PUSH := 1 var OPCODE_GOTO := 2 var OPCODE_CSKIP := 3 var OPCODE_DUPL := 4 var OPCODE_SWITCH := 5 var OPCODE_MULT := 6 var OPCODE_LOAD := 7 var OPCODE_CALL := 9 var OPCODE_RET := 10 var OPCODE_DGOTO := 11 var OPCODE_DEF := 12 var OPCODE_SETG := 8 var OPCODE_DEFFUN := 13 var OPCODE_POP := 14 var OPCODE_GEQ := 15 var OPCODE_OUTPUT := 17 var OPCODE_TOSTR := 18 var OPCODE_CONS := 19 var OPCODE_CAR := 20 var OPCODE_CDR := 16 var OPCODE_SET := 21 var OPCODE_FRSTR := 22 var OPCODE_COMPILE:= 23 var vm_insn_num_opcodes := 0 var vm_insn_info := let var expected_number_opcodes := 30 var a := vm_insn_info_l [expected_number_opcodes] of nil function code ( opcode : int , mnemonic : string , uses_arg1 : int , uses_arg2 : int , uses_arg3 : int ) = ( if a[opcode] <> nil then print(concatm( "Error: Overwriting previously defined opcode information!\n Opcode: " , int_to_string(opcode) , " with previous mnenomic " , a[opcode].mnemonic , "\n")) ; a[opcode] := vm_insn_info { opcode = opcode , mnemonic = mnemonic , uses_arg1 = uses_arg1 , uses_arg2 = uses_arg2 , uses_arg3 = uses_arg3 } ; vm_insn_num_opcodes := vm_insn_num_opcodes + 1 ) in code(OPCODE_ADD, "ADD", 0, 0, 0) ; code(OPCODE_PUSH, "PUSH", 0, 0, 1) ; code(OPCODE_GOTO, "GOTO", 1, 0, 0) ; code(OPCODE_DGOTO, "DGOTO", 1, 0, 0) ; code(OPCODE_CSKIP, "CSKIP", 0, 0, 0) ; code(OPCODE_DUPL, "DUPL", 1, 0, 0) ; code(OPCODE_SWITCH, "SWITCH", 0, 0, 0) ; code(OPCODE_MULT, "MULT", 0, 0, 0) ; code(OPCODE_LOAD, "LOAD", 0, 1, 0) ; code(OPCODE_CALL, "CALL", 1, 0, 0) ; code(OPCODE_RET, "RET", 1, 0, 0) ; code(OPCODE_DEF, "DEF", 0, 1, 0) ; code(OPCODE_SETG, "SETG", 0, 1, 0) ; code(OPCODE_DEFFUN, "DEFFUN", 1, 0, 0) ; code(OPCODE_POP, "POP", 0, 0, 0) ; code(OPCODE_GEQ, "GEQ", 0, 0, 0) ; code(OPCODE_CONS, "CONS", 0, 0, 0) ; code(OPCODE_CAR, "CAR", 0, 0, 0) ; code(OPCODE_CDR, "CDR", 0, 0, 0) ; code(OPCODE_SET, "SET", 0, 1, 0) ; code(OPCODE_OUTPUT, "OUTPUT", 0, 0, 0) ; code(OPCODE_TOSTR, "TOSTR", 0, 0, 0) ; code(OPCODE_FRSTR, "FRSTR", 0, 0, 0) ; code(OPCODE_COMPILE,"COMPILE", 0, 0, 0) ; for i := 1 to expected_number_opcodes - 1 do if a[i] <> nil & a[i-1] = nil then print(concatm("Error: Opcode info array incorrectly initialized!\n Opcode " , int_to_string(i) , " is declared, but " , int_to_string(i-1) , " is not!")) ; print("Virtual Machine possess ") ; print(int_to_string(vm_insn_num_opcodes)) ; print(" instructions. Has space for ") ; print(int_to_string(expected_number_opcodes)) ; print(".\n") ; a end function noop_insn (pos_l: int, pos_r: int): vm_insn = vm_insn { opcode = OPCODE_DGOTO , arg1 = 1 , arg2 = "" , arg3 = nil , pos_l = pos_l , pos_r = pos_r } /**** Compile to VM ****/ type vm_insn_list_link = { insn: vm_insn, next: vm_insn_list_link } type vm_insn_list = { first: vm_insn_list_link, last: vm_insn_list_link } function concat_lists (a: vm_insn_list, b: vm_insn_list): vm_insn_list = ( if a = nil then print("Error: Impossible concat\n") else if b = nil | b.first = nil then () else if (a.first = nil & a.last <> nil) | (a.first <> nil & a.last = nil) then print(concatm( "Error: Instruction list invariant not maintained! First is " , if a.first = nil then "" else "not " , "nil, second is " , if a.last = nil then "" else "not " , "nil\n")) else if a.first = nil then ( a.first := b.first ; a.last := b.last ) else ( a.last.next := b.first ; a.last := b.last ) ; a ) function single_insn (insn: vm_insn): vm_insn_list = let var link := vm_insn_list_link { insn = insn, next = nil } in vm_insn_list { first = link, last = link } end function app_insn (insns: vm_insn_list, opcode:int, arg1: int, arg2: string, pos_l: int, pos_r: int) = ( concat_lists(insns, single_insn(vm_insn { opcode = opcode , arg1 = arg1 , arg2 = arg2 , arg3 = nil , pos_l = pos_l , pos_r = pos_r })) ; ()) function app_insn2 (insns: vm_insn_list, opcode:int, arg3: scheme_value, pos_l: int, pos_r: int) = ( concat_lists(insns, single_insn(vm_insn { opcode = opcode , arg1 = 0 , arg2 = "" , arg3 = arg3 , pos_l = pos_l , pos_r = pos_r })) ; ()) function tail_position (prev_insns: vm_insn_list, return_now: bool, pos_l: int, pos_r: int) = if return_now then app_insn(prev_insns, OPCODE_RET, 1, "", pos_l, pos_r) function tail_position_one (insn: vm_insn, return_now: bool, pos_l: int, pos_r: int): vm_insn_list = let var insns := single_insn(insn) in tail_position(insns, return_now, pos_l, pos_r) ; insns end function insn_list_length (insns: vm_insn_list): int = let var len := 0 var head := insns.first in while head <> nil do ( len := len + 1 ; head := head.next ) ; len end var STD_LIB_ENV: vm_env := nil var STD_LIB_ID_FUNCTION: scheme_value := nil var STD_LIB := let var first_insn := noop_insn(-1, -1) var std_insns := single_insn(first_insn) function app (opcode: int, arg1: int, arg2: string) = app_insn(std_insns, opcode, arg1, arg2, -1, -1) function app2 (opcode: int, arg3: scheme_value) = app_insn2(std_insns, opcode, arg3, -1, -1) function stdval (name: string, value: scheme_value) = STD_LIB_ENV := vm_env { key = name , value = value , next = STD_LIB_ENV } function tape_pos (): int = insn_list_length(std_insns) function stdfun (name: string) = stdval(name, fun_val(tape_pos(), nil)) in () /* Nil */ ; if HAS_NIL_SYMBOL then stdval("nil", nil_val()) /* Identity function */ ; STD_LIB_ID_FUNCTION := fun_val(tape_pos(), nil) ; app(OPCODE_RET, 1, "") /* Useful standard functions */ ; stdfun("display") ; app(OPCODE_TOSTR, 0, "") ; app(OPCODE_OUTPUT, 0, "") ; app2(OPCODE_PUSH, VALUE_UNSPECIFIED) ; app(OPCODE_RET, 1, "") ; stdfun("newline") ; app2(OPCODE_PUSH, str_val("\n")) ; app(OPCODE_OUTPUT, 0, "") ; app2(OPCODE_PUSH, VALUE_UNSPECIFIED) ; app(OPCODE_RET, 1, "") ; stdfun("cons") ; app(OPCODE_CONS, 0, "") ; app(OPCODE_RET, 1, "") ; stdfun("car") ; app(OPCODE_CAR, 0, "") ; app(OPCODE_RET, 1, "") ; stdfun("cdr") ; app(OPCODE_CDR, 0, "") ; app(OPCODE_RET, 1, "") ; first_insn.arg1 := insn_list_length(std_insns) ; std_insns end function compile_to_vm (ast: sexp_ast): vm_tape = let function list (head: vm_insn_list_link): vm_insn_list = let var tail := head in while tail <> nil & tail.next <> nil do tail := tail.next ; vm_insn_list { first = head, last = tail } end function copy_list (ls: vm_insn_list): vm_insn_list = list(ls.first) function atom_to_insn (sym: scheme_value, pos_l: int, pos_r: int): vm_insn = if sym = nil then ( print("Error in atom_to_list: Got nil as sym!\n") ; nil ) else if is_symbol(sym) then vm_insn { opcode = OPCODE_LOAD , arg1 = 0 , arg2 = sym.val_s , arg3 = nil , pos_l = pos_l , pos_r = pos_r } else vm_insn { opcode = OPCODE_PUSH , arg1 = 0 , arg2 = "" , arg3 = sym , pos_l = pos_l , pos_r = pos_r } function is_postfix (ast: sexp_ast): int = (ast <> nil) & (ast.typ = type_pair) & (ast.val_car <> nil) & (ast.val_car.typ <> type_pair) & (ast.val_car.val_s = "+" | ast.val_car.val_s = "*" | ast.val_car.val_s = ">=") function op_insn (opcode: int, pos_l: int, pos_r: int): vm_insn = vm_insn { opcode = opcode , arg1 = 0 , arg2 = "" , arg3 = nil , pos_l = pos_l , pos_r = pos_r } function compile_postfix ( sym : string , num_args : int , insns : vm_insn_list , pos_l : int , pos_r : int , tail_call: bool ): vm_insn_list = let var insn := if sym = "+" then op_insn(OPCODE_ADD, pos_l, pos_r) else if sym = "*" then op_insn(OPCODE_MULT, pos_l, pos_r) else if sym = ">=" then op_insn(OPCODE_GEQ, pos_l, pos_r) else (print("Unknown special form"); nil) in concat_lists(insns, single_insn(insn)) ; tail_position(insns, tail_call, pos_l, pos_r) ; insns end function sexp_ast_length(insns: vm_insn_list): int = let function rec (insns: vm_insn_list_link, sum: int): int = if insns = nil then sum else rec(insns.next, 1 + sum) in rec(insns.first, 0) end function set_tree_positions ( ast: sexp_ast , pos_l: int , pos_r: int ) = /* Useful for setting the positions of generated AST. */ if ast <> nil then ( ast.pos_l := pos_l ; ast.pos_r := pos_r ; if ast.typ = type_pair then ( set_tree_positions( ast.val_cdr, pos_l, pos_r) ; set_tree_positions( ast.val_car, pos_l, pos_r) )) function sexp_is_list_of_type ( ast: sexp_ast , subtype: type_type ): bool = if ast = nil then false else if ast.typ = type_nil then true else ast.typ = type_pair & ast.val_car <> nil & ast.val_car.typ = subtype & sexp_is_list_of_type(ast.val_cdr, subtype) function sexp_is_literals_list ( ast: sexp_ast ): bool = sexp_is_list_of_type(ast, type_symbol) function sexp_is_ellipsis ( ast: sexp_ast ): bool = ast <> nil & ast.typ = type_symbol & ast.val_s = "..." function sexp_list_length (ast: sexp_ast): bool = if ast = nil | not(ast.typ = type_pair | ast.typ = type_nil) then false else if ast.typ = type_nil then 0 else let var len := sexp_list_length(ast.val_cdr) in if len < 0 then len else len + 1 end function sexp_is_pattern_datum (ast: sexp_ast): bool = ast <> nil & ( ast.typ = type_integer | ast.typ = type_string | ast.typ = type_true | ast.typ = type_false /* TODO: character */) function sexp_is_pattern_id (ast: sexp_ast): bool = ast <> nil & ast.typ = type_symbol & not(sexp_is_ellipsis(ast)) function sexp_is_pattern ( ast: sexp_ast ): bool = if ast = nil then false /* Pattern Datum */ else if sexp_is_pattern_datum(ast) then true /* The empty list '() is a pattern */ else if ast.typ = type_nil then true /* Ensure it is a pair */ else if ast.typ <> type_pair | ast.val_car = nil | ast.val_cdr = nil then false /* List of patterns: ( ...) */ /* Non-proper list of patterns: ( ... . ) */ else if ast.typ = type_pair & sexp_is_pattern(ast.val_car) & sexp_is_pattern(ast.val_cdr) then true /* ( ... ) */ else if ast.typ = type_pair & sexp_is_pattern (ast.val_car) & sexp_is_ellipsis(ast.val_cdr.val_car) & ast.val_cdr.val_cdr.typ = type_nil then true /* TODO: Hashtag notation (https://people.csail.mit.edu/jaffer/r5rs/Pattern-language.html) */ else false function sexp_is_template ( ast: sexp_ast ): bool = if ast = nil then false /* Pattern Datum */ else if sexp_is_pattern_datum(ast) then true /* The empty list '() is a template */ else if ast.typ = type_nil then true /* Ensure it is a pair */ else if ast.typ <> type_pair | ast.val_car = nil | ast.val_cdr = nil then false /* List of templates: ( ...) */ /* Pair of templates: ( .