sexpr 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/Gemfile +8 -0
  3. data/Gemfile.lock +25 -0
  4. data/LICENCE.md +22 -0
  5. data/Manifest.txt +15 -0
  6. data/README.md +46 -0
  7. data/Rakefile +23 -0
  8. data/lib/sexpr.rb +31 -0
  9. data/lib/sexpr/alternative.rb +28 -0
  10. data/lib/sexpr/element.rb +9 -0
  11. data/lib/sexpr/grammar.rb +82 -0
  12. data/lib/sexpr/loader.rb +1 -0
  13. data/lib/sexpr/many.rb +52 -0
  14. data/lib/sexpr/reference.rb +30 -0
  15. data/lib/sexpr/rule.rb +29 -0
  16. data/lib/sexpr/sequence.rb +28 -0
  17. data/lib/sexpr/terminal.rb +35 -0
  18. data/lib/sexpr/version.rb +14 -0
  19. data/sexpr.gemspec +188 -0
  20. data/sexpr.noespec +25 -0
  21. data/spec/alternative/test_eat.rb +20 -0
  22. data/spec/alternative/test_match_q.rb +20 -0
  23. data/spec/bool_expr.yml +19 -0
  24. data/spec/grammar.yml +24 -0
  25. data/spec/grammar/test_compile_rule.rb +25 -0
  26. data/spec/grammar/test_compile_rule_defn.rb +98 -0
  27. data/spec/grammar/test_fetch.rb +18 -0
  28. data/spec/grammar/test_parse.rb +32 -0
  29. data/spec/grammar/test_root.rb +20 -0
  30. data/spec/many/test_eat.rb +59 -0
  31. data/spec/many/test_initialize.rb +36 -0
  32. data/spec/many/test_match_q.rb +24 -0
  33. data/spec/reference/test_eat.rb +13 -0
  34. data/spec/reference/test_match_q.rb +20 -0
  35. data/spec/rule/test_eat.rb +21 -0
  36. data/spec/rule/test_match_q.rb +24 -0
  37. data/spec/sequence/test_eat.rb +20 -0
  38. data/spec/sequence/test_match_q.rb +25 -0
  39. data/spec/spec_helper.rb +3 -0
  40. data/spec/terminal/test_eat.rb +20 -0
  41. data/spec/terminal/test_match_q.rb +56 -0
  42. data/spec/terminal/test_terminal_match.rb +89 -0
  43. data/spec/test_bool_expr.rb +27 -0
  44. data/spec/test_load.rb +35 -0
  45. data/spec/test_readme_examples.rb +44 -0
  46. data/spec/test_sexpr.rb +8 -0
  47. data/tasks/debug_mail.rake +75 -0
  48. data/tasks/debug_mail.txt +13 -0
  49. data/tasks/gem.rake +68 -0
  50. data/tasks/spec_test.rake +71 -0
  51. data/tasks/unit_test.rake +76 -0
  52. data/tasks/yard.rake +51 -0
  53. metadata +173 -0
@@ -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
@@ -0,0 +1,8 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :development do
4
+ gem "epath", "~> 0.0.1"
5
+ gem "rake", "~> 0.9.2"
6
+ gem "rspec", "~> 2.8.0"
7
+ gem "wlang", "~> 0.10.2"
8
+ end
@@ -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)
@@ -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.
@@ -0,0 +1,15 @@
1
+ sexpr.gemspec
2
+ sexpr.noespec
3
+ .gemtest
4
+ CHANGELOG.md
5
+ Gemfile
6
+ Gemfile.lock
7
+ bin/**/*
8
+ lib/**/*
9
+ LICENCE.md
10
+ Manifest.txt
11
+ Rakefile
12
+ README.md
13
+ spec/**/*
14
+ tasks/**/*
15
+ test/**/*
@@ -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
@@ -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
@@ -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,9 @@
1
+ module Sexpr
2
+ module Element
3
+
4
+ def ===(sexp)
5
+ match?(sexp)
6
+ end
7
+
8
+ end # module Sexpr
9
+ 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
@@ -0,0 +1 @@
1
+
@@ -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
@@ -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