sbyc 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/lib/sbyc.rb +1 -1
  2. data/lib/sbyc/codetree.rb +9 -6
  3. data/lib/sbyc/codetree/ast_node.rb +39 -4
  4. data/lib/sbyc/codetree/eval/functional_eval.rb +1 -1
  5. data/lib/sbyc/codetree/eval/object_eval.rb +1 -1
  6. data/lib/sbyc/codetree/name2x.rb +2 -0
  7. data/lib/sbyc/codetree/name2x/delegate.rb +57 -0
  8. data/lib/sbyc/codetree/name2x/module_delegate.rb +25 -0
  9. data/lib/sbyc/codetree/proc_parser.rb +46 -14
  10. data/lib/sbyc/codetree/producing.rb +2 -1
  11. data/lib/sbyc/codetree/producing/producer.rb +12 -0
  12. data/lib/sbyc/codetree/producing/tracing_methods.rb +77 -0
  13. data/test/spec/unit/sbyc/codetree/ast_node/code_inject.spec +57 -0
  14. data/test/spec/unit/sbyc/codetree/ast_node/digest.spec +83 -0
  15. data/test/spec/unit/sbyc/codetree/ast_node/rename.spec +36 -0
  16. data/test/spec/unit/sbyc/codetree/ast_node/to_s.spec +1 -1
  17. data/test/spec/unit/sbyc/codetree/ast_node/visit.spec +2 -2
  18. data/test/spec/unit/sbyc/codetree/name2x/delegate/coerce.spec +38 -0
  19. data/test/spec/unit/sbyc/codetree/name2x/delegate/fetch.spec +15 -0
  20. data/test/spec/unit/sbyc/codetree/name2x/delegate/name2class.spec +31 -0
  21. data/test/spec/unit/sbyc/codetree/name2x/delegate/name2module.spec +31 -0
  22. data/test/spec/unit/sbyc/codetree/name2x/delegate/name2name.spec +31 -0
  23. data/test/spec/unit/sbyc/codetree/name2x/module_delegate/name2class.spec +27 -0
  24. data/test/spec/unit/sbyc/codetree/name2x/module_delegate/name2module.spec +22 -0
  25. data/test/spec/unit/sbyc/codetree/name2x/module_delegate/name2name.spec +18 -0
  26. data/test/spec/unit/sbyc/codetree/parse.spec +32 -0
  27. data/test/spec/unit/sbyc/codetree/proc_parser/expr.spec +1 -1
  28. data/test/spec/unit/sbyc/codetree/proc_parser/parse.spec +34 -1
  29. data/test/spec/unit/sbyc/codetree/producing/producer/add_extension.spec +62 -0
  30. data/test/spec/unit/sbyc/codetree/producing/tracing_methods.spec +36 -0
  31. data/test/spec/unit/sbyc/codetree/producing/tracing_methods/default_options.spec +10 -0
  32. data/test/spec/unit/sbyc/codetree/producing/tracing_methods/prepare_options.spec +18 -0
  33. data/test/spec/unit/sbyc/codetree/producing/tracing_methods/trace.spec +59 -0
  34. data/test/spec/unit/sbyc/codetree/producing/tracing_methods/trace_rule_entered.spec +25 -0
  35. data/test/spec/unit/sbyc/codetree/producing/tracing_methods/trace_rule_exited.spec +25 -0
  36. metadata +27 -4
data/lib/sbyc.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module SByC
2
2
 
3
3
  # Version
4
- VERSION = "0.1.0".freeze
4
+ VERSION = "0.1.1".freeze
5
5
 
6
6
  # Provides tools around functional source code trees.
7
7
  module CodeTree
data/lib/sbyc/codetree.rb CHANGED
@@ -2,6 +2,7 @@ module CodeTree
2
2
 
3
3
  # Operator names
