arst 0.0.1 → 0.0.3
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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +9 -2
- data/README.md +230 -21
- data/VERSION +1 -1
- data/arst.gemspec +2 -3
- data/examples/Rakefile +5 -0
- data/examples/simple.arst +16 -0
- data/lib/arst.rb +8 -0
- data/lib/arst/generator.rb +14 -0
- data/lib/arst/generator/arst.rb +37 -0
- data/lib/arst/generator/base.rb +29 -0
- data/lib/arst/generator/c.rb +11 -0
- data/lib/arst/generator/ruby.rb +164 -0
- data/lib/arst/helpers.rb +13 -0
- data/lib/arst/node.rb +25 -0
- data/lib/arst/node/base.rb +39 -0
- data/lib/arst/node/class.rb +32 -0
- data/lib/arst/node/extend.rb +14 -0
- data/lib/arst/node/include.rb +14 -0
- data/lib/arst/node/module.rb +14 -0
- data/lib/arst/node/namable.rb +26 -0
- data/lib/arst/node/root.rb +11 -0
- data/lib/arst/parser.rb +64 -0
- data/lib/arst/rake_task.rb +62 -0
- metadata +40 -49
- data/examples/indentation_sensitive.rb +0 -84
- data/examples/simple.rb +0 -31
@@ -0,0 +1,29 @@
|
|
1
|
+
module ARST
|
2
|
+
module Generator
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
def self.generate(node, options={})
|
7
|
+
new(node).generate(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :node
|
11
|
+
|
12
|
+
def initialize(node)
|
13
|
+
@node = node
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate(options={})
|
17
|
+
parse_children(@node, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def parse_children(node, options={})
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'arst/generator/base'
|
2
|
+
require 'arst/helpers'
|
3
|
+
|
4
|
+
module ARST
|
5
|
+
module Generator
|
6
|
+
|
7
|
+
# TODO: This class is so dirty >=) CLEANN EEETTT
|
8
|
+
class Ruby < Base
|
9
|
+
|
10
|
+
def initialize(node)
|
11
|
+
super(node)
|
12
|
+
|
13
|
+
@current_output = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# =-=-= Hooks =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
19
|
+
|
20
|
+
def default_options
|
21
|
+
{
|
22
|
+
depth: 0,
|
23
|
+
split_files: true,
|
24
|
+
indent_size: 2,
|
25
|
+
indent_char: ' ',
|
26
|
+
newline_size: 1,
|
27
|
+
newline_character: "\n"
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def indent(options)
|
32
|
+
(options[:indent_char] * options[:indent_size]) * options[:depth]
|
33
|
+
end
|
34
|
+
|
35
|
+
def newline(options)
|
36
|
+
newline_char = options[:newline_size] == 0 ? '; ' : options[:newline_character]
|
37
|
+
|
38
|
+
options[:newline_character] * options[:newline_size]
|
39
|
+
end
|
40
|
+
|
41
|
+
# =-=-= Parsers =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
42
|
+
|
43
|
+
def parse_children(node, options={})
|
44
|
+
options = default_options.merge( options )
|
45
|
+
|
46
|
+
if options[:split_files]
|
47
|
+
@current_output = []
|
48
|
+
parse_children_as_multiple_files(node, options)
|
49
|
+
else
|
50
|
+
@current_output = [ { filename: filename_for_single_file(node), body: '' } ]
|
51
|
+
|
52
|
+
parse_children_as_single_file(node, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
result = @current_output
|
56
|
+
@current_output = nil
|
57
|
+
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_children_as_single_file(node, options={})
|
62
|
+
node.children.each do |node|
|
63
|
+
@current_output[0][:body] << indent(options) # TODO: DRY this up as a helper method
|
64
|
+
@current_output[0][:body] << code_from_node(node) # TODO: DRY this up as a helper method
|
65
|
+
@current_output[0][:body] << newline(options) # TODO: DRY this up as a helper method
|
66
|
+
parse_children_as_single_file( node, options.merge(depth: options[:depth]+1) ) unless node.children.empty?
|
67
|
+
@current_output[0][:body] << "#{ indent(options) }end#{ newline(options) }" if [:module, :class].include?(node.type)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_children_as_multiple_files(node, options={})
|
72
|
+
options[:ancestors] = node.ancestors # The main (first) node's ancestor list
|
73
|
+
|
74
|
+
unless [:root, :include, :extend].include?(node.type) # TODO: Hold container (class, module) and non-container (include, extend) in helper method or constant
|
75
|
+
body_code = parse_node_for_multiple_files(node, options)
|
76
|
+
body_requirements = require_statements_from_node(node)
|
77
|
+
body = "#{ body_requirements }#{ body_code }"
|
78
|
+
|
79
|
+
@current_output << { filename: filename_for_split_files(node), body: body }
|
80
|
+
end
|
81
|
+
node.children.each { |child| parse_children_as_multiple_files(child, options) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_node_for_multiple_files(node, options={})
|
85
|
+
output = ''
|
86
|
+
options[:current_ancestor_index] ||= 0
|
87
|
+
ancestor = options[:ancestors][ options[:current_ancestor_index] ]
|
88
|
+
|
89
|
+
output << indent(options) # TODO: DRY this up as a helper method
|
90
|
+
output << code_from_node(ancestor) # TODO: DRY this up as a helper method
|
91
|
+
output << newline(options) # TODO: DRY this up as a helper method
|
92
|
+
|
93
|
+
if options[:current_ancestor_index] == options[:ancestors].count - 1 # The first node
|
94
|
+
child_options = options.merge(depth: options[:depth]+1)
|
95
|
+
|
96
|
+
ancestor.children.each do |child|
|
97
|
+
if [:include, :extend].include?(child.type) # TODO: Hold container (class, module) and non-container (include, extend) in helper method or constant
|
98
|
+
output << indent(child_options) # TODO: DRY this up as a helper method
|
99
|
+
output << code_from_node(child) # TODO: DRY this up as a helper method
|
100
|
+
output << newline(child_options) # TODO: DRY this up as a helper method
|
101
|
+
end
|
102
|
+
end
|
103
|
+
else
|
104
|
+
child_options = options.merge(depth: options[:depth]+1, current_ancestor_index: options[:current_ancestor_index]+1)
|
105
|
+
output << parse_node_for_multiple_files( ancestor, child_options )
|
106
|
+
end
|
107
|
+
|
108
|
+
output << "#{ indent(options) }end#{ newline(options) }" if [:module, :class].include?(ancestor.type)
|
109
|
+
|
110
|
+
output
|
111
|
+
end
|
112
|
+
|
113
|
+
# =-=-= Helpers =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
114
|
+
|
115
|
+
def filename_for_single_file(node)
|
116
|
+
node.children.collect(&:human_name).join('_and_') + '.rb'
|
117
|
+
end
|
118
|
+
|
119
|
+
def filename_for_split_files(node)
|
120
|
+
node.ancestors.collect(&:human_name).join('/') + '.rb'
|
121
|
+
end
|
122
|
+
|
123
|
+
def code_from_node(node)
|
124
|
+
case node.type
|
125
|
+
when :module
|
126
|
+
"module #{ node.name }"
|
127
|
+
when :class
|
128
|
+
code_class = "class #{ node.name }"
|
129
|
+
code_subclass = " < #{ node.superclass }" if node.superclass?
|
130
|
+
|
131
|
+
"#{code_class}#{code_subclass}"
|
132
|
+
when :extend
|
133
|
+
"extend #{ node.name }"
|
134
|
+
when :include
|
135
|
+
"include #{ node.name }"
|
136
|
+
else
|
137
|
+
""
|
138
|
+
# TODO: Raise ARST::Error::InvalidNodeType
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def require_statements_from_node(node)
|
143
|
+
# TODO: Determine scope of subclass or included/extended module
|
144
|
+
nodes_with_requirements = node.children.find_all do |child|
|
145
|
+
[:include, :extend].include?(child.type) || child.type == :class && child.superclass?
|
146
|
+
end
|
147
|
+
|
148
|
+
nodes_with_requirements.unshift(node) if node.type == :class && node.superclass?
|
149
|
+
|
150
|
+
nodes_with_requirements.collect! do |child|
|
151
|
+
name = child.type == :class ? child.superclass : child.name
|
152
|
+
ancestry = name.include?('::') ? name.split('::') : child.ancestors.collect(&:name)
|
153
|
+
path = ancestry.collect { |name| Helpers.underscore(name) }.join('/')
|
154
|
+
|
155
|
+
"require '#{path}'"
|
156
|
+
end
|
157
|
+
|
158
|
+
nodes_with_requirements.empty? ? nil : nodes_with_requirements.join("\n") << "\n\n"
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
data/lib/arst/helpers.rb
ADDED
data/lib/arst/node.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'arst/node/root'
|
2
|
+
require 'arst/node/module'
|
3
|
+
require 'arst/node/class'
|
4
|
+
require 'arst/node/include'
|
5
|
+
require 'arst/node/extend'
|
6
|
+
|
7
|
+
module ARST
|
8
|
+
|
9
|
+
module Node
|
10
|
+
|
11
|
+
# TODO: Rename to something more specific... from_raw_tree?
|
12
|
+
def self.from_options(options)
|
13
|
+
case options[:type]
|
14
|
+
when 'module' then Node::Module.new(options)
|
15
|
+
when 'class' then Node::Class.new(options)
|
16
|
+
when 'extend' then Node::Extend.new(options)
|
17
|
+
when 'include' then Node::Include.new(options)
|
18
|
+
else
|
19
|
+
# TODO: Raise ARST::Error::InvalidNodeType
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'arst/helpers'
|
2
|
+
|
3
|
+
module ARST
|
4
|
+
module Node
|
5
|
+
|
6
|
+
class Base
|
7
|
+
|
8
|
+
attr_reader :parent, :children
|
9
|
+
|
10
|
+
def initialize(options={})
|
11
|
+
# TODO: Validate keys
|
12
|
+
options = default_options.merge(options)
|
13
|
+
|
14
|
+
@parent, @children = options.values_at(:parent, :children)
|
15
|
+
@children.collect! { |child_options| Node.from_options( child_options.merge(parent: self) ) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def ancestors
|
19
|
+
return [] if parent.nil?
|
20
|
+
|
21
|
+
( parent.ancestors || [] ) + [self]
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
@type ||= ARST::Helpers.underscore(self.class.to_s.split(/::/).last).to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def default_options
|
31
|
+
{
|
32
|
+
children: []
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'arst/node/base'
|
2
|
+
require 'arst/node/namable'
|
3
|
+
|
4
|
+
module ARST
|
5
|
+
module Node
|
6
|
+
|
7
|
+
class Class < Base
|
8
|
+
|
9
|
+
include Namable
|
10
|
+
|
11
|
+
attr_reader :superclass
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
super(options)
|
15
|
+
|
16
|
+
# TODO: Validate keys
|
17
|
+
self.superclass = options[:superclass]
|
18
|
+
end
|
19
|
+
|
20
|
+
def superclass=(superclass)
|
21
|
+
# TODO: Sanitize `superclass` given
|
22
|
+
@superclass = superclass.to_s unless superclass.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def superclass?
|
26
|
+
!superclass.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ARST
|
2
|
+
module Node
|
3
|
+
|
4
|
+
module Namable
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
super(options)
|
10
|
+
|
11
|
+
# TODO: Validate keys
|
12
|
+
self.name = options[:name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def name=(name)
|
16
|
+
@name = name.to_s # TODO: Sanitize
|
17
|
+
end
|
18
|
+
|
19
|
+
def human_name
|
20
|
+
ARST::Helpers.underscore(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/arst/parser.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
require 'arst/node'
|
3
|
+
|
4
|
+
module ARST
|
5
|
+
|
6
|
+
# The parser for ARST.
|
7
|
+
class Parser < Parslet::Parser
|
8
|
+
|
9
|
+
def self.parse(input, options={})
|
10
|
+
new.parse(input, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(input, options={})
|
14
|
+
tree = super(input)
|
15
|
+
|
16
|
+
options[:raw] ? tree : Node::Root.new(tree)
|
17
|
+
end
|
18
|
+
|
19
|
+
def indent(depth)
|
20
|
+
str(' ' * depth)
|
21
|
+
end
|
22
|
+
|
23
|
+
rule(:space) { match('[\t\s]') }
|
24
|
+
rule(:spaces) { space.repeat(1) }
|
25
|
+
rule(:newline) { match('\n') }
|
26
|
+
|
27
|
+
rule :constant do
|
28
|
+
match('[A-Z]') >> ( match('[A-Za-z0-9]') | str('::') ).repeat(1) # TODO: Cannot end with '::'
|
29
|
+
end
|
30
|
+
|
31
|
+
rule :module_keyword do
|
32
|
+
str('module').as(:type) >> spaces >> constant.as(:name)
|
33
|
+
end
|
34
|
+
|
35
|
+
rule :class_keyword do
|
36
|
+
str('class').as(:type) >> spaces >> constant.as(:name) >>
|
37
|
+
( spaces >> str('<') >> spaces >> constant.as(:superclass) ).maybe
|
38
|
+
end
|
39
|
+
|
40
|
+
rule :include_keyword do
|
41
|
+
str('include').as(:type) >> space >> constant.as(:name)
|
42
|
+
end
|
43
|
+
|
44
|
+
rule :extend_keyword do
|
45
|
+
str('extend').as(:type) >> space >> constant.as(:name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def node(depth)
|
49
|
+
indent(depth) >>
|
50
|
+
(
|
51
|
+
( class_keyword | module_keyword ) >> newline.maybe >>
|
52
|
+
dynamic { |soutce, context| node(depth+1).repeat(0) }.as(:children) |
|
53
|
+
( include_keyword | extend_keyword ) >> newline.maybe |
|
54
|
+
newline
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
rule(:document) { node(0).repeat.as(:children) }
|
59
|
+
|
60
|
+
root :document
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|