sbyc 0.1.0 → 0.1.1

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