Created
January 12, 2015 02:36
-
-
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...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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