sbyc 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.
Files changed (59) hide show
  1. data/LICENCE.textile +12 -0
  2. data/README.textile +44 -0
  3. data/lib/sbyc.rb +14 -0
  4. data/lib/sbyc/codetree.rb +82 -0
  5. data/lib/sbyc/codetree/ast_node.rb +101 -0
  6. data/lib/sbyc/codetree/eval.rb +3 -0
  7. data/lib/sbyc/codetree/eval/ast_node_ext.rb +38 -0
  8. data/lib/sbyc/codetree/eval/functional_eval.rb +36 -0
  9. data/lib/sbyc/codetree/eval/object_eval.rb +36 -0
  10. data/lib/sbyc/codetree/matching.rb +3 -0
  11. data/lib/sbyc/codetree/matching/ast_node_ext.rb +14 -0
  12. data/lib/sbyc/codetree/matching/match_data.rb +30 -0
  13. data/lib/sbyc/codetree/matching/matcher.rb +83 -0
  14. data/lib/sbyc/codetree/proc_parser.rb +91 -0
  15. data/lib/sbyc/codetree/producing.rb +1 -0
  16. data/lib/sbyc/codetree/producing/producer.rb +68 -0
  17. data/lib/sbyc/codetree/rewriting.rb +4 -0
  18. data/lib/sbyc/codetree/rewriting/class_methods.rb +15 -0
  19. data/lib/sbyc/codetree/rewriting/compiler.rb +28 -0
  20. data/lib/sbyc/codetree/rewriting/instance_methods.rb +92 -0
  21. data/lib/sbyc/codetree/rewriting/match.rb +59 -0
  22. data/test/spec/documentation/codetree/production.spec +59 -0
  23. data/test/spec/documentation/readme/assumptions.spec +33 -0
  24. data/test/spec/documentation/readme/functional_evaluation.spec +29 -0
  25. data/test/spec/documentation/readme/object_evaluation.spec +17 -0
  26. data/test/spec/documentation/readme/rewriting.spec +60 -0
  27. data/test/spec/documentation/readme/semantics.spec +21 -0
  28. data/test/spec/documentation/readme/synopsis.spec +27 -0
  29. data/test/spec/documentation/readme/syntax.spec +26 -0
  30. data/test/spec/spec_helper.rb +13 -0
  31. data/test/spec/test_all.rb +6 -0
  32. data/test/spec/unit/sbyc/codetree/ast_node/coerce.spec +60 -0
  33. data/test/spec/unit/sbyc/codetree/ast_node/equality.spec +75 -0
  34. data/test/spec/unit/sbyc/codetree/ast_node/inspect.spec +23 -0
  35. data/test/spec/unit/sbyc/codetree/ast_node/literal.spec +15 -0
  36. data/test/spec/unit/sbyc/codetree/ast_node/to_a.spec +27 -0
  37. data/test/spec/unit/sbyc/codetree/ast_node/to_s.spec +23 -0
  38. data/test/spec/unit/sbyc/codetree/ast_node/visit.spec +34 -0
  39. data/test/spec/unit/sbyc/codetree/eval/functional_compile.spec +30 -0
  40. data/test/spec/unit/sbyc/codetree/eval/functional_eval.spec +34 -0
  41. data/test/spec/unit/sbyc/codetree/eval/functional_proc.spec +36 -0
  42. data/test/spec/unit/sbyc/codetree/eval/object_compile.spec +36 -0
  43. data/test/spec/unit/sbyc/codetree/eval/object_eval.spec +38 -0
  44. data/test/spec/unit/sbyc/codetree/eval/object_proc.spec +36 -0
  45. data/test/spec/unit/sbyc/codetree/matching/matcher/args_match.spec +91 -0
  46. data/test/spec/unit/sbyc/codetree/matching/matcher/do_match.spec +39 -0
  47. data/test/spec/unit/sbyc/codetree/matching/matcher/function_match.spec +45 -0
  48. data/test/spec/unit/sbyc/codetree/matching/matcher/match.spec +64 -0
  49. data/test/spec/unit/sbyc/codetree/proc_parser/expr.spec +20 -0
  50. data/test/spec/unit/sbyc/codetree/proc_parser/parse.spec +83 -0
  51. data/test/spec/unit/sbyc/codetree/producing/producer.spec +31 -0
  52. data/test/spec/unit/sbyc/codetree/producing/producer/apply_args_conventions.spec +60 -0
  53. data/test/spec/unit/sbyc/codetree/rewriting/instance_methods/apply_args_conventions.spec +60 -0
  54. data/test/spec/unit/sbyc/codetree/rewriting/instance_methods/node.spec +19 -0
  55. data/test/spec/unit/sbyc/codetree/rewriting/instance_methods/rewrite.spec +76 -0
  56. data/test/spec/unit/sbyc/codetree/rewriting/match/apply.spec +23 -0
  57. data/test/spec/unit/sbyc/codetree/rewriting/match/coerce.spec +48 -0
  58. data/test/spec/unit/sbyc/codetree/rewriting/match/matches.spec +27 -0
  59. metadata +129 -0
