User:JayCampbell/weave.rb

From Esolang
Jump to navigation Jump to search

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