Permalink
Browse files

Added custom_initialize variable for use with standalone parsers to a…

…void warnings

Documented variables and directives in kpeg grammar

Refactored standalone_region to abort when a chunk is not found

Added fake filename to Kpeg::CodeGenerator#make

Fixed round-tripping of directives and variables through Kpeg::GrammarRenderer
  • Loading branch information...
1 parent 6251188 commit 11b7394f8475a52dcb61368d15c5a13ff21dc78d @drbrain drbrain committed Mar 8, 2012
View
@@ -1,7 +1,14 @@
-=== 1.0.1
+=== 1.1
+
+* Minor enhancements
+ * In standalone parsers generation of a default initialize method may be
+ disabled with the custom_initialize variable:
+
+ %% custom_initialize = true
* Bug fixes
* Hoe plugin now overwrites generated files
+ * Directives and variables now round-trip through KPeg::GrammarRenderer
=== 1.0 / 2012-04-06
View
@@ -157,6 +157,33 @@ Kpeg allows comments to be added to the grammar file by using the # symbol
# This is a comment in my grammar
+=== Variables
+
+A variable looks like this:
+
+ %% name = value
+
+Kpeg allows the following variables that control the output parser:
+
+name::
+ The class name of the generated parser.
+custom_initialize::
+ When built as a standalone parser a default initialize method will not be
+ included.
+
+=== Directives
+
+A directive looks like this:
+
+ %% header {
+ ...
+ }
+
+Kpeg allows the following directives:
+
+header::
+ Placed before any generated code
+
== Generating and running your parser
Before you can generate your parser you will need to define a root rule. This
View
@@ -90,11 +90,11 @@ def handle_ast(code)
end
end
end
-
+
def indentify(code, indent)
"#{" " * indent}#{code}"
end
-
+
# Default indent is 4 spaces (indent=2)
def output_op(code, op, indent=2)
case op
@@ -309,15 +309,18 @@ def output_op(code, op, indent=2)
else
raise "Unknown op - #{op.class}"
end
-
end
- def standalone_region(path)
- cp = File.read(path)
- start = cp.index("# STANDALONE START")
- fin = cp.index("# STANDALONE END")
+ def standalone_region(path, marker = "STANDALONE")
+ expanded_path = File.expand_path("../#{path}", __FILE__)
+ cp = File.read(expanded_path)
+ start = cp.index("# #{marker} START")
+ fin = cp.index("# #{marker} END")
+
+ unless start and fin
+ abort "#{marker} boundaries in #{path} missing for standalone generation"
+ end
- return nil unless start and fin
cp[start..fin]
end
@@ -334,20 +337,13 @@ def output
if @standalone
code << "class #{@name}\n"
- unless cp = standalone_region(
- File.expand_path("../compiled_parser.rb", __FILE__))
-
- puts "Standalone failure. Check compiler_parser.rb for proper boundary comments"
- exit 1
- end
-
- unless pp = standalone_region(
- File.expand_path("../position.rb", __FILE__))
- puts "Standalone failure. Check position.rb for proper boundary comments"
- end
+ cp = standalone_region("compiled_parser.rb")
+ cpi = standalone_region("compiled_parser.rb", "INITIALIZE")
+ pp = standalone_region("position.rb")
cp.gsub!(/include Position/, pp)
- code << cp << "\n"
+ code << cpi << "\n" unless @grammar.variables['custom_initialize']
+ code << cp << "\n"
else
code << "require 'kpeg/compiled_parser'\n\n"
code << "class #{@name} < KPeg::CompiledParser\n"
@@ -435,7 +431,7 @@ def output
def make(str)
m = Module.new
- m.module_eval output
+ m.module_eval output, "(kpeg parser #{@name})"
cls = m.const_get(@name)
cls.new(str)
@@ -10,8 +10,22 @@ def setup_foreign_grammar
# Leave these markers in! They allow us to generate standalone
# code automatically!
- #
+
+ # INITIALIZE START
+
+ # This is distinct from setup_parser so that a standalone parser
+ # can redefine #initialize and still have access to the proper
+ # parser setup code.
+ def initialize(str, debug=false)
+ setup_parser(str, debug)
+ end
+
+ # INITIALIZE END
+
# STANDALONE START
+
+ # Prepares for parsing +str+. If you define a custom initialize you must
+ # call this method before #parse
def setup_parser(str, debug=false)
@string = str
@pos = 0
@@ -23,14 +37,6 @@ def setup_parser(str, debug=false)
setup_foreign_grammar
end
- # This is distinct from setup_parser so that a standalone parser
- # can redefine #initialize and still have access to the proper
- # parser setup code.
- #
- def initialize(str, debug=false)
- setup_parser(str, debug)
- end
-
attr_reader :string
attr_reader :failing_rule_offset
attr_accessor :result, :pos
@@ -1,4 +1,5 @@
%% name = KPeg::FormatParser
+%% custom_initialize = true
%% {
require 'kpeg/grammar'
View
@@ -1,5 +1,8 @@
class KPeg::FormatParser
# STANDALONE START
+
+ # Prepares for parsing +str+. If you define a custom initialize you must
+ # call this method before #parse
def setup_parser(str, debug=false)
@string = str
@pos = 0
@@ -11,14 +14,6 @@ def setup_parser(str, debug=false)
setup_foreign_grammar
end
- # This is distinct from setup_parser so that a standalone parser
- # can redefine #initialize and still have access to the proper
- # parser setup code.
- #
- def initialize(str, debug=false)
- setup_parser(str, debug)
- end
-
attr_reader :string
attr_reader :failing_rule_offset
attr_accessor :result, :pos
@@ -10,6 +10,16 @@ def render(io)
widest = @grammar.rules.keys.sort { |a,b| a.size <=> b.size }.last
indent = widest.size
+ @grammar.variables.each do |name, value|
+ io.print "%% #{name} = #{value}\n"
+ end
+
+ @grammar.directives.each do |name, act|
+ io.print "%% #{name} {"
+ io.print act.action
+ io.print "}\n\n"
+ end
+
@grammar.setup_actions.each do |act|
io.print "%% {"
io.print act.action
View
@@ -1,5 +1,18 @@
class KPeg::StringEscape
+# INITIALIZE START
+
+ # This is distinct from setup_parser so that a standalone parser
+ # can redefine #initialize and still have access to the proper
+ # parser setup code.
+ def initialize(str, debug=false)
+ setup_parser(str, debug)
+ end
+
+ #
# STANDALONE START
+
+ # Prepares for parsing +str+. If you define a custom initialize you must
+ # call this method before #parse
def setup_parser(str, debug=false)
@string = str
@pos = 0
@@ -11,14 +24,6 @@ def setup_parser(str, debug=false)
setup_foreign_grammar
end
- # This is distinct from setup_parser so that a standalone parser
- # can redefine #initialize and still have access to the proper
- # parser setup code.
- #
- def initialize(str, debug=false)
- setup_parser(str, debug)
- end
-
attr_reader :string
attr_reader :failing_rule_offset
attr_accessor :result, :pos
@@ -1466,6 +1466,32 @@ def _root
assert cg.parse("hello")
end
+ def test_output_standalone
+ gram = KPeg.grammar do |g|
+ g.root = g.dot
+ end
+
+ cg = KPeg::CodeGenerator.new "Test", gram
+ cg.standalone = true
+
+ # if this fails, also change test_variable_custom_initialize
+ assert_match 'def initialize(str, debug=false)', cg.output
+
+ assert cg.parse("hello")
+ end
+
+ def test_variable_custom_initialize
+ gram = KPeg.grammar do |g|
+ g.root = g.dot
+ g.variables['custom_initialize'] = 'whatever'
+ end
+
+ cg = KPeg::CodeGenerator.new "Test", gram
+ cg.standalone = true
+
+ refute_match 'def initialize(str, debug=false)', cg.output
+ end
+
def test_ast_generation
gram = KPeg.grammar do |g|
g.root = g.dot
@@ -203,6 +203,26 @@ def test_collect
assert_equal expected, io.string
end
+ def test_directives
+ gram = KPeg.grammar do |g|
+ g.root = g.dot
+ g.add_directive 'header', g.action("\n# coding: UTF-8\n")
+ end
+
+ io = StringIO.new
+ gr = KPeg::GrammarRenderer.new(gram)
+ gr.render(io)
+
+ expected = <<-TXT
+%% header {
+# coding: UTF-8
+}
+
+root = .
+ TXT
+ assert_equal expected, io.string
+ end
+
def test_setup_actions
gram = KPeg.grammar do |g|
g.root = g.dot
@@ -220,7 +240,24 @@ def test_setup_actions
TXT
assert_equal expected, io.string
end
-
+
+ def test_variables
+ gram = KPeg.grammar do |g|
+ g.root = g.dot
+ g.set_variable 'name', "Foo"
+ end
+
+ io = StringIO.new
+ gr = KPeg::GrammarRenderer.new(gram)
+ gr.render(io)
+
+ expected = <<-TXT
+%% name = Foo
+root = .
+ TXT
+ assert_equal expected, io.string
+ end
+
def test_multiple_render
gram = KPeg.grammar do |g|
g.root = g.multiple("a", 3, 5)

0 comments on commit 11b7394

Please sign in to comment.