sexpr 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +91 -17
  3. data/Gemfile +1 -8
  4. data/README.md +26 -1
  5. data/Rakefile +0 -12
  6. data/lib/sexpr/grammar/matching.rb +9 -4
  7. data/lib/sexpr/grammar/parsing.rb +1 -1
  8. data/lib/sexpr/grammar/tagging.rb +27 -15
  9. data/lib/sexpr/matcher/many.rb +16 -1
  10. data/lib/sexpr/matcher/non_terminal.rb +31 -0
  11. data/lib/sexpr/matcher/rule.rb +3 -6
  12. data/lib/sexpr/matcher/terminal.rb +1 -1
  13. data/lib/sexpr/matcher.rb +3 -2
  14. data/lib/sexpr/node.rb +1 -1
  15. data/lib/sexpr/parser.rb +1 -1
  16. data/lib/sexpr/processor/helper.rb +1 -1
  17. data/lib/sexpr/processor/sexpr_coercions.rb +6 -14
  18. data/lib/sexpr/processor.rb +69 -21
  19. data/lib/sexpr/rewriter.rb +1 -9
  20. data/lib/sexpr/version.rb +3 -3
  21. data/lib/sexpr.rb +11 -3
  22. data/spec/fixtures/bar.rb +9 -0
  23. data/spec/fixtures/foo.rb +14 -0
  24. data/spec/fixtures/preprocessed.rb +33 -0
  25. data/spec/fixtures/simple_processor.rb +19 -0
  26. data/spec/integration/bool_expr/test_not_push.rb +38 -0
  27. data/spec/integration/bool_expr/test_parsing.rb +15 -0
  28. data/spec/integration/bool_expr/test_tagging.rb +25 -0
  29. data/spec/integration/bool_expr/test_validation.rb +15 -0
  30. data/spec/{test_readme_examples.rb → integration/test_readme_examples.rb} +4 -4
  31. data/spec/spec_helper.rb +3 -22
  32. data/spec/unit/grammar/matching/test_compile_rule.rb +55 -0
  33. data/spec/{grammar → unit/grammar}/matching/test_compile_rule_defn.rb +26 -2
  34. data/spec/unit/grammar/matching/test_compile_rules.rb +45 -0
  35. data/spec/{grammar → unit/grammar}/options/test_install_parser.rb +0 -0
  36. data/spec/{grammar → unit/grammar}/options/test_install_path.rb +0 -0
  37. data/spec/{grammar → unit/grammar}/options/test_install_root.rb +0 -0
  38. data/spec/{grammar → unit/grammar}/tagging/test_looks_a_sexpr.rb +4 -4
  39. data/spec/{grammar → unit/grammar}/tagging/test_mod2rulename.rb +0 -0
  40. data/spec/{grammar → unit/grammar}/tagging/test_rule2modname.rb +0 -0
  41. data/spec/unit/grammar/tagging/test_tag_sexpr.rb +61 -0
  42. data/spec/unit/grammar/tagging/test_tagging_module_for.rb +72 -0
  43. data/spec/{grammar → unit/grammar}/test_new.rb +0 -0
  44. data/spec/{grammar → unit/grammar}/test_parse.rb +0 -0
  45. data/spec/{grammar → unit/grammar}/test_sexpr.rb +0 -0
  46. data/spec/{matcher → unit/matcher}/alternative/test_eat.rb +1 -1
  47. data/spec/{matcher → unit/matcher}/alternative/test_match_q.rb +0 -0
  48. data/spec/{matcher → unit/matcher}/many/test_eat.rb +0 -0
  49. data/spec/{matcher → unit/matcher}/many/test_initialize.rb +0 -0
  50. data/spec/{matcher → unit/matcher}/many/test_match_q.rb +0 -0
  51. data/spec/unit/matcher/non_terminal/test_eat.rb +31 -0
  52. data/spec/unit/matcher/non_terminal/test_match_q.rb +32 -0
  53. data/spec/{matcher → unit/matcher}/reference/test_eat.rb +0 -0
  54. data/spec/{matcher → unit/matcher}/reference/test_match_q.rb +1 -1
  55. data/spec/unit/matcher/rule/test_eat.rb +17 -0
  56. data/spec/unit/matcher/rule/test_match_q.rb +17 -0
  57. data/spec/{matcher → unit/matcher}/sequence/test_eat.rb +0 -0
  58. data/spec/{matcher → unit/matcher}/sequence/test_match_q.rb +0 -0
  59. data/spec/{matcher → unit/matcher}/terminal/test_eat.rb +1 -1
  60. data/spec/{matcher → unit/matcher}/terminal/test_match_q.rb +3 -3
  61. data/spec/unit/matcher/terminal/test_terminal_match.rb +108 -0
  62. data/spec/{node → unit/node}/test_sexpr_body.rb +0 -0
  63. data/spec/{node → unit/node}/test_sexpr_copy.rb +0 -0
  64. data/spec/{node → unit/node}/test_sexpr_type.rb +0 -0
  65. data/spec/{node → unit/node}/test_tracking_markers.rb +0 -0
  66. data/spec/{parser → unit/parser}/citrus/test_new.rb +0 -0
  67. data/spec/{parser → unit/parser}/citrus/test_parse.rb +0 -0
  68. data/spec/{parser → unit/parser}/citrus/test_recognize.rb +1 -1
  69. data/spec/{parser → unit/parser}/citrus/test_registration.rb +0 -0
  70. data/spec/{parser → unit/parser}/citrus/test_to_sexpr.rb +0 -0
  71. data/spec/{parser → unit/parser}/test_factor.rb +0 -0
  72. data/spec/{parser → unit/parser}/test_input_text.rb +0 -0
  73. data/spec/{processor → unit/processor}/helper/test_call.rb +0 -0
  74. data/spec/unit/processor/test_apply.rb +34 -0
  75. data/spec/{processor → unit/processor}/test_build_helper_chain.rb +0 -0
  76. data/spec/unit/processor/test_call.rb +47 -0
  77. data/spec/unit/processor/test_grammar.rb +56 -0
  78. data/spec/{processor → unit/processor}/test_helper.rb +0 -0
  79. data/spec/{processor → unit/processor}/test_sexpr_coercions.rb +6 -2
  80. data/spec/unit/processor/test_use.rb +22 -0
  81. data/spec/{rewriter → unit/rewriter}/test_copy_and_apply.rb +0 -0
  82. data/spec/{test_load.rb → unit/test_load.rb} +3 -2
  83. data/spec/{test_rewriter.rb → unit/test_rewriter.rb} +0 -0
  84. data/spec/unit/test_sexpr.rb +8 -0
  85. data/tasks/gem.rake +9 -4
  86. data/tasks/test.rake +11 -0
  87. metadata +126 -183
  88. data/Gemfile.lock +0 -27
  89. data/LICENCE.md +0 -22
  90. data/Manifest.txt +0 -16
  91. data/examples/bool_expr/bool_expr.citrus +0 -72
  92. data/examples/bool_expr/bool_expr.rb +0 -153
  93. data/examples/bool_expr/bool_expr.sexp.yml +0 -23
  94. data/lib/sexpr/loader.rb +0 -0
  95. data/sexpr.gemspec +0 -189
  96. data/sexpr.noespec +0 -26
  97. data/spec/grammar/matching/test_compile_rule.rb +0 -23
  98. data/spec/grammar/tagging/test_tag_sexpr.rb +0 -28
  99. data/spec/grammar.yml +0 -24
  100. data/spec/matcher/rule/test_eat.rb +0 -21
  101. data/spec/matcher/rule/test_match_q.rb +0 -24
  102. data/spec/matcher/terminal/test_terminal_match.rb +0 -89
  103. data/spec/processor/test_call.rb +0 -46
  104. data/spec/processor/test_main_processor.rb +0 -18
  105. data/spec/test_sexpr.rb +0 -8
  106. data/tasks/debug_mail.rake +0 -75
  107. data/tasks/debug_mail.txt +0 -13
  108. data/tasks/spec_test.rake +0 -71
  109. data/tasks/unit_test.rake +0 -76
  110. data/tasks/yard.rake +0 -51
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 67f9fda1b0281f22e49fc267f432c1bed5ba6f270911b0470786662533d257d0
4
+ data.tar.gz: 97b7fc7fce491b594f4fb52a06daa5837407a5d5db59ab6bb6ae56e517e38492
5
+ SHA512:
6
+ metadata.gz: 808fbbb1bd28d417addfd740a85c441e022208ed87111a713bd4cd1920254b606181c3c8a2bbabd2c4fcaa707543b11d3baa1db6e661378fa7f7b350256599f7
7
+ data.tar.gz: 2c3795497e39ededc4cec8897fd178a34911fa3647a40561d8218da19bc9b72671de4b6d86bbd5854beb1d9ebc2907d86536d33531fe7c8b38074338c8824263
data/CHANGELOG.md CHANGED
@@ -1,14 +1,88 @@
1
+ # 1.0.0 / 2021-12-11
2
+
3
+ * Upgrade project structure to modern Enspirit practices
4
+
5
+ * Citrus dependency upgraded to 3.x and code fixed accordingly
6
+
7
+ # 0.6.0 / 2012-09-13
8
+
9
+ * Major enhancements (possibly breaking changes)
10
+
11
+ * All grammar rules are now proper Matcher::Rule instances. A NonTerminal
12
+ matcher is added and is used for non-terminal rule productions and matching
13
+ (all but Terminal and Alternative).
14
+ * `Rewriter.sexpr_grammar` and `Rewriter#sexpr_grammar` have been removed.
15
+ `Processor.grammar` and `Processor#grammar` are provided instead.
16
+
17
+ * Minor enhancements
18
+
19
+ * Added `Grammar#tagging_module_for(rulename)` that returns the user-defined
20
+ module used to tag a given grammar rule production.
21
+ * `Grammar#looks_a_sexpr?(arg)` became public.
22
+
23
+ # 0.5.1 / 2012-03-13
24
+
25
+ * Minor enhancements
26
+
27
+ * Modules and Classes are now recognized for terminals in the same way as Regexp, true,
28
+ false and nil. As there are quite a few bugs in Psych/Sick to put class names in .yaml
29
+ files, Sexpr recognizes them also as strings starting with '::' (e.g. ::Symbol).
30
+
31
+ * Bug fixes
32
+
33
+ * Use `reference.const_get(name, *false*)` when looking for tagging modules to avoid
34
+ finding ruby classes such as Array when tagging sexpr such as [:array, ...].
35
+
36
+ # 0.5.0 / 2012-02-25
37
+
38
+ * Major enhancements
39
+
40
+ * `Processor#apply` is introduced and is intended to be used to apply processing rules
41
+ from the inside of the processor class. This method is equivalent to `#call` when no
42
+ preprocessor is installed (see below).
43
+ * Preprocessors can now be installed on a Processor through its `use` class method.
44
+ Preprocessors are applied ala 'Enumerable#inject' on the s-expression passed at
45
+ `Prcoessor#call`. The preprocessed result is then passed to `#apply` for further
46
+ processing by the processor itself.
47
+ (Please note that preprocessing is *not* performed by `#apply` itself; preprocessing
48
+ is typically applied once one the whole abstract syntax tree instead of successively
49
+ when its nodes are encountered. Tip: use helpers for such behavior).
50
+ * `Processor.use` can also be used to make specific computations ahead of processing
51
+ (such as a symbol table). For this, simply pass a Hash that maps a computation name
52
+ to a processor class. Attribute readers are automatically installed on the processor
53
+ class and instance variables set by `#call` accordingly.
54
+ (Please note such preprocessors do not participate to the rewriting chain described
55
+ above.)
56
+
57
+ * Minor enhancements
58
+
59
+ * `Processor.call(sexpr, opts)` is a shortcut for `Processor.new(opts).call(sexpr)`
60
+ * `Grammar#default_taggging_module` is introduced for cases where either no fine-grained
61
+ tagging is needed (e.g. all nodes tagged with the same Node module) or fine-grained
62
+ tagging is needed but is not a total function (e.g. not all nodes have a
63
+ specialization of Node).
64
+
65
+ * Bug fixes
66
+
67
+ * Processor options taken at construction are now correctly kept under @options (an
68
+ attribute reader is provided)
69
+
70
+ * Breaking changes
71
+
72
+ * The `Processor#main_processor` feature (undocumented and unused in examples) has been
73
+ removed. Using preprocessors is much cleaner that linking processors to each other.
74
+
1
75
  # 0.4.0 / 2012-02-23
