├── BraceCheck.rb ├── BrainFlakError.rb ├── BrainFlakInterpreter.rb ├── BrainFlueueInterpreter.rb ├── ClassicInterpreter.rb ├── Flag.rb ├── Interpreter.rb ├── LICENSE ├── README.md ├── brain_flak.rb └── stack.rb /BraceCheck.rb: -------------------------------------------------------------------------------- 1 | require_relative './BrainFlakError.rb' 2 | 3 | def braceCheck(source) 4 | matches = ["()","[]","<>","{}"] 5 | stack = [] 6 | checking = true 7 | source.split("").each_with_index do |char, i| 8 | if char == "#" 9 | checking = false 10 | elsif char == "\n" 11 | checking = true 12 | end 13 | 14 | if not checking 15 | true #Do nothing 16 | elsif "([<{".include? char 17 | stack.push([char, i]) 18 | elsif ")]>}".include? char 19 | if stack.empty? 20 | raise BrainFlakError.new("Unopened '%s' character." % char,i) 21 | elsif matches.include? stack[-1][0]+char 22 | stack.pop 23 | else 24 | raise BrainFlakError.new("Expected to close '%s' from location %s but instead encountered '%s'." % [stack[-1][0],stack[-1][1],char], i) 25 | end 26 | end 27 | end 28 | if not stack.empty? 29 | raise BrainFlakError.new("Unclosed '%s' character." % stack[-1][0],stack[-1][1]) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /BrainFlakError.rb: -------------------------------------------------------------------------------- 1 | class BrainFlakError < StandardError 2 | 3 | attr_reader :cause, :pos 4 | 5 | def initialize(cause, pos) 6 | @cause = cause 7 | @pos = pos 8 | super("Error at character %d: %s" % [pos, cause]) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /BrainFlakInterpreter.rb: -------------------------------------------------------------------------------- 1 | require 'io/console' 2 | require_relative './stack.rb' 3 | require_relative './Flag.rb' 4 | require_relative './BrainFlakError.rb' 5 | require_relative './Interpreter.rb' 6 | 7 | class BrainFlakInterpreter < Interpreter 8 | 9 | # Nilads ~~~~~~~~~~~~~~~~~~~~~ 10 | 11 | def round_nilad() 12 | @current_value += 1 13 | end 14 | 15 | def square_nilad() 16 | @current_value += @active_stack.height 17 | end 18 | 19 | def curly_nilad() 20 | @current_value += @active_stack.pop 21 | end 22 | 23 | def angle_nilad() 24 | @active_stack = @active_stack == @left ? @right : @left 25 | end 26 | 27 | # Open Braces ~~~~~~~~~~~~~~~~ 28 | 29 | def open_round() 30 | @main_stack.push(['(', @current_value, @index]) 31 | @current_value = 0 32 | end 33 | 34 | def open_square() 35 | @main_stack.push(['[', @current_value, @index]) 36 | @current_value = 0 37 | end 38 | 39 | def open_curly() 40 | @main_stack.push(['{', 0, @index]) 41 | new_index = read_until_matching(@source, @index) 42 | if active_stack.peek == 0 then 43 | @main_stack.pop() 44 | @index = new_index 45 | @cycles += 1 46 | end 47 | end 48 | 49 | def open_angle() 50 | @main_stack.push(['<', @current_value, @index]) 51 | @current_value = 0 52 | end 53 | 54 | # Close Braces ~~~~~~~~~~~~~~~ 55 | 56 | def close_round() 57 | data = @main_stack.pop() 58 | @active_stack.push(@current_value) 59 | @current_value += data[1] 60 | end 61 | 62 | def close_square() 63 | data = @main_stack.pop() 64 | @current_value *= -1 65 | @current_value += data[1] 66 | end 67 | 68 | def close_curly() 69 | data = @main_stack.pop() 70 | @index = data[2] - 1 71 | @last_op = :close_curly 72 | @current_value += data[1] 73 | end 74 | 75 | def close_angle() 76 | data = @main_stack.pop() 77 | @current_value = data[1] 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /BrainFlueueInterpreter.rb: -------------------------------------------------------------------------------- 1 | require 'io/console' 2 | require_relative './stack.rb' 3 | require_relative './Flag.rb' 4 | require_relative './BrainFlakError.rb' 5 | require_relative './Interpreter.rb' 6 | 7 | class BrainFlueueInterpreter < Interpreter 8 | 9 | # Nilads ~~~~~~~~~~~~~~~~~~~~~ 10 | 11 | def round_nilad() 12 | @current_value += 1 13 | end 14 | 15 | def square_nilad() 16 | @current_value += @active_stack.height 17 | end 18 | 19 | def curly_nilad() 20 | @current_value += @active_stack.pop 21 | end 22 | 23 | def angle_nilad() 24 | @active_stack = @active_stack == @left ? @right : @left 25 | end 26 | 27 | # Open Braces ~~~~~~~~~~~~~~~~ 28 | 29 | def open_round() 30 | @main_stack.push(['(', @current_value, @index]) 31 | @current_value = 0 32 | end 33 | 34 | def open_square() 35 | @main_stack.push(['[', @current_value, @index]) 36 | @current_value = 0 37 | end 38 | 39 | def open_curly() 40 | @main_stack.push(['{', 0, @index]) 41 | new_index = read_until_matching(@source, @index) 42 | if active_stack.data.first == 0 or active_stack.data.first == nil then 43 | @main_stack.pop() 44 | @index = new_index 45 | end 46 | end 47 | 48 | def open_angle() 49 | @main_stack.push(['<', @current_value, @index]) 50 | @current_value = 0 51 | end 52 | 53 | # Close Braces ~~~~~~~~~~~~~~~ 54 | 55 | def close_round() 56 | data = @main_stack.pop() 57 | @active_stack.data.unshift(@current_value) 58 | @current_value += data[1] 59 | end 60 | 61 | def close_square() 62 | data = @main_stack.pop() 63 | @current_value *= -1 64 | @current_value += data[1] 65 | end 66 | 67 | def close_curly() 68 | data = @main_stack.pop() 69 | @index = data[2] - 1 70 | @last_op = :close_curly 71 | @current_value += data[1] 72 | end 73 | 74 | def close_angle() 75 | data = @main_stack.pop() 76 | @current_value = data[1] 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /ClassicInterpreter.rb: -------------------------------------------------------------------------------- 1 | require 'io/console' 2 | require_relative './stack.rb' 3 | require_relative './Flag.rb' 4 | require_relative './BrainFlakError.rb' 5 | require_relative './Interpreter.rb' 6 | 7 | class ClassicInterpreter < Interpreter 8 | 9 | def initialize(source, left_in, right_in, debug, max_cycles, ascii_mode) 10 | super(source, left_in, right_in, debug, max_cycles) 11 | @ascii_mode = ascii_mode 12 | end 13 | 14 | # Nilads ~~~~~~~~~~~~~~~~~~~~~ 15 | 16 | def round_nilad() 17 | @current_value += 1 18 | end 19 | 20 | def square_nilad() 21 | @current_value -= 1 22 | end 23 | 24 | def curly_nilad() 25 | @current_value += @active_stack.pop 26 | end 27 | 28 | def angle_nilad() 29 | @active_stack = @active_stack == @left ? @right : @left 30 | end 31 | 32 | # Open Braces ~~~~~~~~~~~~~~~~ 33 | 34 | def open_round() 35 | @main_stack.push(['(', @current_value, @index]) 36 | @current_value = 0 37 | end 38 | 39 | def open_square() 40 | @main_stack.push(['[', @current_value, @index]) 41 | @current_value = 0 42 | end 43 | 44 | def open_curly() 45 | @main_stack.push(['{', 0, @index]) 46 | new_index = read_until_matching(@source, @index) 47 | if active_stack.peek == 0 then 48 | @main_stack.pop() 49 | @index = new_index 50 | end 51 | end 52 | 53 | def open_angle() 54 | @main_stack.push(['<', @current_value, @index]) 55 | @current_value = 0 56 | end 57 | 58 | # Close Braces ~~~~~~~~~~~~~~~ 59 | 60 | def close_round() 61 | data = main_stack.pop() 62 | @active_stack.push(@current_value) 63 | @current_value += data[1] 64 | end 65 | 66 | def close_square() 67 | data = main_stack.pop() 68 | if @ascii_mode 69 | print (@current_value % 2 ** 32).chr(Encoding::UTF_8) 70 | else 71 | puts @current_value 72 | end 73 | @current_value += data[1] 74 | end 75 | 76 | def close_curly() 77 | data = @main_stack.pop() 78 | @index = data[2] - 1 79 | @last_op = :close_curly 80 | @current_value += data[1] 81 | end 82 | 83 | def close_angle() 84 | data = main_stack.pop() 85 | @current_value = data[1] 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /Flag.rb: -------------------------------------------------------------------------------- 1 | 2 | class DebugFlag 3 | def initialize(name, data) 4 | @name = name 5 | if /'.'/.match(data) then 6 | @data = data[1].ord 7 | elsif /\d+/.match(data) then 8 | @data = data.to_i 9 | elsif data == "" then 10 | @data = 0 11 | else 12 | raise BrainFlakError.new("Parsing Error (This should not happen)",0) 13 | end 14 | end 15 | def to_s 16 | return @name 17 | end 18 | def get_data 19 | return @data 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Interpreter.rb: -------------------------------------------------------------------------------- 1 | require 'io/console' 2 | require_relative './stack.rb' 3 | require_relative './Flag.rb' 4 | require_relative './BrainFlakError.rb' 5 | 6 | def read_until_matching(s, start) 7 | stack_height = 0 8 | s[start + 1..s.length].each_char.with_index(1) do |c, i| 9 | case c 10 | when '{' then stack_height += 1 11 | when '}' then 12 | stack_height -= 1 13 | if stack_height == -1 then 14 | return i + start 15 | end 16 | end 17 | end 18 | return nil 19 | end 20 | 21 | class Interpreter 22 | 23 | attr_accessor :current_value, :active_stack 24 | attr_reader :running, :left, :right, :main_stack 25 | 26 | def initialize(source, left_in, right_in, debug, max_cycles) 27 | # Strip comments 28 | source = source.gsub(/(^[^#]*)#.*(\n|$)/, '\1') 29 | # Strips the source of any characters that aren't brackets (or part of debug flags in debug mode) 30 | if debug then 31 | @source = source.gsub(/(?<=^|[()\[\]<>{}]|\s)[^@()\[\]<>{}\s]*/, "") 32 | # Strips extra whitespace 33 | @source = @source.gsub(/\s/,"") 34 | # Strips extra @s 35 | @source = @source.gsub(/@+(?=[()\[\]<>{}]|$)/, "") 36 | else 37 | @source = source.gsub(/[^()\[\]<>{}]*/, "") 38 | end 39 | @left = Stack.new('Left') 40 | @right = Stack.new('Right') 41 | @main_stack = [] 42 | @active_stack = @left 43 | @index = 0 44 | @current_value = 0 45 | # Hash.new([]) does not work since modifications change that original array 46 | @debug_flags = Hash.new{|h,k| h[k] = []} 47 | @last_op = :none 48 | @cycles = 0 49 | @max_cycles = max_cycles 50 | left_in.each do|a| 51 | @left.push(a) 52 | end 53 | right_in.each do|a| 54 | @right.push(a) 55 | end 56 | remove_debug_flags(debug) 57 | @running = @source.length > 0 58 | @run_debug = !@running && @debug_flags.size > 0 59 | end 60 | 61 | def inactive_stack 62 | return @active_stack == @left ? @right : @left 63 | end 64 | 65 | def remove_debug_flags(debug) 66 | while match = /@[^@()\[\]{}<>\s]+/.match(@source) do 67 | str = @source.slice!(match.begin(0)..match.end(0)-1) 68 | slicer = /@[^'\d]*/.match(str) 69 | flag = str.slice(1..slicer.end(0)-1) 70 | data = str.slice(slicer.end(0)..-1) 71 | if /(\d*|'.')/.match(data).end(0) != data.length then 72 | raise BrainFlakError.new("Invalid data, %s, in flag, @%s" % [data,flag],match.begin(0)) 73 | end 74 | if debug then 75 | @debug_flags[match.begin(0)] = @debug_flags[match.begin(0)].push(DebugFlag.new(flag,data)) 76 | end 77 | end 78 | end 79 | 80 | def debug_info_full(ascii_mode) 81 | builder = "" 82 | if ascii_mode then 83 | limit=2**32 84 | builder += @active_stack == @left ? "^\n" : " ^\n" 85 | for i in 0..[@left.height,@right.height].max do 86 | c_right = (@right.at(i) != nil ? @right.at(i) : 32)%limit 87 | c_left = (@left.at(i) != nil ? @left.at(i) : 32)%limit 88 | builder = (c_left.chr(Encoding::UTF_8)).ljust(2) + c_right.chr(Encoding::UTF_8) + "\n" + builder 89 | end 90 | else 91 | if @left.height > 0 then 92 | max_left = @left.get_data.map { |item| item.to_s.length }.max 93 | else 94 | max_left = 1 95 | end 96 | for i in 0..[@left.height,@right.height].max do 97 | builder = @left.at(i).to_s.ljust(max_left+1) + @right.at(i).to_s + "\n" + builder 98 | end 99 | if @active_stack == @left then 100 | builder += "^\n" 101 | else 102 | builder += " "*(max_left+1) + "^\n" 103 | end 104 | end 105 | builder = ("Cycles:\n%d\nProgram:\n" % @cycles) + inspect + "\nStack:" + builder 106 | return builder 107 | end 108 | 109 | def do_debug_flag(index) 110 | @debug_flags[index].each do |flag| 111 | STDERR.print "@%s " % flag.to_s 112 | case flag.to_s 113 | when "dv" then STDERR.puts @current_value 114 | when "av" then STDERR.puts (@current_value%256).chr(Encoding::UTF_8) 115 | when "uv" then STDERR.puts (@current_value%2**32).chr(Encoding::UTF_8) 116 | when "dc","ac" then 117 | STDERR.print @active_stack == @left ? "(left) " : "(right) " 118 | case flag.to_s 119 | when "dc" then 120 | STDERR.puts @active_stack.inspect_array 121 | when "ac" then 122 | STDERR.puts @active_stack.char_inspect_array(2**32) 123 | end 124 | when "dl" then STDERR.puts @left.inspect_array 125 | when "al" then STDERR.puts @left.char_inspect_array(2**32) 126 | when "dr" then STDERR.puts @right.inspect_array 127 | when "ar" then STDERR.puts @right.char_inspect_array(2**32) 128 | when "df" then STDERR.puts debug_info_full(false) 129 | when "af" then STDERR.puts debug_info_full(true) 130 | when "cy" then STDERR.puts @cycles 131 | when "ij" then 132 | injection = $stdin.read 133 | STDERR.puts 134 | sub_interpreter = BrainFlakInterpreter.new(injection, @left.get_data, @right.get_data, true) 135 | sub_interpreter.active_stack = @active_stack == @left ? sub_interpreter.left : sub_interpreter.right 136 | sub_interpreter.current_value = @current_value 137 | begin 138 | while sub_interpreter.step 139 | end 140 | if sub_interpreter.main_stack.length > 0 141 | unmatched_brak = sub_interpreter.main_stack[0] 142 | raise BrainFlakError.new("Unmatched '%s' character." % unmatched_brak[0], unmatched_brak[2] + 1) 143 | end 144 | rescue Interrupt 145 | STDERR.puts "\nKeyboard Interrupt" 146 | STDERR.puts sub_interpreter.inspect 147 | raise "Second Interrupt" 148 | rescue RuntimeError => e 149 | if e.to_s == "Second Interrupt" then 150 | STDERR.puts sub_interpreter.inspect 151 | end 152 | raise e 153 | end 154 | @left.set_data(sub_interpreter.left.get_data) 155 | @right.set_data(sub_interpreter.right.get_data) 156 | @active_stack = sub_interpreter.active_stack == sub_interpreter.left ? @left : @right 157 | @current_value = sub_interpreter.current_value 158 | when "dh" then STDERR.puts @active_stack.height 159 | when "lt" then 160 | STDERR.print "\n" 161 | @current_value += flag.get_data 162 | when "pu" then 163 | #Take input 164 | $stdin.gets 165 | when "ex" then 166 | #Exit program 167 | @running = false 168 | #Clear the stack to prevent error 169 | @main_stack = [] 170 | #Add a newline for formatting 171 | STDERR.puts 172 | end 173 | STDERR.flush 174 | end 175 | end 176 | 177 | def step() 178 | if @run_debug or @running then 179 | if @last_op == :nilad then 180 | do_debug_flag(@index-1) 181 | end 182 | if @last_op != :close_curly then 183 | do_debug_flag(@index) 184 | end 185 | @run_debug = false 186 | end 187 | if !@running then 188 | return false 189 | end 190 | @cycles += 1 191 | if @max_cycles >= 0 and @cycles >= @max_cycles then 192 | raise BrainFlakError.new("Maximum cycles exceeded", @index + 1) 193 | end 194 | current_symbol = @source[@index..@index+1] or @source[@index] 195 | if ['()', '[]', '{}', '<>'].include? current_symbol 196 | case current_symbol 197 | when '()' then round_nilad() 198 | when '[]' then square_nilad() 199 | when '{}' then curly_nilad() 200 | when '<>' then angle_nilad() 201 | end 202 | @last_op = :nilad 203 | @index += 2 204 | else 205 | @last_op = :monad 206 | current_symbol = current_symbol[0] 207 | if is_opening_bracket?(current_symbol) then 208 | case current_symbol 209 | when '(' then open_round() 210 | when '[' then open_square() 211 | when '<' then open_angle() 212 | when '{' then open_curly() 213 | end 214 | 215 | elsif is_closing_bracket?(current_symbol) then 216 | 217 | case current_symbol 218 | when ')' then close_round() 219 | when ']' then close_square() 220 | when '>' then close_angle() 221 | when '}' then close_curly() 222 | end 223 | else raise BrainFlakError.new("Invalid character '%s'." % current_symbol, @index + 1) 224 | end 225 | @index += 1 226 | end 227 | if @index >= @source.length then 228 | @running = false 229 | if @last_op == :nilad then 230 | do_debug_flag(@index-1) 231 | end 232 | do_debug_flag(@index) 233 | end 234 | return true 235 | end 236 | 237 | def debug_info 238 | source = String.new(str=@source) 239 | offset = 0 240 | return "Cycles: %1$d\n"\ 241 | "Current value: %2$d\n"\ 242 | "%6$s Left stack: %3$s\n"\ 243 | "%7$sRight stack: %4$s\n"\ 244 | "Execution stack: %5$p\n"\ 245 | % [@cycles, @current_value, @left.inspect_array, @right.inspect_array, @main_stack, *@active_stack == @left ? ["> ", " "] : [" ", "> "]] 246 | end 247 | 248 | def inspect 249 | source = String.new(str=@source) 250 | index = @index 251 | offset = 0 252 | @debug_flags.each_pair do |k,v| 253 | v.each do |flag| 254 | source.insert(k + offset, "@%s" % flag.to_s); 255 | offset += flag.to_s.length + 1 256 | if k <= index then 257 | index += flag.to_s.length + 1 258 | end 259 | end 260 | end 261 | result = "" 262 | begin 263 | winWidth = IO.console.winsize[1] 264 | rescue NoMethodError 265 | #If no winsize can be found we default to 20 266 | winWidth = 20 267 | end 268 | if source.length <= winWidth then 269 | result += "%s\n%s" % [source, "^".rjust(index + 1)] 270 | elsif index < winWidth/2 then 271 | result += "%s...\n%s" % [source[0..winWidth-4],"^".rjust(index + 1)] 272 | elsif source.length - index < winWidth/2 then 273 | result += "...%s\n%s" % [source[-(winWidth-3)..-1],"^".rjust(winWidth-(source.length-index))] 274 | else 275 | result += "...%s...\n%s" % [source[3+index-winWidth/2..winWidth/2+index-4],"^".rjust(winWidth/2+1)] 276 | end 277 | return result + "\nCharacter %s" % [@index + 1] 278 | end 279 | end 280 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 James Harrington 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brain-Flak 2 | 3 | Brain-Flak is an "Turing-tarpit", e.g. a language which can, *in theory* compute anything, but in reality is very inconvenient and painful to use. It was heavily inspired by [Brainf**k](https://esolangs.org/wiki/Brainfuck), the original turing-tarpit. 4 | 5 | To get started with Brain-Flak, download the project from here, and run `ruby brain_flak.rb inputs`. Additionally, you can [try it online](http://brain-flak.tryitonline.net/)! (Online intrepreter provided thanks to [@DennisMitchell](https://github.com/DennisMitchell)) 6 | 7 | # Tutorial 8 | 9 | Brain-Flak has two stacks, known as 'left' and 'right'. The active stack starts at left. If an empty stack is popped, it will return 0. That's it. No other variables. When the program starts, each command line argument is pushed on to the active stack. 10 | 11 | The only valid characters in a Brain-Flak program are `()[]{}<>`, and they must always be balanced. There are two types of functions: *Nilads* and *Monads*. A *nilad* is a function that takes 0 arguments. Here are all of the nilads: 12 | 13 | - `()` Evaluates to one. 14 | - `[]` Evaluates to the height of the current stack. 15 | - `{}` Pop the active stack. Evaluates to the popped value. 16 | - `<>` Toggle the active stack. Evaluates to zero. 17 | 18 | These commands are added together when they are evaluated. So if we had a '3' on top of the active stack, this snippet: 19 | 20 | ()(){} 21 | 22 | would evaluate to `1 + 1 + active.pop()` which would evaluate to 5. 23 | 24 | The monads take one argument, a chunk of Brain-Flak code. Here are all of the monads: 25 | 26 | - `(n)` Push 'n' on the active stack. 27 | - `[n]` Evaluates to negative 'n' 28 | - `{foo}` While zero is not on the top of the stack, do foo. 29 | - `` Execute foo, but evaluate it as 0. 30 | 31 | The `(...)` monad will also evaluate to its argument, so 32 | 33 | (()()()) 34 | 35 | Will push 3 but 36 | 37 | ((()()())) 38 | 39 | Will push 3 *twice*. 40 | 41 | The `{...}` monad will evaluate to the sum of all runs. So if we had '3' and '4' on the top of the stack: 42 | 43 | {{}} 44 | 45 | would evaluate as 7. 46 | 47 | When the program is done executing, each value left on the active stack is printed, with a newline between. Values on the other stack are ignored. 48 | 49 | That's it. That's the whole language. 50 | 51 | # Sample code 52 | 53 | Here are some full programs that do interesting things. 54 | 55 | ### Adding two numbers: 56 | 57 | ({}{}) 58 | 59 | ### Multiplying two numbers (Positive only): 60 | 61 | {({}<(({})<>{})<>>[()])}<> 62 | 63 | ### Square a number (Positive only): 64 | 65 | ({({})({}[()])}{}) 66 | 67 | ### Print the first *N* Fibonacci numbers: 68 | 69 | <>((()))<>{({}[()])<>({}<>)<>(({})<>({}<>))<>}<>{}{} 70 | -------------------------------------------------------------------------------- /brain_flak.rb: -------------------------------------------------------------------------------- 1 | require_relative './stack.rb' 2 | require_relative './BrainFlakInterpreter.rb' 3 | require_relative './BrainFlueueInterpreter.rb' 4 | require_relative './ClassicInterpreter.rb' 5 | require_relative './BraceCheck.rb' 6 | 7 | VERSION_STRING = "Brain-Flak Ruby Interpreter v1.5.2" 8 | 9 | require 'optparse' 10 | 11 | debug = false 12 | do_in = true 13 | do_out = true 14 | ascii_in = false 15 | ascii_out = false 16 | reverse = false 17 | arg_path = "" 18 | max_cycles = -1 19 | mode = "brainflak" 20 | from_file = true 21 | 22 | parser = OptionParser.new do |opts| 23 | opts.banner = "\nBrain-Flak Ruby Interpreter\n"\ 24 | "Usage:\n"\ 25 | "\tbrain_flak [options] source_file args...\n\n" 26 | 27 | opts.on("-d", "--debug", "Enables parsing of debug commands.") do 28 | debug = true 29 | end 30 | 31 | opts.on("-H", "--help-debug", "Prints a list of debug flags and what they do.") do 32 | flag_desc= [ 33 | ["ac","Prints the current stack as ASCII characters"], 34 | ["al","Prints the left stack as ASCII characters"], 35 | ["av","Prints the current value of the scope as an ASCII character"], 36 | ["ar","Prints the right stack as ASCII characters"], 37 | ["cy","Prints the number of elapsed cycles"], 38 | ["dc","Prints the current stack in decimal"], 39 | ["dh","Prints the height of the current stack in decimal"], 40 | ["dl","Prints the left stack in decimal"], 41 | ["dv","Prints the current value of the scope in decimal"], 42 | ["dr","Prints the right stack in decimal"], 43 | ["ex","Terminates the program"], 44 | ["ij","Pauses program and prompts user for Brain-Flak code to be run in place of the flag"], 45 | ["lt","Passed with a number after the @lt (e.g. @lt6). Evaluates to the number passed with it"], 46 | ["pu","Pauses the program until the user hits the return key"], 47 | ] 48 | flag_desc.each do | flag | 49 | STDERR.puts " "+flag[0]+": "+flag[1] 50 | end 51 | end 52 | 53 | opts.on("-f", "--file=FILE", "Reads input for the brain-flak program from FILE, rather than from the command line.") do |file| 54 | arg_path = file 55 | end 56 | 57 | opts.on("-l","--language=LANGUAGE", "Changes the language to be interpreted. Brain-Flak is the default but Miniflak, Brain-Flueue and Brain-Flak-Classic are also options.") do |lang| 58 | mode = lang[0..-1].downcase.strip.gsub("-fl","fl") 59 | end 60 | 61 | opts.on("-a", "--ascii-in", "Take input in character code points and output in decimal. This overrides previous -A and -c flags.") do 62 | ascii_in = true 63 | ascii_out = false 64 | end 65 | 66 | opts.on("-A", "--ascii-out", "Take input in decimal and output in character code points. This overrides previous -a and -c flags.") do 67 | ascii_in = false 68 | ascii_out = true 69 | end 70 | 71 | opts.on("-c", "--ascii", "Take input and output in character code points, rather than in decimal. This overrides previous -a and -A flags.") do 72 | ascii_in = true 73 | ascii_out = true 74 | end 75 | 76 | opts.on("-n","--no-in", "Input is ignored.") do 77 | do_in = false 78 | end 79 | 80 | opts.on("-N","--no-out", "No output is produced. Debug flags and error messages still appear.") do 81 | do_out = false 82 | end 83 | 84 | opts.on("-r", "--reverse", "Reverses the order that arguments are pushed onto the stack AND that values are printed at the end.") do 85 | reverse = true 86 | end 87 | 88 | opts.on("-h", "--help", "Prints info on the command line usage of Brain-Flak and then exits.") do 89 | STDERR.puts opts 90 | exit 91 | end 92 | 93 | opts.on("-v", "--version", "Prints the version of the Brain-Flak interpreter and then exits.") do 94 | STDERR.puts VERSION_STRING 95 | exit 96 | end 97 | 98 | opts.on("-m", "--max-cycles=MAX", "Sets the maximum cycles. If exceeded the program will terminate.") do |maximum| 99 | #Can cause errors 100 | max_cycles = maximum.to_i 101 | end 102 | 103 | opts.on("-e", "--execute", "Executes the first command line argument as Brain-Flak code.") do 104 | from_file = false 105 | end 106 | 107 | end 108 | 109 | begin 110 | parser.order! 111 | rescue OptionParser::ParseError => e 112 | STDERR.puts e 113 | STDERR.puts "\n" 114 | STDERR.puts parser 115 | exit 116 | end 117 | 118 | if ARGV.length < 1 then 119 | STDERR.puts parser 120 | exit 121 | end 122 | 123 | source_path = ARGV[0] 124 | #No input 125 | if !do_in then 126 | numbers = [] 127 | #Input from file 128 | elsif arg_path != "" then 129 | input_file = File.open(arg_path, 'r:UTF-8') 130 | if !ascii_in 131 | numbers = input_file.read.gsub(/\s+/m, ' ').strip.split(" ") 132 | else 133 | numbers = input_file.read.split("") 134 | end 135 | #Input from command line 136 | else 137 | if ascii_in 138 | numbers = ARGV[1..-1].join(" ").split("") 139 | else 140 | numbers = ARGV[1..-1] 141 | end 142 | end 143 | 144 | if debug then 145 | STDERR.puts "Debug mode... ENGAGED!" 146 | end 147 | 148 | if from_file then 149 | source_file = File.open(source_path, 'r') 150 | source = source_file.read 151 | source_length = source.length 152 | else 153 | source = source_path # contains ARGV[0] 154 | end 155 | 156 | begin 157 | if !ascii_in 158 | numbers.each do |a| 159 | raise BrainFlakError.new("Invalid integer in input: \"%s\"" % [a], 0) if !(a =~ /^-?\d+$/) 160 | end 161 | numbers.map!(&:to_i) 162 | else 163 | numbers.map!(&:ord) 164 | end 165 | numbers.reverse! if !reverse 166 | 167 | #Check the braces are matched 168 | braceCheck(source) 169 | 170 | case mode 171 | when "brainflak" 172 | interpreter = BrainFlakInterpreter.new(source, numbers, [], debug, max_cycles) 173 | when "classic" 174 | interpreter = ClassicInterpreter.new(source, numbers, [], debug, max_cycles, ascii_out) 175 | when "miniflak", "mini" 176 | source = source.gsub(/#.*(\n|$)/,"").gsub(/[^\[\]{}()]/,"") # Parsing is done here so that we can strip `[]` properly 177 | while source =~ /\[\]/ 178 | source = source.gsub("[]","") 179 | end 180 | interpreter = BrainFlakInterpreter.new(source, numbers, [], debug, max_cycles) 181 | when "brainflueue", "flueue" 182 | interpreter = BrainFlueueInterpreter.new(source, numbers, [], debug, max_cycles) 183 | else 184 | raise BrainFlakError.new("No language called '%s'." % mode, 0) 185 | end 186 | while interpreter.step 187 | end 188 | if interpreter.main_stack.length > 0 189 | unmatched_brak = interpreter.main_stack[0] 190 | raise BrainFlakError.new("Unmatched '%s' character." % unmatched_brak[0], unmatched_brak[2] + 1) 191 | end 192 | if do_out then 193 | begin 194 | #Output current state 195 | if debug then 196 | puts interpreter.debug_info_full(ascii_out) 197 | else 198 | interpreter.active_stack.print_stack(ascii_out, reverse) 199 | end 200 | rescue BrainFlakError => e 201 | if e.pos == -1 then 202 | #Catch an error from the stack, becuase the stack does not know where it is in the program 203 | #We remove the beginning of the message (everything before ": ") and make a new error 204 | #with the location being the end of the program 205 | raise BrainFlakError.new(e.message.split(": ")[1..-1].join(": "), source_length) 206 | else 207 | raise e 208 | end 209 | end 210 | end 211 | 212 | rescue BrainFlakError => e 213 | STDERR.puts e.message 214 | 215 | rescue Interrupt 216 | STDERR.puts "\nKeyboard Interrupt" 217 | STDERR.puts interpreter.inspect 218 | 219 | if debug then 220 | STDERR.puts interpreter.debug_info 221 | end 222 | 223 | rescue RuntimeError => e 224 | if e.to_s == "Second Interrupt" 225 | STDERR.puts interpreter.inspect 226 | if debug then 227 | STDERR.puts interpreter.debug_info 228 | end 229 | else 230 | raise e 231 | end 232 | end 233 | 234 | -------------------------------------------------------------------------------- /stack.rb: -------------------------------------------------------------------------------- 1 | require_relative './BrainFlakError.rb' 2 | 3 | class Stack 4 | def initialize(name) 5 | @name = name 6 | @data = [] 7 | end 8 | 9 | attr_reader :data 10 | 11 | def pop 12 | if @data.length != 0 then 13 | return @data.pop 14 | else 15 | return 0 16 | end 17 | end 18 | 19 | def push(current_value) 20 | @data.push(current_value) 21 | end 22 | 23 | def peek 24 | return @data.last || 0 25 | end 26 | 27 | def print_stack(ascii_mode, reverse) 28 | (reverse ? @data: @data.reverse).each do |value| 29 | if ascii_mode 30 | begin 31 | print (value % 2 ** 32).chr(Encoding::UTF_8) 32 | rescue RangeError => ex 33 | #Error at character -1 signals to the main level to insert the current location because stack doesn't know 34 | #where we are in the execution. -1 cannot be attained any other way. 35 | raise BrainFlakError.new("Value #{value} is out of range for UTF_8 encoding.",-1) 36 | end 37 | else 38 | print value.to_s + "\n" 39 | end 40 | end 41 | print "\n" if ascii_mode 42 | STDOUT.flush 43 | end 44 | 45 | def talk 46 | puts @name 47 | end 48 | 49 | def inspect_array 50 | return @data.inspect 51 | end 52 | 53 | def char_inspect_array(n) 54 | return @data.map {|a| (a%n).chr(Encoding::UTF_8)}.join("") 55 | end 56 | 57 | def height 58 | return @data.length 59 | end 60 | 61 | def at(index) 62 | return @data.at(index) 63 | end 64 | 65 | def get_data 66 | return @data 67 | end 68 | 69 | def set_data(data) 70 | @data = data 71 | end 72 | end 73 | 74 | def is_opening_bracket?(b) 75 | if b != nil then 76 | return '([{<'.include? b 77 | end 78 | end 79 | 80 | def is_closing_bracket?(b) 81 | if b != nil then 82 | return ')]}>'.include? b 83 | end 84 | end 85 | 86 | 87 | --------------------------------------------------------------------------------