sexpr 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"