⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
{{implementation|SNUSP}}{{collection|RCSNUSP}}
This is the [[F Sharp|F#]] implementation of the "modular" version. Perhaps I'll get around to bloated later. Allows infinite size data space to the left and right of the original data pointer. I originally mistook the meaning of ',
' and had the user input an arbitrary number which I would place on the tape, but after looking at the sample multiplication program, realized that I was supposed to input the ascii value of the single key entered. Still, it seems like a good command to allow for reading an arbitrary value so I arbitrarily allocated '~
' as the command for that purpose.
Bloated version below this modular version. Somehow this page has been set up so I can't seem to change the original comments to note this fact. Probably I'm just too dumb to know now.
==Modular SNUSP==
open System open System.Collections.Generic type IP (p:(int*int), d:(int*int), dim1, dim2) = let mutable _p = p let mutable _d = d let mutable _fValid = true member this.dim1 = dim1 member this.dim2 = dim2 member this.x = fst this.pos member this.y = snd this.pos member this.dx = fst this.dir member this.dy = snd this.dir member this.pos with get() = _p and set newp = _p <- newp member this.dir with get() = _d and set newd = _d <- newd member this.Clone() = new IP((this.x, this.y), (this.dx, this.dy), dim1, dim2) member this.Invalidate() = _fValid <- false member this.SetTo(ip : IP) = this.pos <- ip.pos; this.dir <- ip.dir member this.Advance() = this.pos <- ((fst this.pos) + (fst this.dir), (snd this.pos) + (snd this.dir)) member this.Valid() = _fValid && this.x >= 0 && this.x < dim2 && this.y >= 0 && this.y < dim2 member this.Reflect(c) = match c with | '/' -> this.dir <- (-this.dy, -this.dx) | '\\' -> this.dir <- (this.dy, this.dx) | _ -> ignore() let RCSNUSP (pgmStr : string) = let StringToPgm (str : string) = let stringsPre = str.Trim([|'\n'; '\r'|]).Split([|'\n'|]) |> Seq.map (fun s -> s.Trim([|'\n'; '\r'|])) let maxLen = stringsPre |> Seq.map (fun s -> s.Length) |> Seq.max let strings = stringsPre |> Seq.map (fun s -> s.PadRight(maxLen)) strings |> Seq.map (fun s -> s.Trim([|'\n'; '\r'|]).ToCharArray()) |> array2D let pgm = StringToPgm pgmStr let ptr = ref 0 // Pointer into input let stk = new Stack<IP>() // Instruction stack let input = ref (Array.create 100 0) let LocateStart pgm = let fFound = ref false let s1 = seq { for i = 0 to ((Array2D.length1 pgm)-1) do for j = 0 to ((Array2D.length2 pgm)-1) do yield (pgm.[i,j], (j, i)) } |> Seq.skipWhile (fun (c, _) -> fFound := !fFound || (c = '$'); not !fFound) |> Seq.truncate 1 |> Seq.toArray if not !fFound then (0,0) else s1 |> Seq.head |> snd let ip = new IP(LocateStart pgm, (1, 0), Array2D.length1 pgm, Array2D.length2 pgm) // Instruction Pointer let InputNumber() = let mutable fValid = false let mutable num = 0 printfn "Enter a valid number" fValid <- Int32.TryParse(Console.ReadLine(), &num); while not fValid do printfn "Invalid input. Please enter a valid number" fValid <- Int32.TryParse(Console.ReadLine(), &num); num let InputAscii() = printfn "Enter an ascii character" let chOut = (Console.ReadKey().KeyChar) printfn "" int chOut let MovePtr fRight = if fRight then ptr := !ptr + 1 if !ptr >= (!input).Length then Array.Resize(input, (!input).Length + 20) else ptr := !ptr - 1 if !ptr < 0 then let newInput = Array.create ((!input).Length + 20) 0 Array.ConstrainedCopy(!input, 0, newInput, 20, (!input).Length) ptr := 19 let interpretCmd() = //if @"><+-.,;/\?!@#".Contains(pgm.[ip.y, ip.x].ToString()) then // printfn "TapePos: %d; TapeVal: %d; Pos (%d, %d) : %c" !ptr (!input).[!ptr] ip.x ip.y pgm.[ip.y, ip.x] // Console.ReadKey() |> ignore match pgm.[ip.y, ip.x] with | '>' -> MovePtr true; ip.Advance() | '<' -> MovePtr false; ip.Advance() | '+' -> (!input).[!ptr] <- (!input).[!ptr] + 1; ip.Advance() | '-' -> (!input).[!ptr] <- (!input).[!ptr] - 1; ip.Advance() | '.' -> printf "%c" (char (!input).[!ptr]); ip.Advance() | ',' -> (!input).[!ptr] <- InputAscii(); ip.Advance() | '~' -> (!input).[!ptr] <- InputNumber(); ip.Advance() | '/' | '\\' -> ip.Reflect(pgm.[ip.y, ip.x]); ip.Advance() | '?' -> ip.Advance(); if (!input).[!ptr] = 0 then ip.Advance() | '!' -> ip.Advance(); ip.Advance() | '@' -> stk.Push(ip.Clone()); ip.Advance() | '#' -> if stk.Count = 0 then ip.Invalidate() else ip.SetTo(stk.Pop()) ip.Advance() ip.Advance() | _ -> ip.Advance() while ip.Valid() do interpretCmd() |> ignore (!input).[!ptr]
==Bloated SNUSP==
Okay, I did the bloated version and made a few other changes. Using *
rather than ~
for inputting a number because it's easy for ~
to get lost in the visual sea of characters that makes up the typical program. Also made separate classes for the engine and the IP. I allow for infinite data space in both directions and threads not waiting for input run while other threads are blocked on input. I tried this on all the programs I could find. There don't appear to be any which really utilize the 2D data space, but I tested it with some simple test programs and it seems okay.
// Bloated RCSNUSP open System open System.Collections.Generic type IP (p:(int*int), d:(int*int), dim1, dim2) = let mutable _p = p // Current instruction position let mutable _d = d // Current direction we're headed let mutable _fValid = true // Override telling us we're invalid let mutable _dataptr = (0, 0) // Data Pointer let _callStack = new Stack<IP>() // Call stack let _dim1 = dim1 // Dimensions of our instruction space let _dim2 = dim2 // Some properties for the above member t.pos with get() = _p and set newp = _p <- newp member t.dir with get() = _d and set newd = _d <- newd member t.dataptr with get() = _dataptr and set newp = _dataptr <- newp member t.x = fst t.pos // X value of our instruction pointer member t.y = snd t.pos // Y value of our instruction pointer member t.dx = fst t.dir // X value of our direction member t.dy = snd t.dir // Y value of our direction member private t.Clone() = new IP((t.x, t.y), (t.dx, t.dy), _dim1, _dim2) // Used for call/return where we don't need the data pointer member t.Split() = let newThread = t.Clone() // New thread starts with same position/direction newThread.dataptr <- t.dataptr // but also inherits the same data position newThread member t.Invalidate() = _fValid <- false member t.SetTo(ip : IP) = t.pos <- ip.pos; t.dir <- ip.dir member t.Advance() = t.pos <- ((fst t.pos) + (fst t.dir), (snd t.pos) + (snd t.dir)) // All important Advance moves the IP in the current direction member t.InRange() = t.x >= 0 && t.x < _dim2 && t.y >= 0 && t.y < _dim1 // See if the IP is still in instruction space member t.Valid() = _fValid && t.InRange() // See if we're valid member t.Reflect(c) = match c with // See which character we're dealing with | '/' -> t.dir <- (-t.dy, -t.dx) // and do the | '\\' -> t.dir <- (t.dy, t.dx) // right thing | _ -> ignore() member t.Return() = if _callStack.Count = 0 then // If there's nothing on the call stack to return to t.Invalidate() // kill ourselves else t.SetTo(_callStack.Pop()) // Otherwise, retrieve the old position and direction t.Advance() // and advance twice to continue t.Advance() member t.Call() = _callStack.Push(t.Clone()) // Push our return point onto the stack type SNUSP (pgmStr : string) = let _pgm = let stringsPre = pgmStr.Trim([|'\n'; '\r'|]).Split([|'\n'|]) // Trim off any initial CR/LFs and split at internal CRs |> Seq.map (fun s -> s.Trim([|'\n'; '\r'|])) // We may or may not still have left over 'n' and 'r's hanging around let maxLen = stringsPre // Array of stripped program lines |> Seq.map (fun s -> s.Length) // Find their length |> Seq.max // and take the max let strings = stringsPre // Array of stripped program lines |> Seq.map (fun s -> s.PadRight(maxLen)) // Pad them all the the max length strings |> Seq.map (fun s -> s.ToCharArray()) // Turn all the lines to char arrays |> array2D // Turn the whole thing into a 2D array let LocateStart pgm = let fFound = ref false let s1 = // It seems like this should just be an imperative nested for, but since F# doesn't have a break statement, once you // start into a for you have to go all the way through it. The sequence below will stop as soon as it finds the '$'. seq { for i = 0 to ((Array2D.length1 pgm)-1) do for j = 0 to ((Array2D.length2 pgm)-1) do yield (pgm.[i,j], (j, i)) } // Sequence of (value, position) pairs |> Seq.skipWhile (fun (c, _) -> fFound := !fFound || (c = '$'); not !fFound) // Keep track of whether we found a '$" and only keep the good stuff |> Seq.truncate 1 // Stop as soon as we've found the '$' |> Seq.toArray // Required to keep lazy evaluations from not actually finding anything at all if not !fFound then // No '$' means start at (0,0) (0,0) else s1 |> Seq.head // else, the only value in the sequence is our '$' |> snd // and the second value in it is it's position let _ips = let ret = new List<IP>() // list of currently active IPs ret.Add(new IP(LocateStart _pgm, (1, 0), Array2D.length1 _pgm, Array2D.length2 _pgm)) // Add our initial IP into it ret let _data2d = ref (Array2D.create 10 10 0) // 2D data space let mutable _fWaitingForKey = false // So we can handle ',' operator nicely let _lstSplitIps = new List<IP>() // List of IPs to be deleted after the current tick let _rnd = new Random() let mutable _builtString = "" member private t.ips = ref _ips member private t.ipLastRemoved = ref (new IP((0,0), (0,0), 0, 0)) // Last IP removed (for return value) member t.ReturnValue() = t.GetData (!t.ipLastRemoved) // Return value member private t.InputNumber() = let mutable out = None // No valid values yet if not _fWaitingForKey then // If necessary _fWaitingForKey <- true printf "Enter a valid number: " // Ask the user for a number if Console.KeyAvailable then // If the user has pressed a key let chNext = Console.ReadKey().KeyChar // get the key if chNext = '\r' then // If they pressed enter printfn "" let mutable num = 0 // set up a number to receive the final value let fValid = Int32.TryParse(_builtString, &num) // see if they entered a valid value if fValid then // If they did, then out <- Some(num) // Set our output to it _fWaitingForKey <- false // And stop checking for keys else // else printf "Invalid input. Please enter a valid number: " // prompt for a valid value _builtString <- "" // and reset the built string else _builtString <- _builtString + chNext.ToString() // if no enter, just append the value to the built up string out // return our output member private t.InputAscii() = if not _fWaitingForKey then // If we haven't put up the prompt yet _fWaitingForKey <- true // keep track of the fact that we have printf "Enter an ascii character: " // and put the prompt up if Console.KeyAvailable then // If there are keys available let chOut = Console.ReadKey().KeyChar // Get the key _fWaitingForKey <- false // Allow the prompt to be put up printfn "" // print a CR, Some(int chOut) // return with the booty else None // return nothing static member private OutputAscii(c) = printf "%c" c // Print a single character member private t.ExpandData top left bottom right = let newInput = Array2D.create ((Array2D.length1 !_data2d) + top + bottom) (Array2D.length2 !_data2d + left + right) 0 // Create the new array with enough space for i in [0..((Array2D.length1 !_data2d) - 1)] do // For each row for j in [0..((Array2D.length2 !_data2d) - 1)] do // and column newInput.[i + top, j + left] <- (!_data2d).[i,j] // Copy old data to the new array _data2d := newInput // Switch to the new array if top <> 0 || left <> 0 then // If necessary !t.ips |> Seq.iter (fun ip -> (ip.dataptr <- ((fst ip.dataptr) + left, ((snd ip.dataptr) + top)))) // Change data pointers in all the IPs member private t.MovePtr2d (ip:IP) (dx, dy) = ip.dataptr <- ((fst ip.dataptr) + dx, (snd ip.dataptr) + dy) // Move the pointer match (dx, dy) with // Check the result to see if we need to expand | (0,_) -> // Check the vertical direction if (snd ip.dataptr) >= (Array2D.length1 !_data2d) then // If we've overflowed the rows t.ExpandData 0 0 10 0 // Expand on the bottom elif (snd ip.dataptr) < 0 then // If we've underflowed the rows t.ExpandData 10 0 0 0 // Expand on the top | (_,0) -> // Check the horizontal direction if (fst ip.dataptr) >= (Array2D.length2 !_data2d) then // If we've overflowed the columns t.ExpandData 0 0 0 10 // expand on the right elif (fst ip.dataptr) < 0 then // If we've underflowed columns t.ExpandData 0 10 0 0 // expand on the left | _ -> raise <| new System.ArgumentException("Bad direction in MovePtr2d") // This should never happen member private t.Split(ip:IP) = let newIp = ip.Split() // Get an IP for the new thread newIp.Advance(); // advance it twice newIp.Advance(); _lstSplitIps.Add(newIp) // and add it to the list of splits member t.lstIp = !t.ips // List of active IPs member private t.GetData (ip:IP) = (!_data2d).[snd ip.dataptr, fst ip.dataptr] // Data being pointed at currently member private t.SetData (ip:IP) n = (!_data2d).[snd ip.dataptr, fst ip.dataptr] <- n // Set the value at the current position member t.InterpretCmd(ip:IP) = // Interpret a single command for a single IP // Rudimentary debugging aid - interferes with any input and doesn't distinguish between threads //if @"><+-.,;:*/\?!@#".Contains(pgm.[ip.y, ip.x].ToString()) then // printfn "TapePos: %A; TapeVal: %d; Pos (%d, %d) : %c" ip.dataptr (t.GetData ip) ip.x ip.y _pgm.[ip.y, ip.x] // Console.ReadKey() |> ignore if ip.Valid() then match _pgm.[ip.y, ip.x] with | '>' -> t.MovePtr2d ip (1, 0); ip.Advance() | '<' -> t.MovePtr2d ip (-1, 0); ip.Advance() | ';' -> t.MovePtr2d ip (0, 1); ip.Advance() | ':' -> t.MovePtr2d ip (0, -1); ip.Advance() | '+' -> t.SetData ip ((t.GetData ip) + 1); ip.Advance() | '-' -> t.SetData ip ((t.GetData ip) - 1); ip.Advance() | '%' -> t.SetData ip (_rnd.Next(0, 256)); ip.Advance() | '.' -> SNUSP.OutputAscii(char (t.GetData ip)); ip.Advance() | ',' -> let chOpt = t.InputAscii() // Check if there are any keys waiting if chOpt <> None then // If there are... t.SetData ip (Option.get chOpt) // set the current position to the ascii value of the key ip.Advance() // and go on to the next instruction | '*' -> let valOpt = t.InputNumber() // Do the same for numbers if valOpt <> None then t.SetData ip (Option.get valOpt) ip.Advance() | '/' | '\\' -> ip.Reflect(_pgm.[ip.y, ip.x]); ip.Advance() | '?' -> ip.Advance(); if (t.GetData ip) = 0 then ip.Advance() | '!' -> ip.Advance(); ip.Advance() | '&' -> t.Split(ip); ip.Advance() | '@' -> ip.Call(); ip.Advance() | '#' -> ip.Return() | _ -> ip.Advance() if not (ip.Valid()) then t.ipLastRemoved := ip member t.InterpretCmd() = // Interpret a single command for each active IP _lstSplitIps.Clear() // Clear the split array let ipRemovals = !t.ips // Start with all ips |> Seq.filter (fun ip -> t.InterpretCmd(ip); // Interpret the command not (ip.Valid())) // Collect the IP if it went invalid |> Seq.toArray // Force laziness to act ipRemovals // For each invalid IP found |> Seq.iter (fun ip -> (!t.ips).Remove(ip) |> ignore) // remove it from the list of active IPs _lstSplitIps // For each new thread created |> Seq.iter (fun ip -> (!t.ips).Add(ip) |> ignore) // Add it to the list of active IPs member t.Execute() = // Execute til exit while (!t.ips).Count <> 0 do // While there are active threads t.InterpretCmd() // Interpret commands static member Run(pgm) = let snusp = new SNUSP(pgm) // Create an engine snusp.Execute() // and run the program with it snusp.ReturnValue() // Return the return value
==Example Inputs==
let p1 = @" read two characters ,>,==\ * / ### ============= ATOI ----------\ convert to integers /=/@</@=/ * // /===== ITOA ++++++++++\ /----------/ multiply @ \=!\ ### === / // /++++++++++/ \----------\ convert back !/@!\ ### ====== / \++++++++++\ /----------/ and print the result \/ \.# * /++++++++++/ \--------# / ### ============== / * \++++++++# | | /-<+>\ #/?=<<<<\!>>>>\ />>+<+<-\ | #\?===/! BMOV1 =====\ \->>>>+/ // / ### == BSPL2 !\======?/# | /->+<\ /===| ### ===== FMOV4 =/ // /<<+>+>-\ | #\?===/! FMOV1 =|===| ### ===========\ /====/ /=== FSPL2 !\======?/# | /==|===| ### ======== |==| ### = / | * * *|* | * | * * * * * * *|* | * * * /+<-\ | * />@/<@/>>@/>> ### \ /= >>\@<\@<\ * /==== ADD2 !\>=?/<# \===== MUL2 =?/>@\==<#<<<==\ \!\<<<<@\>>>>-?/\ * // /-\ * \\ \/@ ### == |======</ * // /== ZERO !\?/# * * * \\* * * * | * * * * | * * * * *// // \\ | \ ### ==== / // \======!\ ### ================= / " // Should print out multiplication of two input ascii values RCSNUSP p1 |> ignore printfn "" printfn "Next three should all yield 48" printfn "value = %d" (RCSNUSP @" /\/\/\ $===++++++\ /++++++/ \++++++\ /++++++/ \/\/\/# ") printfn "value = %d" (RCSNUSP @" #/\/\ $===!\++++\ /++++/ /=\++++\ \!\/\/\/ ") printfn "value = %d" (RCSNUSP @"6=@@@+@+++++#")