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.
- 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
|