2
76
 
3
77
  * Major enhancements
4
78
 
5
- * A processing/rewriting framework has been added to Sexpr. See the `Processor` and `Rewriter`
6
- classes, as well as the boolean expression example.
7
- * Tracking markers can now decorate s-expressions, provided they include the `Sexpr` module.
8
- Tracking markers are a simple Hash of meta-information (i.e. not taken into account for
9
- equality for s-expressions). Such markers can be set with `Grammar#sexpr(sexpr, markers)`.
10
- Default markers are typically provided by parsers for traceability of the s-expression
11
- with the source text it comes from.
79
+ * A processing/rewriting framework has been added to Sexpr. See the `Processor` and
80
+ `Rewriter` classes, as well as the boolean expression example.
81
+ * Tracking markers can now decorate s-expressions, provided they include the `Sexpr`
82
+ module. Tracking markers are a simple Hash of meta-information (i.e. not taken into
83
+ account for equality for s-expressions). Such markers can be set with
84
+ `Grammar#sexpr(sexpr, markers)`. Default markers are typically provided by parsers for
85
+ traceability of the s-expression with the source text it comes from.
12
86
 
13
87
  * Minor enhancements
14
88
 
@@ -20,12 +94,12 @@
20
94
 
21
95
  * Breaking changes
22
96
 
23
- * `Parser.factor` does no longer accept options. This is to avoid the 'yet another options'
24
- symptom and favor convention over configuration.
97
+ * `Parser.factor` does no longer accept options. This is to avoid the 'yet another
98
+ options' symptom and favor convention over configuration.
25
99
  * Accordingly, `Sexpr::Citrus::Parser` no longer takes options at construction either.
26
- * `Grammar#sexpr` does no longer allow parsing options as second argument, but takes tracking
27
- markers (see enhancements). To palliate to this, default parsing options can now be
28
- specified through `Grammar#default_parse_options` (see enhancements).
100
+ * `Grammar#sexpr` does no longer allow parsing options as second argument, but takes
101
+ tracking markers (see enhancements). To palliate to this, default parsing options can
102
+ now be specified through `Grammar#default_parse_options` (see enhancements).
29
103
 
30
104
  # 0.3.0 / 2012-02-21
31
105
 
@@ -44,11 +118,11 @@
44
118
  the result to a constant makes perfect sense and benefits from the ruby's magic naming
45
119
  feature.
46
120
  * A loaded grammar now respond to a :sexpr method that parses (if needed) and returns a
47
- s-expression. The latter, and all its sub-expressions are automatically tagged with the
48
- Sexpr module, as well as with user-defined modules. The latter are automatically discovered
49
- with a convention over configuration heuristics that associates rule names to module names.
50
- That convention may however be overridden with specific grammar methods (see the BoolExpr
51
- example).
121
+ s-expression. The latter, and all its sub-expressions are automatically tagged with
122
+ the Sexpr module, as well as with user-defined modules. The latter are automatically
123
+ discovered with a convention over configuration heuristics that associates rule names
124
+ to module names. That convention may however be overridden with specific grammar
125
+ methods (see the BoolExpr example).
52
126
 
53
127
  # 0.2.0 / 2012-02-21
54
128
 
data/Gemfile CHANGED
@@ -1,9 +1,2 @@
1
1
  source 'http://rubygems.org'
2
-
3
- group :development do
4
- gem "epath", "~> 0.0.1"
5
- gem "citrus", "~> 2.4"
6
- gem "rake", "~> 0.9.2"
7
- gem "rspec", "~> 2.8.0"
8
- gem "wlang", "~> 0.10.2"
9
- end
2
+ gemspec
data/README.md CHANGED
@@ -84,4 +84,29 @@ https://github.com/blambeau/sexpr
84
84
 
85
85
  ### Where to read next?
86
86
 
87
- Have a look at the examples directory.
87
+ Have a look at the examples directory.
88
+
89
+ ## Public API
90
+
91
+ `sexpr` uses Semver and reached 1.0. The public API is as follows:
92
+
93
+ * The structure of YAML grammar files
94
+ * The `Sexpr` module and its public methods
95
+ * The behavior of the `Grammar` class through its public methods
96
+ * The behavior of the `Node` class (public methods)
97
+ * The behavior of the `Processor` and `Rewriter` classes (public & protected methods)
98
+ * The list of error classes and when they are raised
99
+
100
+ ## Contribute
101
+
102
+ Please use github issues and pull requests for all questions, bug reports,
103
+ and contributions. Don't hesitate to get in touch with us with an early code
104
+ spike if you plan to add non trivial features.
105
+
106
+ ## Licence
107
+
108
+ This software is distributed by Enspirit SRL under a MIT Licence. Please
109
+ contact Bernard Lambeau (blambeau@gmail.com) with any question.
110
+
111
+ Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
112
+ actively using and contributing to the library.
data/Rakefile CHANGED
@@ -1,15 +1,3 @@
1
- begin
2
- gem "bundler", "~> 1.0"
3
- require "bundler/setup"
4
- rescue LoadError => ex
5
- puts ex.message
6
- abort "Bundler failed to load, (did you run 'gem install bundler' ?)"
7
- end
8
-
9
- # Dynamically load the gem spec
10
- $gemspec_file = File.expand_path('../sexpr.gemspec', __FILE__)
11
- $gemspec = Kernel.eval(File.read($gemspec_file))
12
-
13
1
  # We run tests by default
14
2
  task :default => :test
15
3
 
@@ -15,7 +15,7 @@ module Sexpr
15
15
  end
16
16
  alias :=== :match?
17
17
 
18
- private
18
+ private
19
19
 
20
20
  def compile_rules(rules)
21
21
  Hash[rules.map{|k,v|
@@ -26,17 +26,19 @@ module Sexpr
26
26
  def compile_rule(name, defn)
27
27
  case rule = compile_rule_defn(defn)
28
28
  when Matcher::Terminal, Matcher::Alternative
29
- rule
30
29
  else
31
- Matcher::Rule.new(name, rule)
30
+ rule = Matcher::NonTerminal.new(name, rule)
32
31
  end
32
+ Matcher::Rule.new(name, rule)
33
33
  end
34
34
 
35
35
  def compile_rule_defn(arg, grammar = self)
36
36
  case arg
37
37
  when Matcher
38
38
  arg
39
- when Regexp, TrueClass, FalseClass, NilClass
39
+ when Regexp, Module
40
+ Matcher::Terminal.new arg
41
+ when TrueClass, FalseClass, NilClass
40
42
  Matcher::Terminal.new arg
41
43
  when lambda{|x| x.is_a?(Array) && x.size == 1 && x.first.is_a?(Array)}
42
44
  Matcher::Sequence.new arg.first.map{|s| compile_rule_defn(s) }
@@ -46,6 +48,9 @@ module Sexpr
46
48
  Matcher::Many.new compile_rule_defn($`), $1
47
49
  when /^[a-z][a-z_]+$/
48
50
  Matcher::Reference.new arg.to_sym, grammar
51
+ when /^::([A-Z][a-z]*.*)$/
52
+ found = $1.split('::').inject(Kernel){|cl,n| cl.const_get(n)}
53
+ Matcher::Terminal.new found
49
54
  else
50
55
  raise ArgumentError, "Invalid rule definition: #{arg.inspect}", caller
51
56
  end
@@ -11,7 +11,7 @@ module Sexpr
11
11
  parser!.parse(input, options)
12
12
  end
13
13
 
14
- private
14
+ private
15
15
 
16
16
  def parser!
17
17
  unless p = parser
@@ -15,25 +15,42 @@ module Sexpr
15
15
  nil
16
16
  end
17
17
 
18
+ def default_tagging_module
19
+ nil
20
+ end
21
+
22
+ def tagging_module_for(rulename)
23
+ if ref = tagging_reference
24
+ modname = rule2modname(rulename)
25
+ ref.const_get(modname, false) rescue default_tagging_module
26
+ else
27
+ default_tagging_module
28
+ end
29
+ end
30
+
18
31
  def sexpr(input, markers = nil)
19
32
  case input
20
33
  when Array
21
- tag_sexpr input, tagging_reference, markers
34
+ tag_sexpr input, markers
22
35
  else
23
36
  sexpr = parser!.to_sexpr(parse(input))
24
- tag_sexpr sexpr, tagging_reference, markers, true
37
+ tag_sexpr sexpr, markers, true
25
38
  end
26
39
  end
27
40
 
28
- private
41
+ def looks_a_sexpr?(arg)
42
+ arg.is_a?(Array) and arg.first.is_a?(Symbol)
43
+ end
44
+
45
+ private
29
46
 
30
- def tag_sexpr(sexpr, reference, markers = nil, force = false)
47
+ def tag_sexpr(sexpr, markers = nil, force = false)
31
48
  return sexpr unless looks_a_sexpr?(sexpr)
32
49
  return sexpr if Sexpr===sexpr and not(force) and markers.nil?
33
50
 
34
51
  # set the Sexpr modules
35
52
  sexpr.extend(Sexpr) unless Sexpr===sexpr
36
- tag_sexpr_with_user_module(sexpr, reference) if reference
53
+ tag_sexpr_with_user_module(sexpr)
37
54
 
38
55
  # set the markers if any
39
56
  if markers
@@ -43,20 +60,15 @@ module Sexpr
43
60
 
44
61
  # recurse
45
62
  sexpr[1..-1].each do |child|
46
- tag_sexpr(child, reference, nil, force)
63
+ tag_sexpr(child, nil, force)
47
64
  end
48
65
  sexpr
49
66
  end
50
67
 
51
- def tag_sexpr_with_user_module(sexpr, reference)
52
- rulename = sexpr.first
53
- modname = rule2modname(rulename)
54
- mod = reference.const_get(modname) rescue nil
55
- sexpr.extend(mod) if mod
56
- end
57
-
58
- def looks_a_sexpr?(arg)
59
- arg.is_a?(Array) and arg.first.is_a?(Symbol)
68
+ def tag_sexpr_with_user_module(sexpr)
69
+ tag = tagging_module_for(sexpr.first)
70
+ sexpr.extend(tag) if tag
71
+ sexpr
60
72
  end
61
73
 
62
74
  def parser!
@@ -28,11 +28,26 @@ module Sexpr
28
28
  i >= @min ? last : nil
29
29
  end
30
30
 
31
+ def parse(sexp, to = [])
32
+ i, last, got = 0, sexp, []
33
+ while sexp && (@max.nil? || i < @max)
34
+ if res = @term.parse(sexp, got)
35
+ last = res
36
+ i += 1
37
+ end
38
+ sexp = res
39
+ end
40
+ if i >= @min
41
+ got.each{|x| to << x }
42
+ last
43
+ end
44
+ end
45
+
31
46
  def inspect
32
47
  "(many #{term.inspect}, #{min}, #{max})"
33
48
  end
34
49
 
35
- private
50
+ private
36
51
 
37
52
  def minmax(min, max)
38
53
  case min
@@ -0,0 +1,31 @@
1
+ module Sexpr
2
+ module Matcher
3
+ class NonTerminal
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
+ "(non-terminal #{name}, #{defn.inspect})"
27
+ end
28
+
29
+ end # class NonTerminal
30
+ end # module Matcher
31
+ end # module Sexpr
@@ -12,14 +12,11 @@ module Sexpr
12
12
  end
13
13
 
14
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])
15
+ defn.match?(sexp)
18
16
  end
19
17
 
20
18
  def eat(sexp)
21
- return nil unless match?(sexp.first)
22
- sexp[1..-1]
19
+ defn.eat(sexp)
23
20
  end
24
21
 
25
22
  def inspect
@@ -28,4 +25,4 @@ module Sexpr
28
25
 
29
26
  end # class Rule
30
27
  end # module Matcher
31
- end # module Sexpr
28
+ end # module Sexpr
@@ -25,7 +25,7 @@ module Sexpr
25
25
 
26
26
  def terminal_match?(term)
27
27
  case @value
28
- when Regexp
28
+ when Regexp, Module
29
29
  @value === term rescue false
30
30
  when TrueClass, FalseClass, NilClass
31
31
  @value == term
data/lib/sexpr/matcher.rb CHANGED
@@ -10,6 +10,7 @@ end # module Sexpr
10
10
  require_relative "matcher/alternative"
11
11
  require_relative "matcher/many"
12
12
  require_relative "matcher/reference"
13
- require_relative "matcher/rule"
14
13
  require_relative "matcher/sequence"
15
- require_relative "matcher/terminal"
14
+ require_relative "matcher/terminal"
15
+ require_relative "matcher/non_terminal"
16
+ require_relative "matcher/rule"
data/lib/sexpr/node.rb CHANGED
@@ -31,7 +31,7 @@ module Sexpr
31
31
  end
32
32
  alias :dup :sexpr_copy
33
33
 
34
- private
34
+ private
35
35
 
36
36
  def sexpr_copy_tagging(copy)
37
37
  (class << self; self; end).included_modules.each do |mod|
data/lib/sexpr/parser.rb CHANGED
@@ -28,7 +28,7 @@ module Sexpr
28
28
 
29
29
  end # class methods
30
30
 
31
- private
31
+ private
32
32
 
33
33
  def input_text(input)
34
34
  case input
@@ -16,7 +16,7 @@ module Sexpr
16
16
  yield(processor, sexpr)
17
17
  end
18
18
 
19
- private
19
+ private
20
20
 
21
21
  def next_call(processor, sexpr, toplevel)
22
22
  if nic = next_in_chain
@@ -5,40 +5,32 @@ module Sexpr
5
5
  module Methods
6
6
 
7
7
  def parse(*args)
8
- sexpr_grammar.parse(*args)
8
+ grammar.parse(*args)
9
9
  end
10
10
 
11
11
  def sexpr(*args)
12
- sexpr_grammar.sexpr(*args)
12
+ grammar.sexpr(*args)
13
13
  end
14
14
 
15
15
  end
16
16
 
17
17
  def call(processor, sexpr, &bl)
18
+ g = processor.class.grammar
19
+
18
20
  # input coercion
19
- sexpr = grammar(processor).sexpr(sexpr)
21
+ sexpr = g.sexpr(sexpr)
20
22
 
21
23
  # recursive call
22
24
  sexpr = next_call(processor, sexpr, bl)
23
25
 
24
26
  # output coercion
25
27
  if sexpr.is_a?(Array) and sexpr.first.is_a?(Symbol)
26
- grammar(processor).sexpr(sexpr)
28
+ g.sexpr(sexpr)
27
29
  else
28
30
  sexpr
29
31
  end
30
32
  end
31
33
 
32
- private
33
-
34
- def grammar(processor)
35
- if processor.respond_to?(:sexpr_grammar)
36
- processor.sexpr_grammar
37
- else
38
- Sexpr
39
- end
40
- end
41
-
42
34
  end # class SexprCoercions
43
35
  end # class Processor
44
36
  end # module Sexpr
@@ -4,36 +4,63 @@ require_relative 'processor/sexpr_coercions'
4
4
  module Sexpr
5
5
  class Processor
6
6
 
7
- ### class methods
7
+ class << self
8
8
 
9
- def self.helpers
10
- @helpers ||= superclass.helpers.dup rescue [ ]
11
- end
9
+ def grammar(grammar = nil)
10
+ @grammar = grammar if grammar
11
+ @grammar ||= (@grammar || superclass.grammar) rescue Sexpr
12
+ @grammar
13
+ end
12
14
 
13
- def self.helper(helper_class)
14
- methods = helper_class.const_get(:Methods) rescue nil
15
- module_eval{ include methods } if methods
16
- helpers << helper_class
17
- end
15
+ def preprocessors
16
+ @preprocessors ||= superclass.preprocessors.dup rescue [ ]
17
+ end
18
18
 
19
- def self.build_helper_chain(helpers = self.helpers)
20
- return NullHelper.new if helpers.empty?
21
- helpers[0...-1].reverse.inject(helpers.last.new) do |chain, h_class|
22
- prepended = h_class.new
23
- prepended.next_in_chain = chain
24
- prepended
19
+ def use(preprocessor, options = nil)
20
+ preprocessor.keys.each{|k| attr_reader(k)} if preprocessor.is_a?(Hash)
21
+ preprocessors << [preprocessor, options]
22
+ end
23
+
24
+ def helpers
25
+ @helpers ||= superclass.helpers.dup rescue [ ]
26
+ end
27
+
28
+ def helper(helper_class)
29
+ methods = helper_class.const_get(:Methods) rescue nil
30
+ module_eval{ include methods } if methods
31
+ helpers << helper_class
25
32
  end
26
- end
27
33
 
28
- ### instance methods
34
+ def build_helper_chain(helpers = self.helpers)
35
+ return NullHelper.new if helpers.empty?
36
+ helpers[0...-1].reverse.inject(helpers.last.new) do |chain, h_class|
37
+ prepended = h_class.new
38
+ prepended.next_in_chain = chain
39
+ prepended
40
+ end
41
+ end
42
+
43
+ def call(sexpr, options = nil)
44
+ new(options).call(sexpr)
45
+ end
29
46
 
30
- attr_reader :main_processor
47
+ end # class << self
31
48
 
32
- def initialize(options = {})
33
- @main_processor = options.delete(:main_processor) || self
49
+ attr_reader :options
50
+
51
+ def initialize(options = nil)
52
+ @options = options || {}
53
+ end
54
+
55
+ def grammar
56
+ self.class.grammar
34
57
  end
35
58
 
36
59
  def call(sexpr)
60
+ apply(preprocess(sexpr))
61
+ end
62
+
63
+ def apply(sexpr)
37
64
  help(sexpr) do |n|
38
65
  meth = :"on_#{n.first}"
39
66
  meth = :"on_missing" unless respond_to?(meth)
@@ -45,7 +72,28 @@ module Sexpr
45
72
  raise UnexpectedSexprError, "Unexpected sexpr: #{sexpr.inspect}"
46
73
  end
47
74
 
48
- private
75
+ private
76
+
77
+ def preprocess(sexpr)
78
+ preprocessors = self.class.preprocessors
79
+ preprocessors.each do |pre|
80
+ sexpr = _preprocess(sexpr, pre)
81
+ end
82
+ sexpr
83
+ end
84
+
85
+ def _preprocess(sexpr, preprocessing)
86
+ pre_class, options = preprocessing
87
+ if Hash===pre_class
88
+ pre_class.each_pair do |k,v|
89
+ computed = v.new(options).call(sexpr)
90
+ self.instance_variable_set(:"@#{k}", computed)
91
+ end
92
+ sexpr
93
+ else
94
+ pre_class.new(options).call(sexpr)
95
+ end
96
+ end
49
97
 
50
98
  def helper_chain
51
99
  @helper_chain ||= self.class.build_helper_chain
@@ -2,17 +2,9 @@ module Sexpr
2
2
  class Rewriter < Processor
3
3
  helper SexprCoercions
4
4
 
5
- def self.grammar(sexpr_grammar)
6
- @sexpr_grammar = sexpr_grammar
7
- end
8
-
9
- def sexpr_grammar
10
- (self.class.instance_variable_get(:"@sexpr_grammar") || super) rescue Sexpr
11
- end
12
-
13
5
  def copy_and_apply(sexpr)
14
6
  sexpr.sexpr_copy do |copy, child|
15
- copy << (Sexpr===child ? call(child) : child)
7
+ copy << (Sexpr===child ? apply(child) : child)
16
8
  end
17
9
  end
18
10
 
data/lib/sexpr/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Sexpr
2
2
  module Version
3
3
 
4
- MAJOR = 0
5
- MINOR = 4
4
+ MAJOR = 1
5
+ MINOR = 0
6
6
  TINY = 0
7
7
 
8
8
  def self.to_s
@@ -11,4 +11,4 @@ module Sexpr
11
11
 
12
12
  end
13
13
  VERSION = Version.to_s
14
- end
14
+ end