sexpr 0.2.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 +13 -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/sexpr.rb +31 -0
- data/lib/sexpr/alternative.rb +28 -0
- data/lib/sexpr/element.rb +9 -0
- data/lib/sexpr/grammar.rb +82 -0
- data/lib/sexpr/loader.rb +1 -0
- data/lib/sexpr/many.rb +52 -0
- data/lib/sexpr/reference.rb +30 -0
- data/lib/sexpr/rule.rb +29 -0
- data/lib/sexpr/sequence.rb +28 -0
- data/lib/sexpr/terminal.rb +35 -0
- data/lib/sexpr/version.rb +14 -0
- data/sexpr.gemspec +188 -0
- data/sexpr.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 +18 -0
- data/spec/grammar/test_parse.rb +32 -0
- data/spec/grammar/test_root.rb +20 -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_sexpr.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 +173 -0
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# 0.2.0 / 2012-02-21
|
2
|
+
|
3
|
+
* Enhancements
|
4
|
+
|
5
|
+
* The project has been renamed as Sexpr instead of SexpGrammar
|
6
|
+
* The root rule to use can now be specified in the options hash taken as second argument
|
7
|
+
of `SexpGrammar.grammar(..., :root => :some_rule_name)`
|
8
|
+
|
9
|
+
# 0.1.0 / 2012-02-20
|
10
|
+
|
11
|
+
* Enhancements
|
12
|
+
|
13
|
+
* Birthday!
|
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
|
+
# Sexpr
|
2
|
+
|
3
|
+
A ruby compilation framework around s-expressions.
|
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/sexpr
|
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('../sexpr.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/sexpr.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative "sexpr/version"
|
2
|
+
require_relative "sexpr/loader"
|
3
|
+
#
|
4
|
+
# A helper to manipulate sexp grammars
|
5
|
+
#
|
6
|
+
module Sexpr
|
7
|
+
|
8
|
+
def self.load(input, options = {})
|
9
|
+
case input
|
10
|
+
when lambda{|x| x.respond_to?(:to_path)}
|
11
|
+
require 'yaml'
|
12
|
+
load(YAML.load_file(input.to_path), options)
|
13
|
+
when String
|
14
|
+
require 'yaml'
|
15
|
+
load(YAML.load(input), options)
|
16
|
+
when Hash
|
17
|
+
Grammar.new(input, options)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Invalid argument for Sexpr::Grammar: #{input}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end # module Sexpr
|
24
|
+
require_relative "sexpr/grammar"
|
25
|
+
require_relative "sexpr/element"
|
26
|
+
require_relative "sexpr/alternative"
|
27
|
+
require_relative "sexpr/many"
|
28
|
+
require_relative "sexpr/reference"
|
29
|
+
require_relative "sexpr/rule"
|
30
|
+
require_relative "sexpr/sequence"
|
31
|
+
require_relative "sexpr/terminal"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Sexpr
|
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 Sexpr
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Sexpr
|
2
|
+
class Grammar
|
3
|
+
|
4
|
+
attr_reader :rules, :options
|
5
|
+
|
6
|
+
def initialize(rules = {}, options = {})
|
7
|
+
@rules = compile_rules(rules)
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](rule_name)
|
12
|
+
@rules[rule_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def root
|
16
|
+
@root ||= begin
|
17
|
+
root = options[:root] || rules.keys.first
|
18
|
+
root = self[root] if root.is_a?(Symbol)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def parser
|
23
|
+
options[:parser]
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(input)
|
27
|
+
case input
|
28
|
+
when lambda{|x| x.respond_to?(:to_path)}
|
29
|
+
parse(File.read(input.to_path))
|
30
|
+
when IO
|
31
|
+
parse(input.read)
|
32
|
+
when String
|
33
|
+
unless p = parser
|
34
|
+
raise NoParserError, "No parser set.", caller
|
35
|
+
end
|
36
|
+
p.parse(input).value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def match?(sexp)
|
41
|
+
root.match?(sexp)
|
42
|
+
end
|
43
|
+
alias :=== :match?
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def compile_rules(rules)
|
48
|
+
Hash[rules.map{|k,v|
|
49
|
+
[k.to_sym, compile_rule(k.to_sym, v)]
|
50
|
+
}]
|
51
|
+
end
|
52
|
+
|
53
|
+
def compile_rule(name, defn)
|
54
|
+
case rule = compile_rule_defn(defn)
|
55
|
+
when Terminal, Alternative
|
56
|
+
rule
|
57
|
+
else
|
58
|
+
Rule.new(name, rule)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def compile_rule_defn(arg)
|
63
|
+
case arg
|
64
|
+
when Element
|
65
|
+
arg
|
66
|
+
when Regexp, TrueClass, FalseClass, NilClass
|
67
|
+
Terminal.new arg
|
68
|
+
when lambda{|x| x.is_a?(Array) && x.size == 1 && x.first.is_a?(Array)}
|
69
|
+
Sequence.new arg.first.map{|s| compile_rule_defn(s) }
|
70
|
+
when Array
|
71
|
+
Alternative.new arg.map{|s| compile_rule_defn(s)}
|
72
|
+
when /([\?\+\*])$/
|
73
|
+
Many.new compile_rule_defn($`), $1
|
74
|
+
when /^[a-z][a-z_]+$/
|
75
|
+
Reference.new arg.to_sym, self
|
76
|
+
else
|
77
|
+
raise ArgumentError, "Invalid rule definition: #{arg.inspect}", caller
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end # class Grammar
|
82
|
+
end # module Sexpr
|
data/lib/sexpr/loader.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/lib/sexpr/many.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Sexpr
|
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 Sexpr
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Sexpr
|
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 Sexpr
|
data/lib/sexpr/rule.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sexpr
|
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 Sexpr
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Sexpr
|
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 Sexpr
|