sbyc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE.textile +12 -0
- data/README.textile +44 -0
- data/lib/sbyc.rb +14 -0
- data/lib/sbyc/codetree.rb +82 -0
- data/lib/sbyc/codetree/ast_node.rb +101 -0
- data/lib/sbyc/codetree/eval.rb +3 -0
- data/lib/sbyc/codetree/eval/ast_node_ext.rb +38 -0
- data/lib/sbyc/codetree/eval/functional_eval.rb +36 -0
- data/lib/sbyc/codetree/eval/object_eval.rb +36 -0
- data/lib/sbyc/codetree/matching.rb +3 -0
- data/lib/sbyc/codetree/matching/ast_node_ext.rb +14 -0
- data/lib/sbyc/codetree/matching/match_data.rb +30 -0
- data/lib/sbyc/codetree/matching/matcher.rb +83 -0
- data/lib/sbyc/codetree/proc_parser.rb +91 -0
- data/lib/sbyc/codetree/producing.rb +1 -0
- data/lib/sbyc/codetree/producing/producer.rb +68 -0
- data/lib/sbyc/codetree/rewriting.rb +4 -0
- data/lib/sbyc/codetree/rewriting/class_methods.rb +15 -0
- data/lib/sbyc/codetree/rewriting/compiler.rb +28 -0
- data/lib/sbyc/codetree/rewriting/instance_methods.rb +92 -0
- data/lib/sbyc/codetree/rewriting/match.rb +59 -0
- data/test/spec/documentation/codetree/production.spec +59 -0
- data/test/spec/documentation/readme/assumptions.spec +33 -0
- data/test/spec/documentation/readme/functional_evaluation.spec +29 -0
- data/test/spec/documentation/readme/object_evaluation.spec +17 -0
- data/test/spec/documentation/readme/rewriting.spec +60 -0
- data/test/spec/documentation/readme/semantics.spec +21 -0
- data/test/spec/documentation/readme/synopsis.spec +27 -0
- data/test/spec/documentation/readme/syntax.spec +26 -0
- data/test/spec/spec_helper.rb +13 -0
- data/test/spec/test_all.rb +6 -0
- data/test/spec/unit/sbyc/codetree/ast_node/coerce.spec +60 -0
- data/test/spec/unit/sbyc/codetree/ast_node/equality.spec +75 -0
- data/test/spec/unit/sbyc/codetree/ast_node/inspect.spec +23 -0
- data/test/spec/unit/sbyc/codetree/ast_node/literal.spec +15 -0
- data/test/spec/unit/sbyc/codetree/ast_node/to_a.spec +27 -0
- data/test/spec/unit/sbyc/codetree/ast_node/to_s.spec +23 -0
- data/test/spec/unit/sbyc/codetree/ast_node/visit.spec +34 -0
- data/test/spec/unit/sbyc/codetree/eval/functional_compile.spec +30 -0
- data/test/spec/unit/sbyc/codetree/eval/functional_eval.spec +34 -0
- data/test/spec/unit/sbyc/codetree/eval/functional_proc.spec +36 -0
- data/test/spec/unit/sbyc/codetree/eval/object_compile.spec +36 -0
- data/test/spec/unit/sbyc/codetree/eval/object_eval.spec +38 -0
- data/test/spec/unit/sbyc/codetree/eval/object_proc.spec +36 -0
- data/test/spec/unit/sbyc/codetree/matching/matcher/args_match.spec +91 -0
- data/test/spec/unit/sbyc/codetree/matching/matcher/do_match.spec +39 -0
- data/test/spec/unit/sbyc/codetree/matching/matcher/function_match.spec +45 -0
- data/test/spec/unit/sbyc/codetree/matching/matcher/match.spec +64 -0
- data/test/spec/unit/sbyc/codetree/proc_parser/expr.spec +20 -0
- data/test/spec/unit/sbyc/codetree/proc_parser/parse.spec +83 -0
- data/test/spec/unit/sbyc/codetree/producing/producer.spec +31 -0
- data/test/spec/unit/sbyc/codetree/producing/producer/apply_args_conventions.spec +60 -0
- data/test/spec/unit/sbyc/codetree/rewriting/instance_methods/apply_args_conventions.spec +60 -0
- data/test/spec/unit/sbyc/codetree/rewriting/instance_methods/node.spec +19 -0
- data/test/spec/unit/sbyc/codetree/rewriting/instance_methods/rewrite.spec +76 -0
- data/test/spec/unit/sbyc/codetree/rewriting/match/apply.spec +23 -0
- data/test/spec/unit/sbyc/codetree/rewriting/match/coerce.spec +48 -0
- data/test/spec/unit/sbyc/codetree/rewriting/match/matches.spec +27 -0
- 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,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
|