Skip to content

Instantly share code, notes, and snippets.

@thomasjslone
Last active April 26, 2023 22:55
Show Gist options
  • Save thomasjslone/cc10954694570bad9587230c239676d3 to your computer and use it in GitHub Desktop.
Save thomasjslone/cc10954694570bad9587230c239676d3 to your computer and use it in GitHub Desktop.
## use this to eval files that were converted to hex
## script = [args[0]].pack('H*').to_s # convert hex string to regular string
##
## This parser is designed to run LinearRuby programs from script files (.lrb files).
## LinearRuby has unimplied rules that can allow programs to ruby on regular ruby and linear ruby, the rules
## can be broken but for the most part breaking them doesnt make sense because in LinearRuby we assume classes
## are useless and write ruby programs in a linear series of script blocks.
## These script files have a few unimplied rules you are expected to follow to create a proper linear ruby file:
##
## 1. No classes or glabals are defined, instance variables and constants replace them.
## 2. The file is composed of blocks of script seperated by '\n\n', so you cannot use it in your code in strings literally, instead use "\n"*2 .
## 3. 'jumpto' followed by an integer or instance variable jumps program flow to that block in the file, if a second instance variable or expression is included it will be the condition for the jump.
## 4. when you define methods they are always on main context
## 5. context is main for every block, the program ends when the counter reaches and runs the last block, or an 'exit' operator is found
##
##
# make sure the parser can access main if running outside rubinsystem program.
if defined?(MAIN) != "constant"; MAIN = self; end
class Parser
def initialize
@main = MAIN ## link to outside MAIN context
@state = "init" ## operation state of the parser class
@level = 0 ## recursion level for :leval also the same as program eval stack level
@file = nil ## path of current running script
end
#load and eval a LinearRuby file
def load_file *args ## path, overridecontextwith, passargs
if File.file?(args[0].to_s) == false; raise "No such file."; end
if File.readable?(args[0].to_s) == false; raise "File read permission denied by host."; end
script_path = args[0].to_s
if args[1].to_s != ""; context = args[1]; else; context = @main; end
begin; script = File.read(script_path); @file = script_path
rescue; raise "Failed to read script file."
end
begin; @state = "eval"; @file = ""
return self.leval(script,context,args[2])
rescue => e; @state = "excep"
raise "Unknown exception state: "+e.to_s+"\n"+e.backtrace.join("\n")
end
end
# eval LinearRuby script string
def leval *args # script, overridecontextwith, passargs
script = args[0].to_s
if args.length == 2 and args[1] != ""; context = args[1]; else; context = MAIN; end
if args.length == 3 and args[2].is_a?(Array); script_args = args[2]; else; script_args = []; end
running = true
blocks = script.split("\n\n")
returnval = []; exceptions = []; block = -1
while running do
@state = "eval"; @level += 1
block += 1
if blocks[block].to_s == ""; running = false; break
elsif blocks[block].to_s.downcase[0..5] == "jumpto"
if blocks[block].to_s[6..1].delete("\n 0123456789").empty?
block = ((blocks[block].to_s[6..-1].to_i)-1)
elsif blocks[block].to_s[6..-1].to_s[0] == "@"
begin; var = context.instance_eval(blocks[block].to_s[6..-1])
if var.is_a?(Integer) == false; raise "Error in file: "[email protected]_s+", block: "+block.to_s+", Invalid jump code, obj is not Integer.; block code: "+blocks[block].to_s; end
block = var-1
next
rescue; raise "Invalid jump code in file:"[email protected]_s+", line: "+block.to_s+"; block: "+"\""+blocks[block].to_s+"\""
end
end
next
else
begin; returnval << context.instance_eval(blocks[block])
rescue => e; exceptions << e.to_s+"\n"+e.backtrace.join("\n")
end
end
end; running = false; @state = "idle"; @level -= 1
if exceptions == []; return returnval
else; return [returnval,exceptions]
end
end
def parse_file(script)
if File.file?(script) == false; raise "No such file."; end
script = File.read(script)
self.parse(script)
end
def parse(script)
blocks = script.split("\n\n")
stack_keywords = ["def", "if", "case", "class","loop", "while", "for", "begin"]
stack_open = [0, 0, 0, 0, 0, 0, 0,0]
last_opened = []
stack_trace = [] ## keeps track of keywords opened and the ends after them
line_no = 0
defined_classes = []
defined_methods = []
global_vars = []
instance_vars = []
local_vars = []
statements = [] ## contains an array for each keyword block containing any statements from the block
unexpected_end = []
script_ok = true
blocks.each do |block|
if block.to_s.downcase[0..5] == "jumpto"
# Handle jump statements
elsif block.to_s == "exit"
# Handle exit statement
break # done parsing after exit
else
lines = block.split(";").join("\n").split("\n")
lines.each do |line| ; line_no += 1
line = line.gsub(/\s+/, " ")
if line[0] == " "; line = line[1..-1]; end
if line[0] == "#"
##comment line
elsif line[0..3] == "def "
stack_open[0] += 1
last_opened << "def"; stack_trace << "def "+line.split(" ")[1..-1].join(" ")
defined_methods << line.split(" ")[1]
elsif line[0..2] == "if "
stack_open[1] += 1
last_opened << "if"; stack_trace << "if "+line.split(" ")[1..-1].join(" ")
elsif line[0..4] == "case "
stack_open[2] += 1
last_opened << "case"; stack_trace << "case "+line.split(" ")[1..-1].join(" ")
elsif line[0..5] == "class "
stack_open[3] += 1
last_opened << "class"; stack_trace << "class "+line.split(" ")[1..-1].join(" ")
defined_classes << line.split(" ")[1]
elsif line[0..4] == "loop"
stack_open[4] += 1
last_opened << "loop"; stack_trace << "loop "+line.split(" ")[1..-1].join(" ")
elsif line[0..4] == "while"
stack_open[5] += 1
last_opened << "while"; stack_trace << "while "+line.split(" ")[1..-1].join(" ")
elsif line[0..2] == "for"
stack_open[6] += 1
last_opened << "for"; stack_trace << "for "+line.split(" ")[1..-1].join(" ")
elsif line[0..4] == "begin"
stack_open[7] += 1
last_opened << "begin"; stack_trace << "begin"
elsif line =~ /^@\w+\b/ # instance variable declaration
var_name = line.split("=")[0][1..-1] # remove the @ from the var name
instance_vars << "@"+var_name unless instance_vars.include?("@"+var_name)
elsif line =~ /^\$\w+\b/ # global variable declaration
var_name = line.split("=")[0][1..-1] # remove the $ from the var name
global_vars << "$"+var_name unless global_vars.include?("$"+var_name)
elsif line =~ /^\w+\b\s*=/ # local variable declaration
var_name = line.split("=")[0].strip
local_vars << var_name unless local_vars.include?(var_name)
elsif line.downcase.strip == "end"
# Close the most recent open block
stack_trace << "end"
if last_opened.any?
stack_open[stack_keywords.index(last_opened.last)] -= 1
last_opened.pop
else; unexpected_end << last_opened[-1].to_s+" @ line "+line_no.to_s
end
else
# Handle other statements
end
end
# Add a separator to the local variables array between blocks
local_vars << "" unless local_vars.empty?
instance_vars << "" unless instance_vars.empty?
global_vars << "" unless global_vars.empty?
defined_classes << "" unless defined_classes.empty?
defined_methods << "" unless defined_methods.empty?
end
end
# Check for stack errors
s=stack_open; s.delete(0)
if s.include?(1)
stack_keywords.each do |k|
if stack_open[stack_keywords.index(k)] == 1
puts "ERROR: Stack left open for keyword: "+k.to_s
script_ok = false
end
end
end
if unexpected_end != []
puts "ERROR: Unexpected 'end' in stack keywords: "+unexpected_end.to_s
script_ok = false
end
defined_classesl = defined_classes; defined_classesl.delete(""); defined_classesl = defined_classesl.length
defined_methodsl = defined_methods; defined_methodsl.delete(""); defined_methodsl = defined_methodsl.length
global_varsl = global_vars; global_varsl.delete(""); global_varsl = global_varsl.length
instance_varsl = instance_vars; instance_varsl.delete(""); instance_varsl = instance_varsl.length
local_varsl = local_vars; local_varsl.delete(""); local_varsl = local_varsl.length
puts "\nParse Results for file: "
puts "Blocks: "+blocks.length.to_s + " Logical Lines: "+line_no.to_s+"\n"
puts "Size: "+script.to_s.length.to_s+" bytes\n"
puts ""
if defined_classesl > 0
puts "Defined classes: "+defined_classesl.to_s+": "+defined_classes.join(", ")+"\n"
puts ""
end
if global_varsl > 0
puts "Global variables: "+global_varsl.to_s+": "+global_vars.join(", ")+"\n"
puts ""
end
puts "Defined methods: "+defined_methodsl.to_s+": "+defined_methods.join(", ")+"\n"
puts ""
puts "Instance variables: "+instance_varsl.to_s+": "+instance_vars.join(", ")+"\n"
puts ""
puts "Local variables: "+local_varsl.to_s+": "+local_vars.join(", ")+"\n"
puts ""
puts "Stack trace: "+stack_trace.length.to_s+" :"+stack_trace.to_s+"\n"
puts ""
puts "Script pass: "+script_ok.to_s
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment