turing.sml (6790B)
1 (* Editor's note: A random script I wrote for a Nature of Reason (80-150) assignment. 2 * Written around spring 2024. The below introductory comment is taken from my submission: 3 * 4 * I was procrastinating a bit last week and so I wrote up a little Standard ML program to 5 * simulate a Turing Machine! 6 * 7 * The bulk of the work is in representing the infinite tape of the Turing machine: I 8 * considered having some wonky double-ended stream at first but I ended up just using a 9 * function of type int -> bool to represent the state of the tape (thus to set index j to 10 * true, you would redefine the existing function f to be fn i => i = j orelse f i). This 11 * is pretty wasteful in terms of memory usage since it means that a Turing program that 12 * just flips one cell forever would use increasing amounts of memory, but explicitly 13 * tracking which cells have been flipped seem like a bunch of work. 14 * 15 * The actual Turing program evaluator is super simple: it just fetches the rules for the 16 * current state from the program, applies them to the tape, and recurses (with the base 17 * case being state 0). 18 * 19 * I almost wrote a parser for this too (did you know SMLNJ ships with a parser combinator 20 * library?!), but some of the validation logic was icky and so I ran out of energy. 21 * Final tidbit: the tape has to track some other metadata (current position, bounds of 1s 22 * to read) and so I actually use a wackier feature of SML: flex records (look at the type 23 * definition for tape! or try typing something like {x=2, y="whee"} into your SML 24 * interpreter!). They’re basically anonymous named tuples. Kinda cool feature which I 25 * haven’t seen in any other language (to be fair they’re a bit limited: often SML will 26 * complain if you try to write polymorphic functions that access fields of arbitrary flex 27 * records). *) 28 29 datatype direction = Left | Right 30 type rule = (int * bool * direction) 31 type instr = (rule * rule) 32 33 signature TAPE = sig 34 type tape 35 val inpNum : int -> tape 36 val inpPair : int*int -> tape 37 val outNum : tape -> int 38 val toList : tape -> bool list 39 val get : tape -> bool 40 val set : tape -> bool -> tape 41 val move : tape -> direction -> tape 42 end 43 44 structure Tape :> TAPE = struct 45 type tape = {bounds: (int * int), loc: int, f: int -> bool} 46 fun inpNum x = {bounds=(0, x), loc=0, f=fn i => i >= 0 andalso i <= x} 47 48 fun inpPair (x,y) = 49 {bounds=(0, x+y+3), loc=0, f=(fn i => (i >= 0 andalso i <= x) orelse 50 (i > x+1 andalso i <= x+2+y))} 51 52 fun toList (t : tape) = 53 let val (min,max) = #bounds t 54 in List.tabulate (max-min, fn i => (#f t) (min+i)) end 55 56 fun outNum (t : tape) = let 57 fun count (t : tape) i sum = 58 if i > (#2 (#bounds t)) then sum 59 else count t (i+1) (sum + (if ((#f t) i) then 1 else 0)) 60 in 61 count t (#1 (#bounds t)) 0 62 end 63 64 fun get (t : tape) = (#f t) (#loc t) 65 fun set (t : tape) new = let 66 val (min, max) = #bounds t 67 val min = Int.min(min, #loc t) 68 val max = Int.max(max, #loc t) 69 in 70 {bounds=(min,max), loc=(#loc t), 71 f=(fn i => if i = (#loc t) then new else (#f t) i)} 72 end 73 fun move (t : tape) dir = 74 let val shift = (case dir of Left => ~1 | Right => 1) 75 in {bounds=(#bounds t), loc=(#loc t)+shift, f=(#f t)} end 76 end 77 78 fun eval t 0 _ = t 79 | eval (tape : Tape.tape) (state : int) (instrs : instr list) = let 80 val ins = List.nth (instrs, state - 1) 81 val (newstate, newval, dir) = if Tape.get tape then #2 ins else #1 ins 82 in 83 eval (Tape.move (Tape.set tape newval) dir) newstate instrs 84 end 85 86 (* I originally had a parser for this, but it ended up being too much of a 87 * hassle (being able to leave off unreachable rules for some states and 88 * other stuff made it annoying to parse). Instead programs are represented 89 * as a list of instructions where the nth item represents the two rules 90 * for the nth state (rule being what to do for a 0 and what to do for a 1. *) 91 92 val plus3 = [((2, true, Right), (1, true, Right)), 93 ((0, true, Right), (0, true, Right))] 94 95 val ident = [((1, true, Right), (0, false, Right))] 96 97 val three = [((2, true, Right), (2, true, Right)), 98 ((3, true, Right), (3, true, Right)), 99 ((4, true, Right), (4, true, Right)), 100 ((0, false, Right), (4, false, Right))] 101 102 val add = [((1, false, Right), (2, false, Right)), 103 ((3, false, Right), (2, true, Right)), 104 ((0, false, Right), (0, false, Right))] 105 106 107 val mock = [((0, false, Right), (2, true, Right)), 108 ((0, true, Right), (3, false, Left)), 109 ((0, false, Right), (0, false, Left))] 110 111 (* Editor's note: The buried remnants of my attempt at a parser: *) 112 113 (* structure P = ParserComb *) 114 115 (* fun skipWS parser = P.skipBefore Char.isSpace parser *) 116 (* (* Combines two parsers, ignoring whitespace. *) *) 117 (* fun +> (a, b) = P.seq (a, skipWS(b)) *) 118 (* infixr 3 +> *) 119 (* (* Only keeps the right hand result. *) *) 120 (* fun %> (a, b) = P.wrap(a +> b, #2) *) 121 (* infixr 3 %> *) 122 (* (* Only keeps the left hand result. *) *) 123 (* fun @> (a, b) = P.wrap(a +> b, #1) *) 124 (* infixr 3 @> *) 125 126 (* (* Optionally applies a parser. On failure, parses to NONE instead of passing *) 127 (* * an actual failure up and causing the overall parse to fail. *) *) 128 (* fun parseOptional parser getc = P.or (P.wrap (parser, SOME), P.result NONE) getc *) 129 130 (* fun parseState getc = ((P.char #"q") %> (Int.scan StringCvt.DEC)) getc *) 131 (* fun parseDir getc = P.or(P.wrap(P.char #"L", fn _ => Left), *) 132 (* P.wrap(P.char #"R", fn _ => Right)) getc *) 133 (* fun parseVal getc = P.or'([P.wrap(P.char #"B", fn _ => false), *) 134 (* P.wrap(P.char #"0", fn _ => false), *) 135 (* P.wrap(P.char #"1", fn _ => true)]) getc *) 136 137 (* fun parseRule getc = *) 138 (* P.wrap(parseState +> P.or'([P.wrap(P.char #"1", fn _ => true), *) 139 (* P.wrap(P.char #"0", fn _ => false), *) 140 (* P.wrap(P.char #"B", fn _ => false)]) *) 141 (* +> P.wrap(parseState +> parseVal +> parseDir, *) 142 (* fn (s, (v, d)) => (s,v,d)), *) 143 (* fn (s, (i, r)) => (s, i, r)) getc *) 144 145 (* exception Parse *) 146 (* fun strToRule str = Option.valOf (StringCvt.scanString parseRule str) *) 147 (* handle Option => raise Parse *) 148 149 (* fun parseRules rules = let *) 150 (* val rules = map strToRule rules *) 151 (* in *) 152 153 (* end *) 154 155 (* (* in *) *) 156 157 (* (* end *) *) 158 159 (* val prog = [ *) 160 (* "q3 1 q2 1 R", *) 161 (* "q3 0 q2 1 R", *) 162 (* "q2 1 q0 1 R" *) 163 (* ] *)