4
4
  OPERATOR_NAMES = {
5
+ :'?' => :varref,
5
6
  :[] => :get,
6
7
  :[]= => :set,
7
8
  :** => :exponentiation,
@@ -17,8 +18,8 @@ module CodeTree
17
18
  :% => :modulo,
18
19
  :=~ => :match,
19
20
  :=== => :matches?,
20
- :& => :bool_and,
21
- :| => :bool_or,
21
+ :& => :and,
22
+ :| => :or,
22
23
  :> => :gt,
23
24
  :>= => :gte,
24
25
  :<= => :lte,
@@ -31,14 +32,15 @@ module CodeTree
31
32
  REVERSE_OPERATOR_NAMES = Hash[*OPERATOR_NAMES.to_a.collect{|c| c.reverse}.flatten]
32
33
 
33
34
  # Parses some code or block
34
- def parse(code = nil, &block)
35
- ProcParser::parse(code, &block)
35
+ def parse(code = nil, options = nil, &block)
36
+ code, options = nil, code if code.kind_of?(Hash) and options.nil?
37
+ ProcParser::parse(code, options || {}, &block)
36
38
  end
37
39
  module_function :parse
38
40
 
39
41
  # Alias for _parse_
40
- def expr(code = nil, &block)
41
- ProcParser::parse(code, &block)
42
+ def expr(code = nil, options = nil, &block)
43
+ parse(code, options || {}, &block)
42
44
  end
43
45
  module_function :expr
44
46
 
@@ -80,3 +82,4 @@ require 'sbyc/codetree/eval'
80
82
  require 'sbyc/codetree/producing'
81
83
  require 'sbyc/codetree/matching'
82
84
  require 'sbyc/codetree/rewriting'
85
+ require 'sbyc/codetree/name2x'
@@ -1,9 +1,8 @@
1
1
  module CodeTree
2
2
  class AstNode
3
- include Enumerable
4
3
 
5
4
  # Name of the method call
6
- attr_reader :name
5
+ attr_accessor :name
7
6
  alias :function :name
8
7
 
9
8
  # Children nodes
@@ -45,9 +44,44 @@ module CodeTree
45
44
 
46
45
  # Makes a depth-first-search visit of the AST
47
46
  def visit(&block)
48
- yield(self, leaf? ? children : children.collect{|c| c.visit(&block)})
47
+ yield(self, leaf? ? children : children.collect{|c| c.respond_to?(:visit) ? c.visit(&block) : c})
48
+ end
49
+
50
+ # Renames some nodes, given a name2name map.
51
+ def rename!(map = nil, &block)
52
+ map = CodeTree::Name2X::Delegate.coerce(map || block)
53
+ visit{|node, collected|
54
+ newname = map.name2name(node.name)
55
+ node.send(:name=, newname) if newname
56
+ nil
57
+ }
58
+ self
59
+ end
60
+
61
+ # Inject code through module mapped to function names
62
+ def code_inject!(map = nil, &block)
63
+ map = CodeTree::Name2X::Delegate.coerce(map || block)
64
+ visit{|node, collected|
65
+ ext = map.name2module(node.function)
66
+ node.extend(ext) if ext
67
+ nil
68
+ }
69
+ self
70
+ end
71
+
72
+ # Create class instances
73
+ def digest(map = nil, &block)
74
+ map = CodeTree::Name2X::Delegate.coerce(map || block)
75
+ visit{|node, collected|
76
+ if node.leaf?
77
+ node.literal
78
+ else
79
+ ext = map.name2class(node.function)
80
+ raise "Unexpected node function: #{node.function}" unless ext
81
+ ext.new(*collected)
82
+ end
83
+ }
49
84
  end
50
- alias :produce :visit
51
85
 
52
86
  # Inspection
53
87
  def inspect
@@ -97,5 +131,6 @@ module CodeTree
97
131
  end
98
132
  end
99
133
 
134
+ private :name=
100
135
  end # module AstNode
101
136
  end # module CodeTree
@@ -3,7 +3,7 @@ module CodeTree
3
3
 
4
4
  # Generates code for a functional evaluation
5
5
  def functional_compile(ast, receiver_object = "receiver", scope_object = "scope", scope_method = :[])
6
- ast.produce{|node, collected|
6
+ ast.visit{|node, collected|
7
7
  case func = node.function
8
8
  when :_
9
9
  collected.first.inspect
@@ -3,7 +3,7 @@ module CodeTree
3
3
 
4
4
  # Generates code for an object evaluation
5
5
  def object_compile(ast, scope_object = "scope", scope_method = :[])
6
- ast.produce{|node, collected|
6
+ ast.visit{|node, collected|
7
7
  case func = node.function
8
8
  when :'_'
9
9
  collected.first.inspect
@@ -0,0 +1,2 @@
1
+ require "sbyc/codetree/name2x/delegate"
2
+ require "sbyc/codetree/name2x/module_delegate"
@@ -0,0 +1,57 @@
1
+ module CodeTree
2
+ module Name2X
3
+ class Delegate
4
+
5
+ # Hash delegate
6
+ attr_reader :delegate
7
+
8
+ # Creates FromHash instance
9
+ def initialize(delegate)
10
+ @delegate = delegate
11
+ end
12
+
13
+ # Converts an argument to a sub-class of this
14
+ # delegate implementation
15
+ def self.coerce(arg)
16
+ case arg
17
+ when Delegate
18
+ arg
19
+ when Hash, Proc
20
+ Delegate.new(arg)
21
+ when Module
22
+ ModuleDelegate.new(arg)
23
+ else
24
+ if arg.respond_to?(:[])
25
+ Delegate.new(arg)
26
+ else
27
+ raise ArgumentError, "Unable to convert #{arg} to a Name2X::Delegate", caller
28
+ end
29
+ end
30
+ end
31
+
32
+ # Make a fetch on from_hash
33
+ def fetch(name)
34
+ delegate[name]
35
+ end
36
+
37
+ # Convert a name to a module
38
+ def name2module(name)
39
+ result = fetch(name)
40
+ result.kind_of?(Module) ? result : nil
41
+ end
42
+
43
+ # Convert a name to a class
44
+ def name2class(name)
45
+ result = fetch(name)
46
+ result.kind_of?(Class) ? result : nil
47
+ end
48
+
49
+ # Convert a name to another name
50
+ def name2name(name)
51
+ result = fetch(name)
52
+ result.kind_of?(Symbol) ? result : nil
53
+ end
54
+
55
+ end # class FromHash
56
+ end # module Name2X
57
+ end # module CodeTree
@@ -0,0 +1,25 @@
1
+ module CodeTree
2
+ module Name2X
3
+ class ModuleDelegate < Delegate
4
+
5
+ # Overrides the default behavior to use const_get instead.
6
+ def fetch(name)
7
+ name = name2name(name)
8
+ delegate.const_defined?(name) ? delegate.const_get(name) : nil
9
+ rescue NameError
10
+ nil
11
+ end
12
+
13
+ # Capitalize a name
14
+ def name2name(name)
15
+ src = name.to_s
16
+ src.gsub!(/[^a-zA-Z\s]/," ")
17
+ src = " " + src.split.join(" ")
18
+ src.gsub!(/ (.)/) { $1.upcase }
19
+ src.to_sym
20
+ end
21
+
22
+ end # class ModuleDelegate
23
+ end # module Name2X
24
+ end # module CodeTree
25
+
@@ -1,11 +1,38 @@
1
1
  module CodeTree
2
2
  class ProcParser
3
+ #
4
+ # Collector when :multiline option is set to true.
5
+ #
6
+ class Collector
7
+
8
+ # Which nodes are kept?
9
+ attr_reader :kept
10
+
11
+ # Creates a collector instance
12
+ def initialize
13
+ @kept = []
14
+ end
15
+
16
+ # Callback when a new Expr is created
17
+ def built(who, children)
18
+ @kept << who
19
+ children.each{|c| @kept.delete_if{|k| k.__id__ == c.__id__}}
20
+ end
21
+
22
+ # Returns expressions as an array of AstNodes
23
+ def to_a
24
+ kept.collect{|c| c.__to_functional_code}
25
+ end
26
+
27
+ end # class Collector
28
+
29
+ # An expression
3
30
  class Expr
4
31
 
5
32
  # Methods that we keep
6
33
  KEPT_METHODS = [ "__send__", "__id__", "instance_eval", "initialize", "object_id",
7
34
  "singleton_method_added", "singleton_method_undefined", "method_missing",
8
- "__evaluate__", "coerce"]
35
+ "__evaluate__", "coerce", "kind_of?"]
9
36
 
10
37
  class << self
11
38
  def __clean_scope__
@@ -18,14 +45,17 @@ module CodeTree
18
45
  end
19
46
 
20
47
  # Creates an expression instance
21
- def initialize(name = nil, children = nil)
48
+ def initialize(name, children, collector)
22
49
  class << self; __clean_scope__; end
23
- @name, @children = name, children
50
+ @name, @children, @collector = name, children, collector
51
+ if @collector and @name
52
+ @collector.built(self, children)
53
+ end
24
54
  end
25
55
 
26
56
  # Returns the associated functional code
27
57
  def __to_functional_code
28
- children = @children.collect{|c|
58
+ children = (@children || []).collect{|c|
29
59
  c.kind_of?(Expr) ? c.__to_functional_code : AstNode.coerce(c)
30
60
  }
31
61
  CodeTree::AstNode.coerce([@name, children])
@@ -36,15 +66,15 @@ module CodeTree
36
66
  def method_missing(name, *args)
37
67
  if @name.nil?
38
68
  if name == :[]
39
- Expr.new(:'?', args)
69
+ Expr.new(:'?', args, @collector)
40
70
  elsif args.empty?
41
- Expr.new(:'?', [name])
71
+ Expr.new(:'?', [name], @collector)
42
72
  else
43
- Expr.new(name, args)
73
+ Expr.new(name, args, @collector)
44
74
  end
45
75
  else
46
76
  args.unshift(self)
47
- Expr.new(name, args)
77
+ Expr.new(name, args, @collector)
48
78
  end
49
79
  end
50
80
 
@@ -55,15 +85,17 @@ module CodeTree
55
85
  end # class Expr
56
86
 
57
87
  # Parses a Proc object
58
- def self.parse_proc(block)
88
+ def self.parse_proc(block, options = {})
89
+ collector = options[:multiline] ? Collector.new : nil
59
90
  e = case block.arity
60
91
  when -1, 0
61
- Expr.new.instance_eval(&block)
92
+ Expr.new(nil, nil, collector).instance_eval(&block)
62
93
  when 1
63
- block.call(Expr.new)
94
+ block.call(Expr.new(nil, nil, collector))
64
95
  else
65
96
  raise ArgumentError, "Unexpected block arity #{block.arity}"
66
97
  end
98
+ return collector.to_a if collector
67
99
  case e
68
100
  when Expr
69
101
  e.__to_functional_code
@@ -73,15 +105,15 @@ module CodeTree
73
105
  end
74
106
 
75
107
  # Parses a block
76
- def self.parse(code = nil, &block)
108
+ def self.parse(code = nil, options = {}, &block)
77
109
  block = code || block
78
110
  case block
79
111
  when Proc
80
- parse_proc(block)
112
+ parse_proc(block, options)
81
113
  when AstNode
82
114
  block
83
115
  when String
84
- parse(Kernel.eval("proc{ #{block} }"))
116
+ parse(Kernel.eval("proc{ #{block} }"), options)
85
117
  else
86
118
  raise ArgumentError, "Unable to parse #{block}"
87
119
  end
@@ -1 +1,2 @@
1
- require 'sbyc/codetree/producing/producer'
1
+ require 'sbyc/codetree/producing/producer'
2
+ require 'sbyc/codetree/producing/tracing_methods'
@@ -5,9 +5,13 @@ module CodeTree
5
5
  # Rules kepts by node function
6
6
  attr_reader :rules
7
7
 
8
+ # Extension options.
9
+ attr_reader :extension_options
10
+
8
11
  # Creates a producer instance
9
12
  def initialize(default_rules = true)
10
13
  @rules = {}
14
+ @extension_options = {}
11
15
  if default_rules
12
16
  rule(:_) {|r,node| node.literal}
13
17
  rule("*"){|r,node| r.apply(node.children)}
@@ -15,6 +19,14 @@ module CodeTree
15
19
  yield(self) if block_given?
16
20
  end
17
21
 
22
+ # Adds an extension module
23
+ def add_extension(mod, options = {})
24
+ self.extend(mod)
25
+ options = mod.send(:prepare_options, options || {}) if mod.respond_to?(:prepare_options)
26
+ extension_options[mod] = options
27
+ self
28
+ end
29
+
18
30
  # Adds a rule
19
31
  def rule(function_name, &block)
20
32
  rules[function_name] = block
@@ -0,0 +1,77 @@
1
+ module CodeTree
2
+ module Producing
3
+ module TracingMethods
4
+
5
+ # Returns the default options
6
+ def self.default_options
7
+ {:indent_support => true,
8
+ :indent_string => " ",
9
+ :newline_support => true,
10
+ :io => STDOUT}
11
+ end
12
+
13
+ # Merge the default options and the options
14
+ def self.prepare_options(options)
15
+ default_options.merge(options)
16
+ end
17
+
18
+ # Returns tracing extension options
19
+ def tracing_options
20
+ extension_options[TracingMethods] ||= {}
21
+ end
22
+
23
+ # Is indentation support installed?
24
+ def tracing_indent_support?
25
+ !!tracing_options[:indent_support]
26
+ end
27
+
28
+ # Is new line support installed?
29
+ def tracing_newline_support?
30
+ !!tracing_options[:newline_support]
31
+ end
32
+
33
+ # Returns the string to use for indentation
34
+ def tracing_indent_string
35
+ tracing_options[:indent_string] ||= " "
36
+ end
37
+
38
+ # Returns the current indentation level
39
+ def tracing_indent_level
40
+ tracing_options[:indent_level] ||= 0
41
+ end
42
+
43
+ # Returns the IO object (actually, any object supporting the << operator)
44
+ # that must be used for tracing the Producer.
45
+ def tracing_io
46
+ tracing_options[:io] ||= STDOUT
47
+ end
48
+
49
+ # Logs a message on the IO object, without any other side effect (no
50
+ # depth increment, for instance).
51
+ def trace(message)
52
+ message = (tracing_indent_string*tracing_indent_level + message.to_s) if tracing_indent_support? and tracing_indent_level>0
53
+ message = "#{message}\n" if tracing_newline_support?
54
+ if tracing_io.kind_of?(IO)
55
+ tracing_io << message
56
+ else
57
+ tracing_io << message
58
+ end
59
+ end
60
+
61
+ # Logs that a rule has been entered and increment the depth
62
+ # level
63
+ def trace_rule_entered(message)
64
+ trace(message)
65
+ tracing_options[:indent_level] = tracing_indent_level+1
66
+ end
67
+
68
+ # Logs that a rule has been exited and decrements the depth
69
+ # level
70
+ def trace_rule_exited(message)
71
+ tracing_options[:indent_level] = tracing_indent_level-1
72
+ trace(message)
73
+ end
74
+
75
+ end # module TracingMethods
76
+ end # module Producing
77
+ end # module CodeTree