sbyc 0.1.0

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