arst 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|