sexp_grammar 0.1.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.
- data/CHANGELOG.md +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +25 -0
- data/LICENCE.md +22 -0
- data/Manifest.txt +15 -0
- data/README.md +46 -0
- data/Rakefile +23 -0
- data/lib/sexp_grammar.rb +31 -0
- data/lib/sexp_grammar/alternative.rb +28 -0
- data/lib/sexp_grammar/element.rb +9 -0
- data/lib/sexp_grammar/grammar.rb +57 -0
- data/lib/sexp_grammar/loader.rb +1 -0
- data/lib/sexp_grammar/many.rb +52 -0
- data/lib/sexp_grammar/reference.rb +30 -0
- data/lib/sexp_grammar/rule.rb +29 -0
- data/lib/sexp_grammar/sequence.rb +28 -0
- data/lib/sexp_grammar/terminal.rb +35 -0
- data/lib/sexp_grammar/version.rb +14 -0
- data/sexp_grammar.gemspec +188 -0
- data/sexp_grammar.noespec +25 -0
- data/spec/alternative/test_eat.rb +20 -0
- data/spec/alternative/test_match_q.rb +20 -0
- data/spec/bool_expr.yml +19 -0
- data/spec/grammar.yml +24 -0
- data/spec/grammar/test_compile_rule.rb +25 -0
- data/spec/grammar/test_compile_rule_defn.rb +98 -0
- data/spec/grammar/test_fetch.rb +16 -0
- data/spec/many/test_eat.rb +59 -0
- data/spec/many/test_initialize.rb +36 -0
- data/spec/many/test_match_q.rb +24 -0
- data/spec/reference/test_eat.rb +13 -0
- data/spec/reference/test_match_q.rb +20 -0
- data/spec/rule/test_eat.rb +21 -0
- data/spec/rule/test_match_q.rb +24 -0
- data/spec/sequence/test_eat.rb +20 -0
- data/spec/sequence/test_match_q.rb +25 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/terminal/test_eat.rb +20 -0
- data/spec/terminal/test_match_q.rb +56 -0
- data/spec/terminal/test_terminal_match.rb +89 -0
- data/spec/test_bool_expr.rb +27 -0
- data/spec/test_load.rb +35 -0
- data/spec/test_readme_examples.rb +44 -0
- data/spec/test_sexp_grammar.rb +8 -0
- data/tasks/debug_mail.rake +75 -0
- data/tasks/debug_mail.txt +13 -0
- data/tasks/gem.rake +68 -0
- data/tasks/spec_test.rake +71 -0
- data/tasks/unit_test.rake +76 -0
- data/tasks/yard.rake +51 -0
- metadata +169 -0
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
epath (0.0.1)
|
6
|
+
rake (0.9.2.2)
|
7
|
+
rspec (2.8.0)
|
8
|
+
rspec-core (~> 2.8.0)
|
9
|
+
rspec-expectations (~> 2.8.0)
|
10
|
+
rspec-mocks (~> 2.8.0)
|
11
|
+
rspec-core (2.8.0)
|
12
|
+
rspec-expectations (2.8.0)
|
13
|
+
diff-lcs (~> 1.1.2)
|
14
|
+
rspec-mocks (2.8.0)
|
15
|
+
wlang (0.10.2)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
java
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
epath (~> 0.0.1)
|
23
|
+
rake (~> 0.9.2)
|
24
|
+
rspec (~> 2.8.0)
|
25
|
+
wlang (~> 0.10.2)
|
data/LICENCE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# The MIT Licence
|
2
|
+
|
3
|
+
Copyright (c) 2012 - Bernard Lambeau
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# SexpGrammar
|
2
|
+
|
3
|
+
A helper to manipulate sexp grammars.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
grammar = SexpGrammar.load(<<-YAML)
|
8
|
+
|
9
|
+
# alternative rule
|
10
|
+
bool_expr:
|
11
|
+
- bool_and
|
12
|
+
- bool_or
|
13
|
+
- bool_not
|
14
|
+
- var_ref
|
15
|
+
- literal
|
16
|
+
|
17
|
+
# non-terminal
|
18
|
+
bool_and:
|
19
|
+
- [ bool_expr, bool_expr ]
|
20
|
+
bool_or:
|
21
|
+
- [ bool_expr, bool_expr ]
|
22
|
+
bool_not:
|
23
|
+
- [ bool_expr ]
|
24
|
+
literal:
|
25
|
+
- [ truth_value ]
|
26
|
+
var_ref:
|
27
|
+
- [ var_name ]
|
28
|
+
|
29
|
+
# terminals
|
30
|
+
var_name:
|
31
|
+
!ruby/regexp /^[a-z]+$/
|
32
|
+
truth_value:
|
33
|
+
- true
|
34
|
+
- false
|
35
|
+
|
36
|
+
YAML
|
37
|
+
|
38
|
+
grammar === [:bool_and, [:bool_not, [:var_ref, "x"]], [:literal, true]]
|
39
|
+
# => true
|
40
|
+
|
41
|
+
grammar === [:bool_and, [:literal, "true"]]
|
42
|
+
# => false (second term is missing)
|
43
|
+
|
44
|
+
## Links
|
45
|
+
|
46
|
+
https://github.com/blambeau/sexp_grammar
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
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('../sexp_grammar.gemspec', __FILE__)
|
11
|
+
$gemspec = Kernel.eval(File.read($gemspec_file))
|
12
|
+
|
13
|
+
# We run tests by default
|
14
|
+
task :default => :test
|
15
|
+
|
16
|
+
#
|
17
|
+
# Install all tasks found in tasks folder
|
18
|
+
#
|
19
|
+
# See .rake files there for complete documentation.
|
20
|
+
#
|
21
|
+
Dir["tasks/*.rake"].each do |taskfile|
|
22
|
+
load taskfile
|
23
|
+
end
|
data/lib/sexp_grammar.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative "sexp_grammar/version"
|
2
|
+
require_relative "sexp_grammar/loader"
|
3
|
+
#
|
4
|
+
# A helper to manipulate sexp grammars
|
5
|
+
#
|
6
|
+
module SexpGrammar
|
7
|
+
|
8
|
+
def self.load(input)
|
9
|
+
case input
|
10
|
+
when lambda{|x| x.respond_to?(:to_path)}
|
11
|
+
require 'yaml'
|
12
|
+
load(YAML.load_file input.to_path)
|
13
|
+
when String
|
14
|
+
require 'yaml'
|
15
|
+
load(YAML.load input)
|
16
|
+
when Hash
|
17
|
+
Grammar.new(input)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Invalid argument for SexpGrammar::Grammar: #{input}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end # module SexpGrammar
|
24
|
+
require_relative "sexp_grammar/grammar"
|
25
|
+
require_relative "sexp_grammar/element"
|
26
|
+
require_relative "sexp_grammar/alternative"
|
27
|
+
require_relative "sexp_grammar/many"
|
28
|
+
require_relative "sexp_grammar/reference"
|
29
|
+
require_relative "sexp_grammar/rule"
|
30
|
+
require_relative "sexp_grammar/sequence"
|
31
|
+
require_relative "sexp_grammar/terminal"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Alternative
|
3
|
+
include Element
|
4
|
+
|
5
|
+
attr_reader :terms
|
6
|
+
|
7
|
+
def initialize(terms)
|
8
|
+
@terms = terms
|
9
|
+
end
|
10
|
+
|
11
|
+
def match?(sexp)
|
12
|
+
terms.any?{|t| t.match?(sexp)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def eat(sexp)
|
16
|
+
@terms.each do |alt|
|
17
|
+
res = alt.eat(sexp)
|
18
|
+
return res if res
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
"(alt #{terms.inspect})"
|
25
|
+
end
|
26
|
+
|
27
|
+
end # class Alternative
|
28
|
+
end # module SexpGrammar
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Grammar
|
3
|
+
|
4
|
+
attr_reader :rules
|
5
|
+
|
6
|
+
def initialize(rules = {}, root = rules.keys.first)
|
7
|
+
@rules = compile_rules(rules)
|
8
|
+
@root = root && self[root.to_sym]
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](rule_name)
|
12
|
+
@rules[rule_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def match?(sexp)
|
16
|
+
@root.match?(sexp)
|
17
|
+
end
|
18
|
+
alias :=== :match?
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def compile_rules(rules)
|
23
|
+
Hash[rules.map{|k,v|
|
24
|
+
[k.to_sym, compile_rule(k.to_sym, v)]
|
25
|
+
}]
|
26
|
+
end
|
27
|
+
|
28
|
+
def compile_rule(name, defn)
|
29
|
+
case rule = compile_rule_defn(defn)
|
30
|
+
when Terminal, Alternative
|
31
|
+
rule
|
32
|
+
else
|
33
|
+
Rule.new(name, rule)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def compile_rule_defn(arg)
|
38
|
+
case arg
|
39
|
+
when Element
|
40
|
+
arg
|
41
|
+
when Regexp, TrueClass, FalseClass, NilClass
|
42
|
+
Terminal.new arg
|
43
|
+
when lambda{|x| x.is_a?(Array) && x.size == 1 && x.first.is_a?(Array)}
|
44
|
+
Sequence.new arg.first.map{|s| compile_rule_defn(s) }
|
45
|
+
when Array
|
46
|
+
Alternative.new arg.map{|s| compile_rule_defn(s)}
|
47
|
+
when /([\?\+\*])$/
|
48
|
+
Many.new compile_rule_defn($`), $1
|
49
|
+
when /^[a-z][a-z_]+$/
|
50
|
+
Reference.new arg.to_sym, self
|
51
|
+
else
|
52
|
+
raise ArgumentError, "Invalid rule definition: #{arg.inspect}", caller
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end # class Grammar
|
57
|
+
end # module SexpGrammar
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Many
|
3
|
+
include Element
|
4
|
+
|
5
|
+
attr_reader :term, :min, :max
|
6
|
+
|
7
|
+
def initialize(term, min, max = nil)
|
8
|
+
@term = term
|
9
|
+
@min, @max = minmax(min, max)
|
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
|
+
i, last = 0, sexp
|
20
|
+
while sexp && (@max.nil? || i < @max)
|
21
|
+
if res = @term.eat(sexp)
|
22
|
+
last = res
|
23
|
+
i += 1
|
24
|
+
end
|
25
|
+
sexp = res
|
26
|
+
end
|
27
|
+
i >= @min ? last : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
"(many #{term.inspect}, #{min}, #{max})"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def minmax(min, max)
|
37
|
+
case min
|
38
|
+
when Integer
|
39
|
+
[min, max]
|
40
|
+
when '?'
|
41
|
+
[0, 1]
|
42
|
+
when '+'
|
43
|
+
[1, nil]
|
44
|
+
when '*'
|
45
|
+
[0, nil]
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Invalid multiplicity: #{min}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end # class Sequence
|
52
|
+
end # module SexpGrammar
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Reference
|
3
|
+
include Element
|
4
|
+
|
5
|
+
attr_reader :rule_name
|
6
|
+
attr_reader :grammar
|
7
|
+
|
8
|
+
def initialize(rule_name, grammar)
|
9
|
+
@rule_name = rule_name
|
10
|
+
@grammar = grammar
|
11
|
+
end
|
12
|
+
|
13
|
+
def rule
|
14
|
+
@rule ||= @grammar[@rule_name]
|
15
|
+
end
|
16
|
+
|
17
|
+
def match?(sexp)
|
18
|
+
rule && rule.match?(sexp)
|
19
|
+
end
|
20
|
+
|
21
|
+
def eat(sexp)
|
22
|
+
rule && rule.eat(sexp)
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"(ref #{rule_name}, #{rule.inspect})"
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class Reference
|
30
|
+
end # module SexpGrammar
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Rule
|
3
|
+
include Element
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :defn
|
7
|
+
|
8
|
+
def initialize(name, defn)
|
9
|
+
@name = name
|
10
|
+
@defn = defn
|
11
|
+
end
|
12
|
+
|
13
|
+
def match?(sexp)
|
14
|
+
return nil unless sexp.is_a?(Array)
|
15
|
+
return false unless sexp.first && (sexp.first == name)
|
16
|
+
defn.match?(sexp[1..-1])
|
17
|
+
end
|
18
|
+
|
19
|
+
def eat(sexp)
|
20
|
+
return nil unless match?(sexp.first)
|
21
|
+
sexp[1..-1]
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"(rule #{name}, #{defn.inspect})"
|
26
|
+
end
|
27
|
+
|
28
|
+
end # class Rule
|
29
|
+
end # module SexpGrammar
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Sequence
|
3
|
+
include Element
|
4
|
+
|
5
|
+
attr_reader :terms
|
6
|
+
|
7
|
+
def initialize(terms)
|
8
|
+
@terms = terms
|
9
|
+
end
|
10
|
+
|
11
|
+
def match?(sexp)
|
12
|
+
return nil unless sexp.is_a?(Array)
|
13
|
+
eat = eat(sexp)
|
14
|
+
eat && eat.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def eat(sexp)
|
18
|
+
@terms.inject sexp do |rest,rule|
|
19
|
+
rest && rule.eat(rest)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
"(seq #{terms.inspect})"
|
25
|
+
end
|
26
|
+
|
27
|
+
end # class Sequence
|
28
|
+
end # module SexpGrammar
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SexpGrammar
|
2
|
+
class Terminal
|
3
|
+
include Element
|
4
|
+
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"(terminal #{value.inspect})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def match?(sexp)
|
16
|
+
terminal_match?(sexp)
|
17
|
+
end
|
18
|
+
|
19
|
+
def eat(sexp)
|
20
|
+
match?(sexp.first) ? sexp[1..-1] : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def terminal_match?(term)
|
26
|
+
case @value
|
27
|
+
when Regexp
|
28
|
+
@value === term rescue false
|
29
|
+
when TrueClass, FalseClass, NilClass
|
30
|
+
@value == term
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end # class Terminal
|
35
|
+
end # module SexpGrammar
|