sexp_grammar 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|