@@ -0,0 +1,91 @@
1
+ module CodeTree
2
+ class ProcParser
3
+ class Expr
4
+
5
+ # Methods that we keep
6
+ KEPT_METHODS = [ "__send__", "__id__", "instance_eval", "initialize", "object_id",
7
+ "singleton_method_added", "singleton_method_undefined", "method_missing",
8
+ "__evaluate__", "coerce"]
9
+
10
+ class << self
11
+ def __clean_scope__
12
+ # Removes all methods that are not needed to the class
13
+ (instance_methods + private_instance_methods).each do |m|
14
+ m_to_s = m.to_s
15
+ undef_method(m_to_s.to_sym) unless ('__' == m_to_s[0..1]) or KEPT_METHODS.include?(m_to_s)
16
+ end
17
+ end
18
+ end
19
+
20
+ # Creates an expression instance
21
+ def initialize(name = nil, children = nil)
22
+ class << self; __clean_scope__; end
23
+ @name, @children = name, children
24
+ end
25
+
26
+ # Returns the associated functional code
27
+ def __to_functional_code
28
+ children = @children.collect{|c|
29
+ c.kind_of?(Expr) ? c.__to_functional_code : AstNode.coerce(c)
30
+ }
31
+ CodeTree::AstNode.coerce([@name, children])
32
+ end
33
+ alias :inspect :__to_functional_code
34
+
35
+ # Called when a method is missing
36
+ def method_missing(name, *args)
37
+ if @name.nil?
38
+ if name == :[]
39
+ Expr.new(:'?', args)
40
+ elsif args.empty?
41
+ Expr.new(:'?', [name])
42
+ else
43
+ Expr.new(name, args)
44
+ end
45
+ else
46
+ args.unshift(self)
47
+ Expr.new(name, args)
48
+ end
49
+ end
50
+
51
+ def coerce(other)
52
+ [self, other]
53
+ end
54
+
55
+ end # class Expr
56
+
57
+ # Parses a Proc object
58
+ def self.parse_proc(block)
59
+ e = case block.arity
60
+ when -1, 0
61
+ Expr.new.instance_eval(&block)
62
+ when 1
63
+ block.call(Expr.new)
64
+ else
65
+ raise ArgumentError, "Unexpected block arity #{block.arity}"
66
+ end
67
+ case e
68
+ when Expr
69
+ e.__to_functional_code
70
+ else
71
+ CodeTree::AstNode.coerce(e)
72
+ end
73
+ end
74
+
75
+ # Parses a block
76
+ def self.parse(code = nil, &block)
77
+ block = code || block
78
+ case block
79
+ when Proc
80
+ parse_proc(block)
81
+ when AstNode
82
+ block
83
+ when String
84
+ parse(Kernel.eval("proc{ #{block} }"))
85
+ else
86
+ raise ArgumentError, "Unable to parse #{block}"
87
+ end
88
+ end
89
+
90
+ end # class ProcParser
91
+ end # module CodeTree
@@ -0,0 +1 @@
1
+ require 'sbyc/codetree/producing/producer'
@@ -0,0 +1,68 @@
1
+ module CodeTree
2
+ module Producing
3
+ class Producer
4
+
5
+ # Rules kepts by node function
6
+ attr_reader :rules
7
+
8
+ # Creates a producer instance
9
+ def initialize(default_rules = true)
10
+ @rules = {}
11
+ if default_rules
12
+ rule(:_) {|r,node| node.literal}
13
+ rule("*"){|r,node| r.apply(node.children)}
14
+ end
15
+ yield(self) if block_given?
16
+ end
17
+
18
+ # Adds a rule
19
+ def rule(function_name, &block)
20
+ rules[function_name] = block
21
+ end
22
+
23
+ # Applies on some arguments
24
+ def apply(*args)
25
+ case args = apply_args_conventions(*args)
26
+ when CodeTree::AstNode
27
+ apply_on_node(args)
28
+ when Array
29
+ args.collect{|c| apply(c)}
30
+ else
31
+ args
32
+ end
33
+ end
34
+
35
+ # Applies on a given node
36
+ def apply_on_node(node)
37
+ func = node.function
38
+ if rules.key?(func)
39
+ @rules[func].call(self, node)
40
+ elsif rules.key?("*")
41
+ @rules["*"].call(self, node)
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ # Applies arument conventions allowed by _apply_
48
+ def apply_args_conventions(*args)
49
+ if args.size == 1 and args[0].kind_of?(CodeTree::AstNode)
50
+ args[0]
51
+ elsif args.size > 1 and args[0].kind_of?(Symbol)
52
+ function = args.shift
53
+ children = args.collect{|c| apply_args_conventions(c)}.flatten
54
+ CodeTree::AstNode.coerce([function, children])
55
+ elsif args.all?{|a| a.kind_of?(CodeTree::AstNode)}
56
+ args
57
+ elsif args.size == 1
58
+ args[0]
59
+ else
60
+ raise ArgumentError, "Unable to apply on #{args.inspect} (#{args.size})", caller
61
+ end
62
+ end
63
+
64
+ private :apply_on_node
65
+ private :apply_args_conventions
66
+ end # class Producer
67
+ end # module Producing
68
+ end # module CodeTree
@@ -0,0 +1,4 @@
1
+ require 'sbyc/codetree/rewriting/match'
2
+ require 'sbyc/codetree/rewriting/compiler'
3
+ require 'sbyc/codetree/rewriting/instance_methods'
4
+ require 'sbyc/codetree/rewriting/class_methods'
@@ -0,0 +1,15 @@
1
+ module CodeTree
2
+ module Rewriting
3
+ class Rewriter
4
+ module ClassMethods
5
+
6
+ # Compiles a rewriter
7
+ def compile(code = nil, &block)
8
+ Rewriter::Compiler::compile(code || block)
9
+ end
10
+
11
+ end # module ClassMethods
12
+ extend ClassMethods
13
+ end # class Rewriter
14
+ end # module Rewriting
15
+ end # module CodeTree
@@ -0,0 +1,28 @@
1
+ module CodeTree
2
+ module Rewriting
3
+ class Rewriter
4
+ module Compiler
5
+
6
+ # Compiles an Ast to a Rewriter instance
7
+ def compile(ast)
8
+ compiled = Rewriter.new
9
+ Rewriter.new{|r|
10
+ r.rule(:rewrite){|r, node, *children|
11
+ r.apply(children)
12
+ }
13
+ r.rule(:upon){|r, upon, upon_match, upon_rule|
14
+ matcher = CodeTree::matcher(upon_match)
15
+ upon_expr = CodeTree::expr(upon_rule)
16
+ r.scope.rule(matcher){|compiled, matched, *matched_children|
17
+ upon_expr.apply(compiled, (matcher =~ matched))
18
+ }
19
+ }
20
+ }.rewrite(ast, compiled)
21
+ compiled
22
+ end
23
+ module_function :compile
24
+
25
+ end # module Compiler
26
+ end # class Rewriter
27
+ end # module Rewriting
28
+ end # module CodeTree
@@ -0,0 +1,92 @@
1
+ module CodeTree
2
+ module Rewriting
3
+ class Rewriter
4
+ module InstanceMethods
5
+
6
+ # Installed rules
7
+ attr_reader :rules
8
+
9
+ # The scope
10
+ attr_reader :scope
11
+
12
+ # Creates a Rewriter instance
13
+ def initialize
14
+ @rules = []
15
+ yield(self) if block_given?
16
+ end
17
+
18
+ def ANY() Rewriter::Match::ANY; end
19
+ def BRANCH() Rewriter::Match::BRANCH; end
20
+ def LEAF() Rewriter::Match::LEAF; end
21
+
22
+ # Adds a rule to the engine
23
+ def rule(match, &block)
24
+ @rules << Rewriter::Match.coerce(match, block)
25
+ end
26
+
27
+ # Rewrites some code
28
+ def rewrite(code = nil, scope = nil, &block)
29
+ @stack = []
30
+ @scope = block ? code : scope
31
+ apply(CodeTree.coerce(block || code))
32
+ ensure
33
+ @stack = nil
34
+ @scope = nil
35
+ end
36
+
37
+ # Returns the current context node, being the top node on the stack
38
+ def context_node
39
+ @stack.last
40
+ end
41
+
42
+ # Applies rules on a node
43
+ def apply(*args)
44
+ case node = apply_args_conventions(*args)
45
+ when CodeTree::AstNode
46
+ apply_on_node(node)
47
+ when Array
48
+ node.collect{|c| c.kind_of?(CodeTree::AstNode) ? apply_on_node(c) : c}
49
+ else
50
+ node
51
+ end
52
+ end
53
+
54
+ # Applies on a single node
55
+ def apply_on_node(node)
56
+ raise ArgumentError, "Node expected, #{node.inspect} received" unless node.kind_of?(CodeTree::AstNode)
57
+ @stack.push(node)
58
+ rule = @rules.find{|r| r === node}
59
+ result = (rule ? rule.apply(self, node) : nil)
60
+ @stack.pop
61
+ result
62
+ end
63
+
64
+ # Produces a node by copying another one
65
+ def node(function, *children)
66
+ CodeTree::AstNode.coerce([function, children.flatten])
67
+ end
68
+
69
+ #
70
+ # Applies conventions announced by the _apply_ method.
71
+ #
72
+ def apply_args_conventions(*args)
73
+ if args.size == 1 and args[0].kind_of?(CodeTree::AstNode)
74
+ args[0]
75
+ elsif args.size > 1 and args[0].kind_of?(Symbol)
76
+ function = args.shift
77
+ children = args.collect{|c| apply_args_conventions(c)}.flatten
78
+ CodeTree::AstNode.coerce([function, children])
79
+ elsif args.all?{|a| a.kind_of?(CodeTree::AstNode)}
80
+ args
81
+ elsif args.size == 1
82
+ args[0]
83
+ else
84
+ raise ArgumentError, "Unable to apply on #{args.inspect} (#{args.size})", caller
85
+ end
86
+ end
87
+
88
+ end # module InstanceMethods
89
+ include InstanceMethods
90
+ end # class Rewriter
91
+ end # module Rewriting
92
+ end # module CodeTree
@@ -0,0 +1,59 @@
1
+ module CodeTree
2
+ module Rewriting
3
+ class Rewriter
4
+ class Match
5
+
6
+ # Matches any node
7
+ ANY = lambda{|ast_node| true}
8
+
9
+ # Matches a branch node
10
+ BRANCH = lambda{|ast_node| ast_node.kind_of?(CodeTree::AstNode) and ast_node.branch?}
11
+
12
+ # Matches a leaf node
13
+ LEAF = lambda{|ast_node| ast_node.kind_of?(CodeTree::AstNode) and ast_node.leaf?}
14
+
15
+ # Coerce argument to a Match instance
16
+ def self.coerce(arg, block)
17
+ case arg
18
+ when Proc
19
+ Match.new(arg, block)
20
+ when Symbol
21
+ Match.new(lambda{|node| node.kind_of?(CodeTree::AstNode) and node.name == arg}, block)
22
+ when CodeTree::Matcher
23
+ Match.new(arg, block)
24
+ when "."
25
+ Match.new(Match::ANY, block)
26
+ when "*"
27
+ Match.new(Match::BRANCH, block)
28
+ when "@*", "_"
29
+ Match.new(Match::LEAF, block)
30
+ else
31
+ raise "Unexpected rule #{arg.class} #{arg.inspect}"
32
+ end
33
+ end
34
+
35
+ # Creates a match instance
36
+ def initialize(predicate, block)
37
+ @predicate, @block = predicate, block
38
+ end
39
+
40
+ # Does this match
41
+ def matches?(ast_node)
42
+ @predicate.call(ast_node)
43
+ end
44
+ alias :=== :matches?
45
+
46
+ # Applies this match
47
+ def apply(rewriter, ast_node)
48
+ case ast_node
49
+ when CodeTree::AstNode
50
+ @block.call(rewriter, ast_node, *ast_node.children)
51
+ else
52
+ ast_node
53
+ end
54
+ end
55
+
56
+ end # class Match
57
+ end # class Rewriter
58
+ end # module Rewriting
59
+ end # module CodeTree
@@ -0,0 +1,59 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe "Gh-Pages documentation: codetree/production.redcloth" do
4
+
5
+ describe "What is said about the visit method" do
6
+ subject {
7
+ CodeTree::parse{ z * (x + y).to_s }.visit do |node, collected|
8
+ if node.function == :'?'
9
+ node.literal
10
+ else
11
+ collected.flatten
12
+ end
13
+ end
14
+ }
15
+ it { should == [:z, :x, :y] }
16
+ end
17
+
18
+ describe "What is said about the visit method (second example)" do
19
+ subject {
20
+ values = {:x => 3, :y => 6, :z => 2}
21
+ CodeTree::parse{ z * (x + y) }.visit do |node, collected|
22
+ case node.function
23
+ when :'?'
24
+ values[node.literal]
25
+ when :'+'
26
+ collected[0] + collected[1]
27
+ when :'*'
28
+ collected[0] * collected[1]
29
+ end
30
+ end
31
+ }
32
+ it { should == 18 }
33
+ end
34
+
35
+ describe "What is said about the engine (first example)" do
36
+ let(:producer) {
37
+ ::CodeTree::producer{|p|
38
+
39
+ # This rule matches leaf nodes (literals)
40
+ p.rule(:'_') do |engine, node|
41
+ "On: #{node.literal.inspect}"
42
+ end
43
+
44
+ # This rule matches everything else
45
+ p.rule("*") do |engine, node|
46
+ "Before: #{node.inspect}"
47
+ engine.apply(node.children)
48
+ "After: #{node.inspect}"
49
+ nil
50
+ end
51
+
52
+ }
53
+ }
54
+ let(:expr) { CodeTree::parse{ (concat "hello ", who) } }
55
+ subject { producer.apply(expr) }
56
+ it { should be_nil }
57
+ end
58
+
59
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe "README # assumptions section" do
4
+
5
+ [
6
+ [ proc{ x + 12 + 17 }, "(+ (+ x, 12), 17)" ],
7
+ [ proc{ x + (12 + 17) }, "(+ x, 29)" ],
8
+ [ proc{ (x + 12) + 17 }, "(+ (+ x, 12), 17)" ],
9
+ [ proc{ if x then 0 else 1 end }, "0"],
10
+ [ proc{ x and z }, "z"],
11
+ [ proc{ x && z }, "z"],
12
+ [ proc{ x or z }, "x"],
13
+ [ proc{ x || z }, "x"],
14
+ ].each{|test|
15
+ specify { CodeTree::parse(test[0]).to_s.should == test[1] }
16
+ }
17
+
18
+ if RUBY_VERSION < "1.9.0"
19
+ [ [ proc{ !(x) }, "false"],
20
+ [ proc{ not(x) }, "false"]
21
+ ].each{|test|
22
+ specify { CodeTree::parse(test[0]).to_s.should == test[1] }
23
+ }
24
+ else
25
+ [ [ proc{ !(x) }, "(! x)"],
26
+ [ proc{ not(x) }, "(! x)"]
27
+ ].each{|test|
28
+ specify { CodeTree::parse(test[0]).to_s.should == test[1] }
29
+ }
30
+ end
31
+
32
+
33
+ end