Skip to content

Instantly share code, notes, and snippets.

@IceDragon200
Created January 12, 2015 02:36
Show Gist options
  • Save IceDragon200/68235459eb7ccd6ea103 to your computer and use it in GitHub Desktop.
Save IceDragon200/68235459eb7ccd6ea103 to your computer and use it in GitHub Desktop.
My sad attempt at writing a set of parsers for minecraft mod configs...
require 'pp'
require 'rltk/parser'
require 'rltk/lexer'
require 'rltk/ast'
require 'json'
module ConfigReaders
# OpenComputers has a simple config file format
# lines starting with # are comments (ignoring spaces)
# lines ending with { are objects
# lines ending with [ are arrays
# lines ending with a , are array elements
module OpenComputers
class Lexer < RLTK::Lexer
match_first
rule(/\#/) { push_state(:comment) }
rule(/\n/, :comment) { pop_state }
rule(/./, :comment)
rule(/([\+\-]?\d+\.\d+)/) { |t| [:NUMBER, t.to_f] }
rule(/([\+\-]?\d+)/) { |t| [:NUMBER, t.to_i] }
rule(/(true|false)/) { |t| [:BOOLEAN, t == 'true'] }
rule(/\,/) { :COMMA }
rule(/\=/) { :EQUAL }
rule(/\{/) { :LCB }
rule(/\}/) { :RCB }
rule(/\[/) { :LSB }
rule(/\]/) { :RSB }
rule(/\"\"/) { [:STRING, ''] }
rule(/\"/) { push_state(:string) }
rule(/([^\"]+)/, :string) { |t| [:STRING, t] }
rule(/\"/, :string) { pop_state }
rule(/([A-Za-z_][A-Za-z_0-9]*)/) { |t| [:IDENT, t] }
rule(/\n/)
rule(/\s+/)
end
class Expression < RLTK::ASTNode
end
class EObject < Expression
value :name, String
value :contents, Array
end
class EValue < Expression
end
class ENumber < EValue
value :v, Numeric
end
class EString < EValue
value :v, String
end
class EBoolean < EValue
value :v, Object
end
class EIdentifier < EValue
value :v, String
end
class EVariable < Expression
value :name, String
value :v, Expression
end
class EList < Expression
value :contents, Array
end
class Parser < RLTK::Parser
production(:input, 'e') { |e| e }
production(:e, 'ocontent_list') { |o| o }
production(:ocontent) do
clause('assign') { |s| [s] }
clause('object') { |o| [o] }
end
production(:ocontent_list) do
clause('ocontent') { |s| s }
clause('ocontent ocontent_list') { |s, o| s + o }
end
production(:assign, 'IDENT EQUAL value') { |name, _, value| EVariable.new(name, value) }
#production(:assign) do
# clause('IDENT EQUAL value') { |name, _, value| EVariable.new(name, value) }
# clause('IDENT EQUAL IDENT') { |name, _, ident| EVariable.new(name, EIdentifier.new(ident)) }
#end
production(:object, 'IDENT LCB ocontent_list RCB') { |name, _, content, _| EObject.new(name, content) }
production(:value) do
clause('NUMBER') { |n| ENumber.new(n) }
clause('STRING') { |n| EString.new(n) }
clause('IDENT') { |n| EIdentifier.new(n) }
clause('BOOLEAN') { |n| EBoolean.new(n) }
clause('array') { |a| a }
end
production(:array, 'LSB array_contents RSB') { |_, l, _| EList.new(l) }
production(:array_contents) do
clause('') { [] }
clause('value') { |v| [v] }
clause('value COMMA array_contents') { |v, _, a| [v] + a }
end
finalize
end
def self.make(data, depth = 0)
case data
when Array
data.map { |s| make(s, depth + 1) }
when EList
make(data.contents, depth + 1)
when EObject
{
data.name => make(data.contents, depth + 1).reduce({}, &:merge)
}
when EVariable
{
data.name => make(data.v, depth + 1)
}
when EValue
data.v
end
end
def self.load_file(filename)
File.open(filename, 'r') do |f|
lex = Lexer.new
parser = Parser.new
make(parser.parse(lex.lex(f.read)))
end
end
end
module Common
class Lexer < RLTK::Lexer
match_first
rule(/\n/, :default, [:array]) { unset_flag(:varvalue); :NEWLINE }
rule(/\n/) { unset_flag(:varvalue); nil }
rule(/\=/) { unset_flag(:varname); set_flag(:varvalue); :EQUAL }
rule(/\{/) { :LCB }
rule(/\}/) { :RCB }
rule(/\</) { unset_flag(:varname); set_flag(:array); :LAB }
rule(/\>/) { unset_flag(:array); :RAB }
rule(/\s+/)
rule(/\#/) { push_state(:comment) }
rule(/\n/, :comment) { pop_state }
rule(/./, :comment)
rule(/\"/) { push_state(:string) }
rule(/([^\"]+)/, :string, [:varname]) { |t| [:IDENT, t] }
rule(/([^\"]+)/, :string) { |t| [:STRING, t] }
rule(/\"/, :string) { pop_state }
# type annotation
rule(/[\w]:/) { |t| set_flag(:varname); [:TYPE, t[0]] }
rule(/([^\=\<\n\s]+)/, :default, [:varname]) { |t| [:IDENT, t] }
rule(/([^\n\{]+)/, :default, [:varvalue]) { |t| unset_flag(:varvalue); [:STRING, t] }
# values
rule(/(true|false)/) { |t| unset_flag(:varvalue); [:BOOLEAN, t == 'true'] }
rule(/(null)/) { |t| unset_flag(:varvalue); :NULL }
# All other idents
rule(/([A-Za-z_][A-Za-z_\:\.0-9]+)/) { |t| unset_flag(:varvalue); [:IDENT, t] }
rule(/([\+\-]?\d+\.\d+)/) { |t| unset_flag(:varvalue); [:NUMBER, t.to_f] }
rule(/([\+\-]?\d+)/) { |t| unset_flag(:varvalue); [:NUMBER, t.to_i] }
end
class Expression < RLTK::ASTNode
end
class EObject < Expression
value :name, String
value :contents, Array
end
class EVariable < Expression
value :type, String
value :key, String
value :value, Object
end
class Parser < RLTK::Parser
production(:input, 'e') { |e| e }
production(:e, 'ocontent_list') { |contents| contents }
production(:ocontent_list) do
clause('ocontent') { |s| s }
clause('ocontent ocontent_list') { |s, o| s + o }
end
production(:ocontent) do
clause('ary_assign') { |s| [s] }
clause('assign') { |s| [s] }
clause('object') { |o| [o] }
end
production(:object, 'varname LCB ocontent_list RCB') { |name, _, content, _| EObject.new(name, content) }
production(:assign, 'TYPE varname EQUAL value') { |t, n, _, v| EVariable.new(t, n, v) }
production(:ary_assign, 'TYPE varname array') { |t, n, v| EVariable.new(t, n, v) }
production(:value) do
clause('NUMBER') { |s| s }
clause('STRING') { |s| s }
clause('BOOLEAN') { |s| s }
clause('IDENT') { |s| s }
clause('NULL') { |_| nil }
end
production(:array, 'LAB array_contents RAB') { |_, l, _| l }
production(:array_contents) do
clause('') { [] }
clause('NEWLINE array_contents') { |_, c| c }
clause('value') { |v| [v] }
clause('value NEWLINE') { |v,_| [v] }
clause('value NEWLINE array_contents') { |v, _, a| [v] + a }
end
production(:varname) do
clause('STRING') { |s| s }
clause('IDENT') { |s| s }
end
finalize
end
def self.make(data, depth = 0)
case data
when Array
data.map { |s| make(s, depth + 1) }
when EObject
{
data.name => make(data.contents, depth + 1).reduce({}, &:merge)
}
when EVariable
{
data.key => make(data.value, depth + 1)
}
else
data
end
end
def self.parse(io)
lex = Lexer.new
parser = Parser.new
contents = String === io ? io : io.read
tokens = lex.lex(contents)
STDERR.puts tokens
asts = parser.parse(tokens)
make(asts)
end
def self.load_file(filename)
File.open(filename, 'r') do |f|
parse(f)
end
end
end
end
test = %Q(
# Configuration file
general {
# If set to false, flat wire textures will be used for logic gates. Significant performance improvement
B:3Dlogicwires=true
I:1Thing=2
}
)
data = ConfigReaders::OpenComputers.load_file('/home/icy/.minecraft/config/OpenComputers.cfg')
#puts JSON.pretty_generate(data)
#data = ConfigReaders::Common.parse(test)
data = ConfigReaders::Common.load_file('/home/icy/.minecraft/config/ProjectRed.cfg')
#puts JSON.pretty_generate(data)
data = ConfigReaders::Common.load_file('/home/icy/.minecraft/config/buildcraft/main.conf')
puts JSON.pretty_generate(data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment