sexpr 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/CHANGELOG.md +24 -1
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +2 -0
  4. data/LICENCE.md +1 -1
  5. data/Manifest.txt +1 -0
  6. data/README.md +46 -31
  7. data/examples/bool_expr/bool_expr.citrus +72 -0
  8. data/examples/bool_expr/bool_expr.rb +86 -0
  9. data/examples/bool_expr/bool_expr.sexp.yml +23 -0
  10. data/lib/sexpr.rb +27 -20
  11. data/lib/sexpr/errors.rb +15 -0
  12. data/lib/sexpr/grammar.rb +21 -77
  13. data/lib/sexpr/grammar/matching.rb +56 -0
  14. data/lib/sexpr/grammar/options.rb +53 -0
  15. data/lib/sexpr/grammar/parsing.rb +20 -0
  16. data/lib/sexpr/grammar/tagging.rb +49 -0
  17. data/lib/sexpr/loader.rb +0 -1
  18. data/lib/sexpr/matcher.rb +15 -0
  19. data/lib/sexpr/matcher/alternative.rb +30 -0
  20. data/lib/sexpr/matcher/many.rb +54 -0
  21. data/lib/sexpr/matcher/reference.rb +32 -0
  22. data/lib/sexpr/matcher/rule.rb +31 -0
  23. data/lib/sexpr/matcher/sequence.rb +30 -0
  24. data/lib/sexpr/matcher/terminal.rb +37 -0
  25. data/lib/sexpr/node.rb +16 -0
  26. data/lib/sexpr/parser.rb +48 -0
  27. data/lib/sexpr/parser/citrus.rb +67 -0
  28. data/lib/sexpr/version.rb +2 -2
  29. data/sexpr.gemspec +1 -0
  30. data/sexpr.noespec +2 -1
  31. data/spec/grammar/matching/test_compile_rule.rb +23 -0
  32. data/spec/grammar/matching/test_compile_rule_defn.rb +103 -0
  33. data/spec/grammar/options/test_install_parser.rb +36 -0
  34. data/spec/grammar/options/test_install_path.rb +19 -0
  35. data/spec/grammar/options/test_install_root.rb +27 -0
  36. data/spec/grammar/tagging/test_looks_a_sexpr.rb +20 -0
  37. data/spec/grammar/tagging/test_mod2rulename.rb +19 -0
  38. data/spec/grammar/tagging/test_rule2modname.rb +19 -0
  39. data/spec/grammar/tagging/test_tag_sexpr.rb +28 -0
  40. data/spec/grammar/test_new.rb +15 -0
  41. data/spec/grammar/test_parse.rb +27 -18
  42. data/spec/grammar/test_sexpr.rb +53 -0
  43. data/spec/{alternative → matcher/alternative}/test_eat.rb +1 -1
  44. data/spec/{alternative → matcher/alternative}/test_match_q.rb +1 -1
  45. data/spec/{many → matcher/many}/test_eat.rb +1 -1
  46. data/spec/{many → matcher/many}/test_initialize.rb +1 -1
  47. data/spec/{many → matcher/many}/test_match_q.rb +1 -1
  48. data/spec/{reference → matcher/reference}/test_eat.rb +1 -1
  49. data/spec/{reference → matcher/reference}/test_match_q.rb +1 -1
  50. data/spec/{rule → matcher/rule}/test_eat.rb +1 -1
  51. data/spec/{rule → matcher/rule}/test_match_q.rb +1 -1
  52. data/spec/{sequence → matcher/sequence}/test_eat.rb +1 -1
  53. data/spec/{sequence → matcher/sequence}/test_match_q.rb +1 -1
  54. data/spec/{terminal → matcher/terminal}/test_eat.rb +1 -1
  55. data/spec/{terminal → matcher/terminal}/test_match_q.rb +1 -1
  56. data/spec/{terminal → matcher/terminal}/test_terminal_match.rb +1 -1
  57. data/spec/node/test_sexpr_body.rb +18 -0
  58. data/spec/node/test_sexpr_type.rb +14 -0
  59. data/spec/parser/citrus/test_new.rb +28 -0
  60. data/spec/parser/citrus/test_parse.rb +40 -0
  61. data/spec/parser/citrus/test_recognize.rb +18 -0
  62. data/spec/parser/citrus/test_registration.rb +20 -0
  63. data/spec/parser/citrus/test_to_sexpr.rb +16 -0
  64. data/spec/parser/test_factor.rb +17 -0
  65. data/spec/parser/test_input_text.rb +27 -0
  66. data/spec/spec_helper.rb +18 -1
  67. data/spec/test_load.rb +40 -13
  68. data/spec/test_readme_examples.rb +40 -30
  69. data/spec/test_sexpr.rb +1 -1
  70. metadata +118 -68
  71. data/lib/sexpr/alternative.rb +0 -28
  72. data/lib/sexpr/element.rb +0 -9
  73. data/lib/sexpr/many.rb +0 -52
  74. data/lib/sexpr/reference.rb +0 -30
  75. data/lib/sexpr/rule.rb +0 -29
  76. data/lib/sexpr/sequence.rb +0 -28
  77. data/lib/sexpr/terminal.rb +0 -35
  78. data/spec/bool_expr.yml +0 -19
  79. data/spec/grammar/test_compile_rule.rb +0 -25
  80. data/spec/grammar/test_compile_rule_defn.rb +0 -98
  81. data/spec/grammar/test_fetch.rb +0 -18
  82. data/spec/grammar/test_root.rb +0 -20
  83. data/spec/test_bool_expr.rb +0 -27
@@ -0,0 +1,56 @@
1
+ module Sexpr
2
+ module Grammar
3
+ module Matching
4
+
5
+ def root_rule
6
+ rules[root]
7
+ end
8
+
9
+ def [](rule_name)
10
+ rules[rule_name]
11
+ end
12
+
13
+ def match?(sexp)
14
+ root_rule.match?(sexp)
15
+ end
16
+ alias :=== :match?
17
+
18
+ private
19
+
20
+ def compile_rules(rules)
21
+ Hash[rules.map{|k,v|
22
+ [k.to_sym, compile_rule(k.to_sym, v)]
23
+ }]
24
+ end
25
+
26
+ def compile_rule(name, defn)
27
+ case rule = compile_rule_defn(defn)
28
+ when Matcher::Terminal, Matcher::Alternative
29
+ rule
30
+ else
31
+ Matcher::Rule.new(name, rule)
32
+ end
33
+ end
34
+
35
+ def compile_rule_defn(arg, grammar = self)
36
+ case arg
37
+ when Matcher
38
+ arg
39
+ when Regexp, TrueClass, FalseClass, NilClass
40
+ Matcher::Terminal.new arg
41
+ when lambda{|x| x.is_a?(Array) && x.size == 1 && x.first.is_a?(Array)}
42
+ Matcher::Sequence.new arg.first.map{|s| compile_rule_defn(s) }
43
+ when Array
44
+ Matcher::Alternative.new arg.map{|s| compile_rule_defn(s)}
45
+ when /([\?\+\*])$/
46
+ Matcher::Many.new compile_rule_defn($`), $1
47
+ when /^[a-z][a-z_]+$/
48
+ Matcher::Reference.new arg.to_sym, grammar
49
+ else
50
+ raise ArgumentError, "Invalid rule definition: #{arg.inspect}", caller
51
+ end
52
+ end
53
+
54
+ end # module Matching
55
+ end # module Grammar
56
+ end # module Sexpr
@@ -0,0 +1,53 @@
1
+ module Sexpr
2
+ module Grammar
3
+ module Options
4
+
5
+ attr_reader :options
6
+ attr_reader :path
7
+ attr_reader :rules
8
+ attr_reader :root
9
+ attr_reader :parser
10
+
11
+ def install_options(options)
12
+ @options = options
13
+ install_path
14
+ install_rules
15
+ install_root
16
+ install_parser
17
+ yield self if block_given?
18
+ end
19
+
20
+ def option(key)
21
+ @options[key.to_sym] || @options[key.to_s]
22
+ end
23
+
24
+ def install_path
25
+ @path = option(:path)
26
+ end
27
+
28
+ def install_rules
29
+ @rules = option(:rules) || {}
30
+ @rules = compile_rules(@rules)
31
+ end
32
+
33
+ def install_root
34
+ @root = option(:root)
35
+ @root = rules.keys.first unless @root
36
+ @root = root.to_sym if @root
37
+ end
38
+
39
+ def install_parser
40
+ @parser = option(:parser)
41
+ if @parser.is_a?(String) && !File.exists?(@parser)
42
+ @parser = File.join(File.dirname(path), @parser)
43
+ end
44
+ @parser = Parser.factor(@parser) if @parser
45
+ end
46
+
47
+ def compile_rules(rules)
48
+ @rules = rules
49
+ end
50
+
51
+ end # module Options
52
+ end # module Grammar
53
+ end # module Sexpr
@@ -0,0 +1,20 @@
1
+ module Sexpr
2
+ module Grammar
3
+ module Parsing
4
+
5
+ def parse(input, options = {})
6
+ parser!.parse(input, options)
7
+ end
8
+
9
+ private
10
+
11
+ def parser!
12
+ unless p = parser
13
+ raise NoParserError, "No parser set.", caller
14
+ end
15
+ p
16
+ end
17
+
18
+ end # module Parsing
19
+ end # module Grammar
20
+ end # module Sexpr
@@ -0,0 +1,49 @@
1
+ module Sexpr
2
+ module Grammar
3
+ module Tagging
4
+
5
+ def rule2modname(rule)
6
+ rule.to_s.gsub(/(^|_)([a-z])/){|x| $2.capitalize}.to_sym
7
+ end
8
+
9
+ def mod2rulename(mod)
10
+ mod = mod.name.to_s.split('::').last.to_sym if mod.is_a?(Module)
11
+ mod.to_s.gsub(/[A-Z]/){|x| "_#{x.downcase}"}[1..-1].to_sym
12
+ end
13
+
14
+ def sexpr(input, options = {})
15
+ case input
16
+ when Array
17
+ tag_sexpr input
18
+ else
19
+ tag_sexpr parser!.sexpr(input, options)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def tag_sexpr(sexpr, reference = self)
26
+ if looks_a_sexpr?(sexpr)
27
+ sexpr = tag_sexpr_with_user_module(sexpr, reference)
28
+ sexpr[1..-1].each do |child|
29
+ tag_sexpr(child, reference)
30
+ end
31
+ end
32
+ sexpr
33
+ end
34
+
35
+ def tag_sexpr_with_user_module(sexpr, reference)
36
+ sexpr.extend(Sexpr)
37
+ rulename = sexpr.first
38
+ modname = rule2modname(rulename)
39
+ mod = reference.const_get(modname) rescue nil
40
+ mod ? sexpr.extend(mod) : sexpr
41
+ end
42
+
43
+ def looks_a_sexpr?(arg)
44
+ arg.is_a?(Array) and arg.first.is_a?(Symbol)
45
+ end
46
+
47
+ end # module Tagging
48
+ end # module Grammar
49
+ end # module Sexpr
@@ -1 +0,0 @@
1
-
@@ -0,0 +1,15 @@
1
+ module Sexpr
2
+ module Matcher
3
+
4
+ def ===(sexp)
5
+ match?(sexp)
6
+ end
7
+
8
+ end # module Matcher
9
+ end # module Sexpr
10
+ require_relative "matcher/alternative"
11
+ require_relative "matcher/many"
12
+ require_relative "matcher/reference"
13
+ require_relative "matcher/rule"
14
+ require_relative "matcher/sequence"
15
+ require_relative "matcher/terminal"
@@ -0,0 +1,30 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class Alternative
4
+ include Matcher
5
+
6
+ attr_reader :terms
7
+
8
+ def initialize(terms)
9
+ @terms = terms
10
+ end
11
+
12
+ def match?(sexp)
13
+ terms.any?{|t| t.match?(sexp)}
14
+ end
15
+
16
+ def eat(sexp)
17
+ @terms.each do |alt|
18
+ res = alt.eat(sexp)
19
+ return res if res
20
+ end
21
+ nil
22
+ end
23
+
24
+ def inspect
25
+ "(alt #{terms.inspect})"
26
+ end
27
+
28
+ end # class Alternative
29
+ end # module Matcher
30
+ end # module Sexpr
@@ -0,0 +1,54 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class Many
4
+ include Matcher
5
+
6
+ attr_reader :term, :min, :max
7
+
8
+ def initialize(term, min, max = nil)
9
+ @term = term
10
+ @min, @max = minmax(min, max)
11
+ end
12
+
13
+ def match?(sexp)
14
+ return nil unless sexp.is_a?(Array)
15
+ eat = eat(sexp)
16
+ eat && eat.empty?
17
+ end
18
+
19
+ def eat(sexp)
20
+ i, last = 0, sexp
21
+ while sexp && (@max.nil? || i < @max)
22
+ if res = @term.eat(sexp)
23
+ last = res
24
+ i += 1
25
+ end
26
+ sexp = res
27
+ end
28
+ i >= @min ? last : nil
29
+ end
30
+
31
+ def inspect
32
+ "(many #{term.inspect}, #{min}, #{max})"
33
+ end
34
+
35
+ private
36
+
37
+ def minmax(min, max)
38
+ case min
39
+ when Integer
40
+ [min, max]
41
+ when '?'
42
+ [0, 1]
43
+ when '+'
44
+ [1, nil]
45
+ when '*'
46
+ [0, nil]
47
+ else
48
+ raise ArgumentError, "Invalid multiplicity: #{min}"
49
+ end
50
+ end
51
+
52
+ end # class Sequence
53
+ end # module Matcher
54
+ end # module Sexpr
@@ -0,0 +1,32 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class Reference
4
+ include Matcher
5
+
6
+ attr_reader :rule_name
7
+ attr_reader :grammar
8
+
9
+ def initialize(rule_name, grammar)
10
+ @rule_name = rule_name
11
+ @grammar = grammar
12
+ end
13
+
14
+ def rule
15
+ @rule ||= @grammar[@rule_name]
16
+ end
17
+
18
+ def match?(sexp)
19
+ rule && rule.match?(sexp)
20
+ end
21
+
22
+ def eat(sexp)
23
+ rule && rule.eat(sexp)
24
+ end
25
+
26
+ def inspect
27
+ "(ref #{rule_name}, #{rule.inspect})"
28
+ end
29
+
30
+ end # class Reference
31
+ end # module Matcher
32
+ end # module Sexpr
@@ -0,0 +1,31 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class Rule
4
+ include Matcher
5
+
6
+ attr_reader :name
7
+ attr_reader :defn
8
+
9
+ def initialize(name, defn)
10
+ @name = name
11
+ @defn = defn
12
+ end
13
+
14
+ def match?(sexp)
15
+ return nil unless sexp.is_a?(Array)
16
+ return false unless sexp.first && (sexp.first == name)
17
+ defn.match?(sexp[1..-1])
18
+ end
19
+
20
+ def eat(sexp)
21
+ return nil unless match?(sexp.first)
22
+ sexp[1..-1]
23
+ end
24
+
25
+ def inspect
26
+ "(rule #{name}, #{defn.inspect})"
27
+ end
28
+
29
+ end # class Rule
30
+ end # module Matcher
31
+ end # module Sexpr
@@ -0,0 +1,30 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class Sequence
4
+ include Matcher
5
+
6
+ attr_reader :terms
7
+
8
+ def initialize(terms)
9
+ @terms = terms
10
+ end
11
+
12
+ def match?(sexp)
13
+ return nil unless sexp.is_a?(Array)
14
+ eat = eat(sexp)
15
+ eat && eat.empty?
16
+ end
17
+
18
+ def eat(sexp)
19
+ @terms.inject sexp do |rest,rule|
20
+ rest && rule.eat(rest)
21
+ end
22
+ end
23
+
24
+ def inspect
25
+ "(seq #{terms.inspect})"
26
+ end
27
+
28
+ end # class Sequence
29
+ end # module Matcher
30
+ end # module Sexpr
@@ -0,0 +1,37 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class Terminal
4
+ include Matcher
5
+
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+
12
+ def inspect
13
+ "(terminal #{value.inspect})"
14
+ end
15
+
16
+ def match?(sexp)
17
+ terminal_match?(sexp)
18
+ end
19
+
20
+ def eat(sexp)
21
+ match?(sexp.first) ? sexp[1..-1] : nil
22
+ end
23
+
24
+ private
25
+
26
+ def terminal_match?(term)
27
+ case @value
28
+ when Regexp
29
+ @value === term rescue false
30
+ when TrueClass, FalseClass, NilClass
31
+ @value == term
32
+ end
33
+ end
34
+
35
+ end # class Terminal
36
+ end # module Matcher
37
+ end # module Sexpr
@@ -0,0 +1,16 @@
1
+ module Sexpr
2
+ module Node
3
+
4
+ def sexpr_type
5
+ first
6
+ end
7
+ alias :sexp_type :sexpr_type
8
+
9
+ def sexpr_body
10
+ self[1..-1]
11
+ end
12
+ alias :sexp_body :sexpr_body
13
+
14
+ end # module Node
15
+ include Node
16
+ end # module Sexpr
@@ -0,0 +1,48 @@
1
+ module Sexpr
2
+ module Parser
3
+
4
+ class << self
5
+
6
+ def registered_parser_classes
7
+ @registered_parser_classes ||= []
8
+ end
9
+
10
+ def register(parser_class)
11
+ registered_parser_classes << parser_class
12
+ end
13
+
14
+ def find_parser_class(external_parser)
15
+ registered_parser_classes.find{|cl|
16
+ cl.recognizes?(external_parser)
17
+ }
18
+ end
19
+
20
+ def factor(external_parser, options = {})
21
+ return external_parser if Parser===external_parser
22
+ if cl = find_parser_class(external_parser)
23
+ cl.new(external_parser, options)
24
+ else
25
+ raise UnrecognizedParserError, "Parser not recognized: #{external_parser}"
26
+ end
27
+ end
28
+
29
+ end # class methods
30
+
31
+ private
32
+
33
+ def input_text(input)
34
+ case input
35
+ when lambda{|x| x.respond_to?(:to_path)}
36
+ input_text File.read(input.to_path)
37
+ when IO
38
+ input_text input.read
39
+ when String
40
+ input
41
+ else
42
+ raise InvalidParseSourceError, "Invalid parse source: #{input}"
43
+ end
44
+ end
45
+
46
+ end # module Parser
47
+ end # module Sexpr
48
+ require_relative "parser/citrus"