User:JayCampbell/weave.rb
weave.rb - The brainfuck forking derivative interpreter
weave.rb is a Ruby interpreter by JayCampbell for the Weave programming language, which allows multiple bf programs to run concurrently, and adds a tape shared by all threads. Every thread that existed at the beginning of each cycle executes one instruction during that cycle.
-Y turns on Brainfork functionality. Threads are spawned every time the Y instruction is run.
-p turn on pbrain functionality. Upon reaching a set of (parenthesis), the innards of that block are saved as a subroutine "named" the value of the current cell. To execute this code later, set the current cell to that same number and run the colon (:) token.
-X turns on braintwist functionality, so the X token "swaps the data and code arrays, making it possible to store code in the data array for later execution." If the Brainfork option is turned on, "the X instruction only swaps the arrays of the thread calling the instruction. As a result, one thread's code array can be used as another's data array."
-j turns on JumpFuck functionality. The ampersand defines a jump point, the percent sign jumps there. Undefined jump points send you to cell zero. See the linked spec for info on jump point IDs and arguments.
-r makes weave.rb act according to Reversible Brainfuck rules.
-E turns on EsoAPI support. The file esoapi.dsk is created if it does not exist, which can be randomly accessed by programs using the correct null-prefixed commands. This program prints the contents of that file: .+++.>[.>]
-R turns on embedded Ruby support. is used as the trigger. Program lines may contain "#ruby" (optionally followed by a number, defaulting to zero) and ruby code. These ruby snippets are collected and triggered by sending the EsoAPI output of zero followed by 10. If a section number was supplied (e.g. #ruby3, or #ruby9) then it could be triggered with a 10-higher EsoAPI call (e.g. 0 3, or 0 19).
-v displays verbose output during the run.
All the switches can be used together. Multiple initial threads can be started using semicolons and/or multiple source files, and embedded Y's in each can cause further forking. Threads do not share pbrain functions, but calls and returns between threads are possible using the shared tape and delay loops.
All files specified at command line create separate program threads. Semicolons separating instructions also cause the source to be broken into threads. Each file's post-exclamation input text is appended (in filename order) to a master input stream available to all threads. If no input text is supplied in the source files, standard input is fetched. (Original Weave spec called for a ! character before threads; the standard use for that character is implemented instead - attaching input text to the end of a script.)
Other notes: Numbers do not wrap, and may be negative or large. Negative pointer references count backward from the end of the rightmost position ever referenced on that particular tape. weave.rb is also an interpreter and debugger for standard brainfuck programs.
Concurrent bf programming
The new instructions introduced by Weave (local/master tape switch, NOP for pausing) means arbitrary segments of the master tape could be reserved as communication channels and common storage for an indefinite number of bf threads. Feedback?
Examples
$ ./weave.rb 99.bf 99.bf 99.bf 999999 bbbooottttttllleeesss ooofff bbbeeeeeerrr ooonnn ttthhheee wwwaaallllll,,, 999999 bbbooottttttllleeesss ooofff bbbeeeeeerrr,,, $ ./weave.rb sange-archive/hello* HHHELLOWelloheallo t HelloW WORWoLrlDd! orld!! worldis! your name?
The following program produces the output GHOSTY:
~+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++; ~@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..@@@@@@.@@@..@@@@.
The first thread switches to the master tape and starts incrementing. The second thread switches but NOP waits until the first has incremented enough, then prints during the appropriate cycles.
If you save the next few lines to a file called pbrain1.pb and run the command "./weave -p pbrain1.pb",
+([-]<[-]<[>+>+<<-]>>[<<+>>-]) +([-]>[-]+:<+) >>+++++++++++++[<+++++>-] ++: >++: >++: >++: <<<<.>.>.>.>.
... the output is ABCDE. Per the [|pbrain site], "This pbrain program initializes a memory location to 65, the ASCII value of the letter 'A'. It then calls a function for subsequent memory locations to copy the previous location and add one to it. Once a few cells are initialized, it prints all the cells to standard output."
Embedding ruby for debugging
>++++++++++++++++++++++++ # ruby print " hi " +++++++++++++++++++++++++ # ruby8 print "tape size: #{thread[:tape].size}" +++++++++++++++++++++++++< # ruby print "there " >>++++++++++<< .>>.<< # this line triggers ruby0 by outputting 0 10 >..........< .>>.<< >>++++++++<< .>>.<< # run up to 18 for some debug >>--------<< .>>.<< # go back to 10 and print hi again $ ./weave.rb -E -R rubytest.b hi there JJJJJJJJJJ hi there tape size: 3 hi there
weave.rb ruby source code
updated by JayCampbell 08:58, 2 December 2008 (UTC)
#!/usr/bin/ruby # ./weave.rb [-v] [-Y] [-p] file1.txt file2.txt ... # A threading debugging interpreter by Jay Campbell 2008 # for a variety of brainfuck derivative esoteric programming language # http://esoteric.voxelperfect.net/wiki/User:JayCampbell/weave.rb $sectorsize = 512 class WeaverParser attr_accessor :threads, :commontape def initialize @threads = Array.new @commontape = String.new @commontape << 0.chr @num = 0 end def addthread( text ) allowed = '[<>+-.,]~@' allowed << 'Yy' if $brainfork allowed << '():' if $pbrain allowed << 'X' if $braintwist allowed << 'x' if $braintwist2 allowed << '&%' if $jumpfuck thread = Hash.new thread[:commands] = text.tr( '^' + allowed, '' ) thread[:codepointer] = 0 thread[:tape] = String.new thread[:tape] << 0.chr thread[:pointer] = 0 thread[:otherpointer] = 0 thread[:running] = true thread[:local] = true thread[:functions] = Hash.new thread[:stack] = Array.new thread[:num] = @num += 1 thread[:jumps] = Hash.new(0) if $jumpfuck thread[:prevoutput] = String.new calc_parens( thread ) @threads << thread return thread end def calc_parens( thread ) thread[:leftsquares] = Hash.new thread[:rightsquares] = Hash.new thread[:rightrounds] = Hash.new thread[:leftrounds] = Hash.new prev_square = Array.new prev_round = Array.new (thread[:commandsize] = thread[:commands].size).times do |n| c = thread[:commands][n].chr if c == '[' then prev_square.push n elsif c == ']' jump = prev_square.unshift.pop thread[:leftsquares].store(jump, n) thread[:rightsquares].store(n, jump) elsif c == '(' then prev_round.push n elsif c == ')' jump = prev_round.unshift.pop thread[:leftrounds].store(jump, n) thread[:rightrounds].store(n, jump) end end end def run while (threads = @threads.select{ |t| t[:running] == true }).size > 0 threads.each do |thread| if thread[:local] then tape = thread[:tape]; pointer = :pointer else tape = @commontape ; pointer = :otherpointer end tape << 0.chr while tape.size <= thread[pointer] command = thread[:commands][thread[:codepointer]].chr here = thread[pointer] value = tape[here] if command == '~' then thread[:local] = thread[:local] ? false : true elsif command == '+' then tape[here] += 1 elsif command == '-' then tape[here] -= 1 elsif command == '>' then thread[pointer] += 1 elsif command == '<' then thread[pointer] -= 1 elsif command == ',' if $reversible if value == 0 then getinput else thread[:running] = false end else tape[here] = getinput end elsif command == '.' if $esoapi and thread[:prevoutput] == 0 if value == 1 then $esofile.seek( $sectorsize, IO::SEEK_CUR ) elsif value == 2 then $esofile.seek( -$sectorsize, IO::SEEK_CUR ) elsif value == 3 tape << 0.chr while tape.size <= (here + $sectorsize) tape[here + 1 .. here + $sectorsize] = $esofile.read($sectorsize) elsif value == 4 then $esofile.syswrite tape[here + 1 .. here + $sectorsize] elsif value == 5 then $esofile.seek( 0, IO::SEEK_SET ) elsif value == 8 then tape[here] = 0 elsif value > 9 and $ruby[value-10] then eval $ruby[value-10] else $stdout.putc value end thread[:prevoutput] = nil else $stdout.putc value end thread[:prevoutput] = value elsif command == '[' and (value == 0 or ($reversible and value != 0)) thread[:codepointer] = thread[:leftsquares][thread[:codepointer]] elsif command == ']' and value != 0 thread[:codepointer] = thread[:rightsquares][thread[:codepointer]] - 1 elsif $brainfork and command.downcase == 'y' fork = thread.dup tape[here] = 0 fork[:tape] = thread[:tape].dup fork[:codepointer] += 1 fork[:pointer] += 1 fork[:tape] << 0.chr while fork[:tape].size <= fork[:pointer] fork[:tape][fork[:pointer]] = 1 fork[:num] = @num += 1 @threads << fork elsif $pbrain and command == '(' thread[:functions][value] = thread[:codepointer] thread[:codepointer] = thread[:leftrounds][thread[:codepointer]] elsif $pbrain and command == ')' thread[:codepointer] = thread[:stack].pop elsif $pbrain and command == ':' and func_addr = thread[:functions][value] thread[:stack] << thread[:codepointer] thread[:codepointer] = func_addr elsif $braintwist and command == 'X' tmp = thread[:tape] thread[:tape] = thread[:commands] thread[:commands] = tmp calc_parens thread tmp = thread[:codepointer] thread[:codepointer] = thread[:pointer] - 1 thread[:pointer] = tmp elsif $braintwist and command == 'x' tmp = tape[here] tape[here] = thread[:commands][thread[:codepointer]] thread[:commands][thread[:codepointer]] = tmp elsif $jumpfuck and command == '&' thread[:jumps][value] = thread[:codepointer] tape[here] = thread[:jumparg] || 0 thread[:jumparg] = 0 elsif $jumpfuck and command == '%' thread[:jumparg] = tape[here + 1] || 0 thread[:codepointer] = thread[:jumps][value] end if (thread[:codepointer] += 1) >= thread[:commandsize] if prev = thread[:stack].pop then thread[:codepointer] = prev else thread[:running] = false end end if thread[:codepointer] < 0 then thread[:codepointer] = 0 end if $verbose then $stdout.puts "t#{thread[:num]} #{command} #{thread[:codepointer]}/#{thread[:pointer]} #{tape.inspect}" end end end if $verbose then $stderr.puts "\nMaster: "; p @commontape; @threads.each do |t| $stderr.puts "Thread: #{t[:num]}"; p t[:tape] end end end def getinput if $input if $input.size > 0 then return $input.unshift else return 0 end else return ($stdin.getc || 0) end end end $input = $verbose = $brainfork = $pbrain = false files = Array.new ARGV.each do |arg| if arg[0].chr == '-' arg[1..-1].split(//).each do |c| if c == 'v' then $verbose = true elsif c == 'y' or arg[1].chr == 'Y' then $brainfork = true elsif c == 'X' then $braintwist = true elsif c == 'x' then $braintwist2 = true elsif c == 'p' then $pbrain = true elsif c == 'r' then $reversible = true elsif c == 'j' then $jumpfuck = true elsif c == 'E' then $esoapi = true elsif c == 'R' then $ruby = Hash.new end end else files << arg end end unless files.size > 0 then $stderr.puts 'Usage: weave.rb [-v] [-Y] filename.txt'; exit end if $esoapi then begin $esofile = File.open('esoapi.dsk', File::RDWR|File::CREAT) rescue $stderr.puts 'Could not open esoapi.dsk, turning off EsoAPI support' $esoapi = false end end parser = WeaverParser.new files.each do |filename| program = String.new File.open(filename, 'r') do |f| until f.eof? line = f.gets.chomp if line =~ /#\s*ruby\s*(\d+)? (.*)/ then $ruby[$1.to_i] ||= ''; $ruby[$1.to_i] << $2 + "\n" end program << line.gsub(/#.*/,'') end end # again per bf, bang means input, not new thread program.split(/;/).each do |commands| if commands =~ /(.*)!(.*)/ then commands = $1; $input ||= Array.new; $input << $2; $textinput = true end parser.addthread(commands) end end parser.run