trope 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +334 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/examples/1.output.rb +56 -0
- data/examples/1.rb +18 -0
- data/lib/trope.rb +20 -0
- data/lib/trope/base.treetop +49 -0
- data/lib/trope/error.rb +8 -0
- data/lib/trope/language.rb +14 -0
- data/lib/trope/language/attribute_keyword.rb +17 -0
- data/lib/trope/language/body.rb +12 -0
- data/lib/trope/language/constant.rb +12 -0
- data/lib/trope/language/end_keyword.rb +12 -0
- data/lib/trope/language/float_literal.rb +17 -0
- data/lib/trope/language/integer_literal.rb +17 -0
- data/lib/trope/language/literal.rb +17 -0
- data/lib/trope/language/object_keyword.rb +12 -0
- data/lib/trope/language/string_literal.rb +12 -0
- data/lib/trope/language_parser.treetop +57 -0
- data/lib/trope/node.rb +17 -0
- data/lib/trope/parser.rb +37 -0
- data/lib/trope/parser/error.rb +31 -0
- data/trope.gemspec +53 -0
- metadata +333 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Ryan Scott Lewis <ryan@rynet.us>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
# Trope
|
2
|
+
|
3
|
+
**[Documentation][docs] - [Gem][gems] - [Source][source]**
|
4
|
+
|
5
|
+
Prototyping language that transcompiles into pure Ruby code.
|
6
|
+
|
7
|
+
1. Build your concept in Trope.
|
8
|
+
2. Write specs.
|
9
|
+
3. Transcompile into Ruby.
|
10
|
+
4. Destroy Trope files.
|
11
|
+
5. Red, green, refactor.
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
> NOTE: Trope is not released yet, the gem is just a placeholder.
|
16
|
+
|
17
|
+
### Bundler: `gem 'trope'`
|
18
|
+
|
19
|
+
### RubyGems: `gem install trope`
|
20
|
+
|
21
|
+
## Example
|
22
|
+
|
23
|
+
Create `library.trope`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
object Book
|
27
|
+
attr name <String> -!wd 'Unnamed book'
|
28
|
+
attr isbn <Integer> -w
|
29
|
+
attr library <Library> -w do
|
30
|
+
before write { @library.books.delete(self) unless @library.nil? }
|
31
|
+
after write { @library.books.push(self) unless @library.books.include?(self) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
object Library
|
36
|
+
attr books <Array> -d Array.new
|
37
|
+
meth add_book do |attributes_or_book <Hash, Book>|
|
38
|
+
book = attributes_or_book.is_a?(Book) ? attributes_or_book : Book.new(attributes_or_book)
|
39
|
+
book.library = self
|
40
|
+
|
41
|
+
@books << book
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Now generate the Ruby code:
|
47
|
+
|
48
|
+
```sh
|
49
|
+
$ trope compile libary.trope
|
50
|
+
```
|
51
|
+
|
52
|
+
Those 15 lines will be transcompiled into the following pure Ruby code in `library.rb`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class Book
|
56
|
+
class Error < RuntimeError; end
|
57
|
+
|
58
|
+
class InvalidAttributesError < Error
|
59
|
+
def to_s
|
60
|
+
'attributes must be a Hash or respond to #to_h'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class MissingAttributeError < Error
|
65
|
+
def initialize(attr_name, attr_class)
|
66
|
+
@name, @class = attr_name.to_s, attr_class.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
"attribute '#@name' does not exist for #@class"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MissingNameError < Error
|
75
|
+
def to_s
|
76
|
+
'name cannot be nil'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class InvalidNameError < Error
|
81
|
+
def to_s
|
82
|
+
'name must be an instance of String or respond to :to_s'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class InvalidIsbnError < Error
|
87
|
+
def to_s
|
88
|
+
'isbn must be an instance of Integer or respond to :to_i'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class MissingLibraryError < Error
|
93
|
+
def to_s
|
94
|
+
'library cannot be nil'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class InvalidLibraryError < Error
|
99
|
+
def to_s
|
100
|
+
'library must be an instance of Library'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
attr_reader *(@@_attributes = [:name, :isbn, :library])
|
105
|
+
|
106
|
+
def initialize(attributes={})
|
107
|
+
raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)
|
108
|
+
attributes = attributes.to_h unless attributes.is_a?(Hash)
|
109
|
+
|
110
|
+
raise MissingNameError if attributes.has_key?(:name) && attributes[:name].nil?
|
111
|
+
attributes[:name] = 'Unnamed book' unless attributes.has_key?(:name)
|
112
|
+
|
113
|
+
attributes.each do |name, value|
|
114
|
+
raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)
|
115
|
+
|
116
|
+
setter_method = "#{name}="
|
117
|
+
setter_method = "_#{setter_method}" unless self.class.method_defined?(setter_method)
|
118
|
+
|
119
|
+
send(setter_method, value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def name=(value)
|
124
|
+
raise MissingNameError if value.nil?
|
125
|
+
raise InvalidNameError unless value.is_a?(String) || value.respond_to?(:to_s)
|
126
|
+
value = value.to_i unless value.is_a?(Integer)
|
127
|
+
|
128
|
+
@name = value
|
129
|
+
end
|
130
|
+
|
131
|
+
def isbn=(value)
|
132
|
+
raise InvalidIsbnError unless value.is_a?(Integer) || value.respond_to?(:to_i)
|
133
|
+
value = value.to_i unless value.is_a?(Integer)
|
134
|
+
|
135
|
+
@isbn = value
|
136
|
+
end
|
137
|
+
|
138
|
+
def library=(value)
|
139
|
+
raise InvalidLibraryError unless value.is_a?(Library) || value.nil?
|
140
|
+
|
141
|
+
@library.books.delete(self) unless @library.nil?
|
142
|
+
|
143
|
+
@library = value
|
144
|
+
|
145
|
+
@library.books.push(self) unless @library.books.include?(self)
|
146
|
+
|
147
|
+
@library
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Library
|
152
|
+
class Error < RuntimeError; end
|
153
|
+
|
154
|
+
class InvalidAttributesError < Error
|
155
|
+
def to_s
|
156
|
+
'attributes must be an instance of Hash or respond to #to_h'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class MissingAttributeError < Error
|
161
|
+
def initialize(attr_name, attr_class)
|
162
|
+
@name, @class = attr_name.to_s, attr_class.to_s
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_s
|
166
|
+
"attribute '#@name' does not exist for #@class"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class InvalidBooksError < Error
|
171
|
+
def to_s
|
172
|
+
'books must be an instance of Array or respond to #to_a'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
attr_reader *(@@_attributes = [:books])
|
177
|
+
|
178
|
+
def initialize(attributes={})
|
179
|
+
raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)
|
180
|
+
attributes = attributes.to_h unless attributes.is_a?(Hash)
|
181
|
+
|
182
|
+
attributes[:books] = Array.new unless attributes.has_key?(:books)
|
183
|
+
|
184
|
+
attributes.each do |name, value|
|
185
|
+
raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)
|
186
|
+
|
187
|
+
setter_method = "#{name}="
|
188
|
+
setter_method = "_#{setter_method}" unless self.class.method_defined?(setter_method)
|
189
|
+
|
190
|
+
send(setter_method, value)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def add_book(attributes_or_book={})
|
195
|
+
raise InvalidAttributesError unless attributes_or_book.is_a?(Hash) || attributes_or_book.respond_to?(:to_h) || attributes_or_book.is_a?(Book)
|
196
|
+
attributes_or_book = attributes_or_book.to_h unless attributes_or_book.is_a?(Hash) || attributes_or_book.is_a?(Book)
|
197
|
+
|
198
|
+
book = attributes_or_book.is_a?(Book) ? attributes_or_book : Book.new(attributes_or_book)
|
199
|
+
book.library = self
|
200
|
+
|
201
|
+
@books << book
|
202
|
+
end
|
203
|
+
|
204
|
+
protected
|
205
|
+
|
206
|
+
def _books=(value)
|
207
|
+
raise InvalidBooksError unless value.is_a?(Array) || value.respond_to?(:to_a)
|
208
|
+
value = value.to_a unless value.is_a?(Array)
|
209
|
+
|
210
|
+
@books = value
|
211
|
+
end
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
Using the transcompiled Ruby code will produce the expected results:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
p library = Library.new # => #<Library:0x007fc55c0ce418 @books=[]>
|
219
|
+
p library.add_book name: 'Book 1', isbn: 1 # => [#<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418 @books=[...]>>]
|
220
|
+
p library # => #<Library:0x007fc55c0ce418 @books=[#<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418 ...>>]>
|
221
|
+
p library.books.first # => #<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418 @books=[#<Book:0x007fc55c0cde78 ...>]>>
|
222
|
+
p library.books.first.isbn = nil # => nil
|
223
|
+
p library.books.first.name = nil # => Book::MissingNameError: name cannot be nil
|
224
|
+
p library.books.first.library = nil # => Book::MissingLibraryError: library cannot be nil
|
225
|
+
p library.books.first.isbn = ['array'] # => Book::InvalidIsbnError: isbn must be an instance of Integer or respond to :to_i
|
226
|
+
p library = Library.new(books: 123) # => Library::InvalidBooksError: books must be an instance of Array or respond to #to_a
|
227
|
+
```
|
228
|
+
|
229
|
+
### Breakdown
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
object Book
|
233
|
+
attr name <String> -!wd 'Unnamed book'
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
This says that I have an object `Book` that has an attribute `name` (`attr name`) that
|
238
|
+
must either be an instance/subclass of `String` or be able to convert to an instance of
|
239
|
+
`String` using `#to_s` (`<String>`). It is a required attribute that can never be set to nil (`!`), has a writer method (`w`),
|
240
|
+
and defaults to 'Unnamed book'.
|
241
|
+
|
242
|
+
The minus sign (`-`) indicates a 'switch' or 'option', must like most *nix command line
|
243
|
+
programs. The example could also have been written like so:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
object Book
|
247
|
+
attr name <String> -! -w -d 'Unnamed book'
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
The above examples will transcompile into the following:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
class Book
|
255
|
+
class Error < RuntimeError; end
|
256
|
+
|
257
|
+
class InvalidAttributesError < Error
|
258
|
+
def to_s
|
259
|
+
'attributes must be a Hash or respond to #to_h'
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class MissingAttributeError < Error
|
264
|
+
def initialize(attr_name, attr_class)
|
265
|
+
@name, @class = attr_name.to_s, attr_class.to_s
|
266
|
+
end
|
267
|
+
|
268
|
+
def to_s
|
269
|
+
"attribute '#@name' does not exist for #@class"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class MissingNameError < Error
|
274
|
+
def to_s
|
275
|
+
'name cannot be nil'
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class InvalidNameError < Error
|
280
|
+
def to_s
|
281
|
+
'name must be an instance of String or respond to :to_s'
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
attr_reader *(@@_attributes = [:name])
|
286
|
+
@@_required_attributes = [:name]
|
287
|
+
|
288
|
+
def initialize(attributes={})
|
289
|
+
raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)
|
290
|
+
attributes = attributes.to_h unless attributes.is_a?(Hash)
|
291
|
+
|
292
|
+
raise MissingNameError if attributes.has_key?(:name) && attributes[:name].nil?
|
293
|
+
|
294
|
+
attributes[:name] = 'Unnamed book' unless attributes.has_key?(:name)
|
295
|
+
|
296
|
+
attributes.each do |name, value|
|
297
|
+
raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)
|
298
|
+
|
299
|
+
setter_method = "#{name}="
|
300
|
+
setter_method = "_#{setter_method}" unless self.class.method_defined?(setter_method)
|
301
|
+
|
302
|
+
send(setter_method, value)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def name=(value)
|
307
|
+
raise MissingNameError if value.nil?
|
308
|
+
raise InvalidNameError unless value.is_a?(String) || value.respond_to?(:to_s)
|
309
|
+
value = value.to_i unless value.is_a?(Integer)
|
310
|
+
|
311
|
+
@name = value
|
312
|
+
end
|
313
|
+
end
|
314
|
+
```
|
315
|
+
|
316
|
+
## Contributing
|
317
|
+
|
318
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
319
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
320
|
+
* Fork the project
|
321
|
+
* Start a feature/bugfix branch
|
322
|
+
* Commit and push until you are happy with your contribution
|
323
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
324
|
+
* Please try not to mess with the Rakefile, VERSION, or Gemfile. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
325
|
+
|
326
|
+
## Copyright
|
327
|
+
|
328
|
+
Copyright © 2012 Ryan Scott Lewis <ryan@rynet.us>.
|
329
|
+
|
330
|
+
The MIT License (MIT) - See LICENSE for further details.
|
331
|
+
|
332
|
+
[docs]: http://rubydoc.info/gems/trope/frames
|
333
|
+
[gems]: https://rubygems.org/gems/trope
|
334
|
+
[source]: https://github.com/RyanScottLewis/trope
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
def require_task(path)
|
4
|
+
begin
|
5
|
+
require path
|
6
|
+
|
7
|
+
yield
|
8
|
+
rescue LoadError
|
9
|
+
puts '', "Could not load '#{path}'.", 'Try to `rake gem:spec` and `bundle install` and try again.', ''
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
spec = Gem::Specification.new do |s|
|
14
|
+
|
15
|
+
# Variables
|
16
|
+
s.name = 'trope'
|
17
|
+
s.summary = 'Prototyping language that transcompiles into pure Ruby code.'
|
18
|
+
|
19
|
+
# Dependencies
|
20
|
+
s.add_dependency 'version', '~> 1.0.0'
|
21
|
+
s.add_dependency 'treetop', '~> 1.4.12'
|
22
|
+
s.add_dependency 'polyglot', '~> 0.3.3'
|
23
|
+
s.add_development_dependency 'guard-rspec', '~> 2.1'
|
24
|
+
s.add_development_dependency 'guard-yard', '~> 2.0'
|
25
|
+
s.add_development_dependency 'rb-fsevent', '~> 0.9'
|
26
|
+
s.add_development_dependency 'fuubar', '~> 1.1'
|
27
|
+
s.add_development_dependency 'redcarpet', '~> 2.2.2'
|
28
|
+
s.add_development_dependency 'github-markup', '~> 0.7'
|
29
|
+
|
30
|
+
# Pragmatically set variables and constants
|
31
|
+
s.author = 'Ryan Scott Lewis'
|
32
|
+
s.email = 'ryan@rynet.us'
|
33
|
+
s.homepage = "http://github.com/RyanScottLewis/#{s.name}"
|
34
|
+
s.version = Pathname.glob('VERSION*').first.read
|
35
|
+
s.description = Pathname.glob('README*').first.read
|
36
|
+
s.require_paths = ['lib']
|
37
|
+
s.files = `git ls-files`.lines.to_a.collect { |s| s.strip }
|
38
|
+
s.executables = `git ls-files -- bin/*`.lines.to_a.collect { |s| File.basename(s.strip) }
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'Generate the gemspec defined in this Rakefile'
|
43
|
+
task :gemspec do
|
44
|
+
Pathname.new("#{spec.name}.gemspec").open('w') { |f| f.write(spec.to_ruby) }
|
45
|
+
end
|
46
|
+
|
47
|
+
require_task 'rake/version_task' do
|
48
|
+
Rake::VersionTask.new do |t|
|
49
|
+
t.with_git_tag = true
|
50
|
+
t.with_gemspec = spec
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require 'rubygems/package_task'
|
55
|
+
Gem::PackageTask.new(spec) do |t|
|
56
|
+
t.need_zip = false
|
57
|
+
t.need_tar = false
|
58
|
+
end
|
59
|
+
|
60
|
+
task default: :gemspec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class User
|
2
|
+
class Error < RuntimeError; end
|
3
|
+
class TypeError < Error; end
|
4
|
+
|
5
|
+
class InvalidAttributesError < Error
|
6
|
+
def to_s
|
7
|
+
'attributes must be a Hash or respond to #to_h or #to_hash'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class MissingNameError < Error
|
12
|
+
def to_s
|
13
|
+
'name cannot be nil'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvalidAgeError < Error
|
18
|
+
def to_s
|
19
|
+
'age must be an instance of Integer or respond to :to_i'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class InvalidIdError < Error
|
24
|
+
def to_s
|
25
|
+
'age must be an instance of Integer or respond to :to_i'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :name, :age, :id
|
30
|
+
|
31
|
+
def initialize(attributes={})
|
32
|
+
raise InvalidAttributesError unless attributes.is_a?(Hash) || [:to_h, :to_hash].any? { |method| attributes.respond_to?(method) }
|
33
|
+
attributes = attributes.to_hash rescue attributes.to_h unless attributes.is_a?(Hash)
|
34
|
+
|
35
|
+
attributes.each do |name, value|
|
36
|
+
setter_method = "#{name}="
|
37
|
+
setter_method = "_#{setter_method}" unless self.class.method_defined?(setter_method)
|
38
|
+
|
39
|
+
send(setter_method, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def name=(value)
|
45
|
+
raise MissingNameError if value.nil?
|
46
|
+
|
47
|
+
@name = value
|
48
|
+
end
|
49
|
+
|
50
|
+
def age=(value)
|
51
|
+
raise InvalidAgeError unless value.is_a?(Integer) || value.respond_to?(:to_i)
|
52
|
+
value = value.to_i unless value.is_a?(Integer)
|
53
|
+
|
54
|
+
@age = value
|
55
|
+
end
|
56
|
+
end
|
data/examples/1.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative '../lib/trope'
|
2
|
+
|
3
|
+
body = Trope.parse(DATA.read)
|
4
|
+
|
5
|
+
puts '----- body -----------------------------------------------------------------------', ''
|
6
|
+
p body
|
7
|
+
|
8
|
+
puts '', '----- attributes -----------------------------------------------------------------', ''
|
9
|
+
|
10
|
+
attribute = body.elements[1]
|
11
|
+
puts attribute.value
|
12
|
+
attribute = body.elements[2]
|
13
|
+
puts attribute.value
|
14
|
+
|
15
|
+
puts ''
|
16
|
+
|
17
|
+
|
18
|
+
__END__
|
data/lib/trope.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'version'
|
4
|
+
|
5
|
+
__LIB__ ||= Pathname.new(__FILE__).join('..').expand_path
|
6
|
+
$:.unshift(__LIB__.to_s) unless $:.include?(__LIB__)
|
7
|
+
|
8
|
+
require 'trope/parser'
|
9
|
+
|
10
|
+
# Prototyping language that transcompiles into pure Ruby code.
|
11
|
+
module Trope
|
12
|
+
is_versioned
|
13
|
+
|
14
|
+
class << self
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
delegate :parse => Parser
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Trope
|
2
|
+
grammar Base
|
3
|
+
|
4
|
+
rule single_quote
|
5
|
+
"'"
|
6
|
+
end
|
7
|
+
|
8
|
+
rule double_quote
|
9
|
+
'"'
|
10
|
+
end
|
11
|
+
|
12
|
+
rule quote
|
13
|
+
single_quote / double_quote
|
14
|
+
end
|
15
|
+
|
16
|
+
rule hash
|
17
|
+
'#'
|
18
|
+
end
|
19
|
+
|
20
|
+
rule reserved_characters
|
21
|
+
quote
|
22
|
+
end
|
23
|
+
|
24
|
+
rule digit
|
25
|
+
[0-9]
|
26
|
+
end
|
27
|
+
|
28
|
+
rule lowercase_letter
|
29
|
+
[a-z]
|
30
|
+
end
|
31
|
+
|
32
|
+
rule uppercase_letter
|
33
|
+
[A-Z]
|
34
|
+
end
|
35
|
+
|
36
|
+
rule letter
|
37
|
+
lowercase_letter / uppercase_letter
|
38
|
+
end
|
39
|
+
|
40
|
+
rule letter_or_digit
|
41
|
+
letter / digit
|
42
|
+
end
|
43
|
+
|
44
|
+
rule space
|
45
|
+
[\s]+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/trope/error.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'trope/node'
|
2
|
+
require 'trope/base'
|
3
|
+
|
4
|
+
module Trope
|
5
|
+
grammar Language
|
6
|
+
include Base
|
7
|
+
|
8
|
+
rule body
|
9
|
+
(
|
10
|
+
# identifier /
|
11
|
+
object_keyword / attribute_keyword / end_keyword /
|
12
|
+
float / integer / string / space
|
13
|
+
)* <Body>
|
14
|
+
end
|
15
|
+
|
16
|
+
rule integer
|
17
|
+
('+' / '-')? digit+ (![\.e]) <IntegerLiteral>
|
18
|
+
end
|
19
|
+
|
20
|
+
rule float
|
21
|
+
('+' / '-')? digit+ ([\.e] digit+) <FloatLiteral>
|
22
|
+
end
|
23
|
+
|
24
|
+
rule double_quoted_string
|
25
|
+
double_quote ('\\' double_quote / !double_quote .)* double_quote <StringLiteral>
|
26
|
+
end
|
27
|
+
|
28
|
+
rule single_quoted_string
|
29
|
+
single_quote ('\\' single_quote / !single_quote .)* single_quote <StringLiteral>
|
30
|
+
end
|
31
|
+
|
32
|
+
rule string
|
33
|
+
single_quoted_string / double_quoted_string
|
34
|
+
end
|
35
|
+
|
36
|
+
# rule identifier
|
37
|
+
# [a-zA-Z\=\*] [a-zA-Z0-9_\=\*]* <Identifier>
|
38
|
+
# end
|
39
|
+
|
40
|
+
rule constant
|
41
|
+
uppercase_letter letter_or_digit* <Constant>
|
42
|
+
end
|
43
|
+
|
44
|
+
rule object_keyword
|
45
|
+
'object' space constant <ObjectKeyword>
|
46
|
+
end
|
47
|
+
|
48
|
+
rule attribute_keyword
|
49
|
+
'attr' 'ibute'? space string <AttributeKeyword>
|
50
|
+
end
|
51
|
+
|
52
|
+
rule end_keyword
|
53
|
+
'end' !(!' ' .) <EndKeyword>
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
data/lib/trope/node.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'treetop/runtime/syntax_node'
|
2
|
+
|
3
|
+
module Trope
|
4
|
+
|
5
|
+
# The base syntax node.
|
6
|
+
class Node < Treetop::Runtime::SyntaxNode
|
7
|
+
|
8
|
+
alias_method :elements_with_treetop_nodes, :elements
|
9
|
+
|
10
|
+
# @return [<Trope::Node>]
|
11
|
+
def elements
|
12
|
+
elements_with_treetop_nodes.find_all { |node| node.is_a?(Trope::Node) }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/trope/parser.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
require 'trope/parser/error'
|
4
|
+
require 'trope/language'
|
5
|
+
require 'trope/language_parser'
|
6
|
+
|
7
|
+
module Trope
|
8
|
+
|
9
|
+
# A controller for the Trope::LanguageParser
|
10
|
+
module Parser
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Set the `tree` attribute.
|
14
|
+
# This contains the last parsed result.
|
15
|
+
attr_reader :tree
|
16
|
+
|
17
|
+
# Get a new or the cached instance of this class.
|
18
|
+
#
|
19
|
+
# @return [Trope::LanguageParser]
|
20
|
+
def instance
|
21
|
+
@instance ||= LanguageParser.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set the `tree` attribute.
|
25
|
+
#
|
26
|
+
# @return [Trope::Language::Body]
|
27
|
+
def parse(data)
|
28
|
+
@tree = instance.parse(data)
|
29
|
+
raise Error, instance unless @tree
|
30
|
+
|
31
|
+
@tree
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'trope/error'
|
2
|
+
require 'trope/language_parser'
|
3
|
+
|
4
|
+
module Trope
|
5
|
+
module Parser
|
6
|
+
|
7
|
+
# The base class for Trope::Parser errors.
|
8
|
+
class Error < Trope::Error
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegator :@language_parser, :failure_reason, :reason
|
13
|
+
def_delegator :@language_parser, :failure_line, :line
|
14
|
+
def_delegator :@language_parser, :failure_column, :column
|
15
|
+
|
16
|
+
# @param [Trope::LanguageParser] language_parser
|
17
|
+
def initialize(language_parser)
|
18
|
+
raise TypeError unless language_parser.instance_of?(Trope::LanguageParser)
|
19
|
+
|
20
|
+
@language_parser = language_parser
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def to_s
|
25
|
+
reason || "Invalid - column #{column}, line #{line}"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/trope.gemspec
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "trope"
|
5
|
+
s.version = "0.0.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Ryan Scott Lewis"]
|
9
|
+
s.date = "2012-12-01"
|
10
|
+
s.description = "# Trope\n\n**[Documentation][docs] - [Gem][gems] - [Source][source]**\n\nPrototyping language that transcompiles into pure Ruby code.\n\n1. Build your concept in Trope.\n2. Write specs.\n3. Transcompile into Ruby.\n4. Destroy Trope files.\n5. Red, green, refactor.\n\n## Install\n\n> NOTE: Trope is not released yet, the gem is just a placeholder.\n\n### Bundler: `gem 'trope'`\n\n### RubyGems: `gem install trope`\n\n## Example\n\nCreate `library.trope`:\n\n```ruby\nobject Book\n attr name <String> -!wd 'Unnamed book'\n attr isbn <Integer> -w\n attr library <Library> -w do\n before write { @library.books.delete(self) unless @library.nil? }\n after write { @library.books.push(self) unless @library.books.include?(self) }\n end\nend\n\nobject Library\n attr books <Array> -d Array.new\n meth add_book do |attributes_or_book <Hash, Book>|\n book = attributes_or_book.is_a?(Book) ? attributes_or_book : Book.new(attributes_or_book)\n book.library = self\n \n @books << book\n end\nend\n```\n\nNow generate the Ruby code:\n\n```sh\n$ trope compile libary.trope\n```\n\nThose 15 lines will be transcompiled into the following pure Ruby code in `library.rb`:\n\n```ruby\nclass Book\n class Error < RuntimeError; end\n \n class InvalidAttributesError < Error\n def to_s\n 'attributes must be a Hash or respond to #to_h'\n end\n end\n \n class MissingAttributeError < Error\n def initialize(attr_name, attr_class)\n @name, @class = attr_name.to_s, attr_class.to_s\n end\n \n def to_s\n \"attribute '\#@name' does not exist for \#@class\"\n end\n end\n \n class MissingNameError < Error\n def to_s\n 'name cannot be nil'\n end\n end\n \n class InvalidNameError < Error\n def to_s\n 'name must be an instance of String or respond to :to_s'\n end\n end\n \n class InvalidIsbnError < Error\n def to_s\n 'isbn must be an instance of Integer or respond to :to_i'\n end\n end\n \n class MissingLibraryError < Error\n def to_s\n 'library cannot be nil'\n end\n end\n \n class InvalidLibraryError < Error\n def to_s\n 'library must be an instance of Library'\n end\n end\n \n attr_reader *(@@_attributes = [:name, :isbn, :library])\n \n def initialize(attributes={})\n raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)\n attributes = attributes.to_h unless attributes.is_a?(Hash)\n \n raise MissingNameError if attributes.has_key?(:name) && attributes[:name].nil?\n attributes[:name] = 'Unnamed book' unless attributes.has_key?(:name)\n \n attributes.each do |name, value|\n raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)\n \n setter_method = \"\#{name}=\"\n setter_method = \"_\#{setter_method}\" unless self.class.method_defined?(setter_method)\n \n send(setter_method, value)\n end\n end\n \n def name=(value)\n raise MissingNameError if value.nil?\n raise InvalidNameError unless value.is_a?(String) || value.respond_to?(:to_s)\n value = value.to_i unless value.is_a?(Integer)\n \n @name = value\n end\n \n def isbn=(value)\n raise InvalidIsbnError unless value.is_a?(Integer) || value.respond_to?(:to_i)\n value = value.to_i unless value.is_a?(Integer)\n \n @isbn = value\n end\n \n def library=(value)\n raise InvalidLibraryError unless value.is_a?(Library) || value.nil?\n \n @library.books.delete(self) unless @library.nil?\n \n @library = value\n \n @library.books.push(self) unless @library.books.include?(self)\n \n @library\n end\nend\n\nclass Library\n class Error < RuntimeError; end\n \n class InvalidAttributesError < Error\n def to_s\n 'attributes must be an instance of Hash or respond to #to_h'\n end\n end\n \n class MissingAttributeError < Error\n def initialize(attr_name, attr_class)\n @name, @class = attr_name.to_s, attr_class.to_s\n end\n \n def to_s\n \"attribute '\#@name' does not exist for \#@class\"\n end\n end\n \n class InvalidBooksError < Error\n def to_s\n 'books must be an instance of Array or respond to #to_a'\n end\n end\n \n attr_reader *(@@_attributes = [:books])\n \n def initialize(attributes={})\n raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)\n attributes = attributes.to_h unless attributes.is_a?(Hash)\n \n attributes[:books] = Array.new unless attributes.has_key?(:books)\n \n attributes.each do |name, value|\n raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)\n \n setter_method = \"\#{name}=\"\n setter_method = \"_\#{setter_method}\" unless self.class.method_defined?(setter_method)\n \n send(setter_method, value)\n end\n end\n \n def add_book(attributes_or_book={})\n raise InvalidAttributesError unless attributes_or_book.is_a?(Hash) || attributes_or_book.respond_to?(:to_h) || attributes_or_book.is_a?(Book)\n attributes_or_book = attributes_or_book.to_h unless attributes_or_book.is_a?(Hash) || attributes_or_book.is_a?(Book)\n \n book = attributes_or_book.is_a?(Book) ? attributes_or_book : Book.new(attributes_or_book)\n book.library = self\n \n @books << book\n end\n \n protected\n \n def _books=(value)\n raise InvalidBooksError unless value.is_a?(Array) || value.respond_to?(:to_a)\n value = value.to_a unless value.is_a?(Array)\n \n @books = value\n end\nend\n```\n\nUsing the transcompiled Ruby code will produce the expected results:\n\n```ruby\np library = Library.new # => #<Library:0x007fc55c0ce418 @books=[]>\np library.add_book name: 'Book 1', isbn: 1 # => [#<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418 @books=[...]>>]\np library # => #<Library:0x007fc55c0ce418 @books=[#<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418 ...>>]>\np library.books.first # => #<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418 @books=[#<Book:0x007fc55c0cde78 ...>]>>\np library.books.first.isbn = nil # => nil\np library.books.first.name = nil # => Book::MissingNameError: name cannot be nil\np library.books.first.library = nil # => Book::MissingLibraryError: library cannot be nil\np library.books.first.isbn = ['array'] # => Book::InvalidIsbnError: isbn must be an instance of Integer or respond to :to_i\np library = Library.new(books: 123) # => Library::InvalidBooksError: books must be an instance of Array or respond to #to_a\n```\n\n### Breakdown\n\n```ruby\nobject Book\n attr name <String> -!wd 'Unnamed book'\nend\n```\n\nThis says that I have an object `Book` that has an attribute `name` (`attr name`) that \nmust either be an instance/subclass of `String` or be able to convert to an instance of \n`String` using `#to_s` (`<String>`). It is a required attribute that can never be set to nil (`!`), has a writer method (`w`),\nand defaults to 'Unnamed book'.\n\nThe minus sign (`-`) indicates a 'switch' or 'option', must like most *nix command line \nprograms. The example could also have been written like so:\n\n```ruby\nobject Book\n attr name <String> -! -w -d 'Unnamed book'\nend\n```\n\nThe above examples will transcompile into the following:\n\n```ruby\nclass Book\n class Error < RuntimeError; end\n \n class InvalidAttributesError < Error\n def to_s\n 'attributes must be a Hash or respond to #to_h'\n end\n end\n \n class MissingAttributeError < Error\n def initialize(attr_name, attr_class)\n @name, @class = attr_name.to_s, attr_class.to_s\n end\n \n def to_s\n \"attribute '\#@name' does not exist for \#@class\"\n end\n end\n \n class MissingNameError < Error\n def to_s\n 'name cannot be nil'\n end\n end\n \n class InvalidNameError < Error\n def to_s\n 'name must be an instance of String or respond to :to_s'\n end\n end\n \n attr_reader *(@@_attributes = [:name])\n @@_required_attributes = [:name]\n \n def initialize(attributes={})\n raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)\n attributes = attributes.to_h unless attributes.is_a?(Hash)\n \n raise MissingNameError if attributes.has_key?(:name) && attributes[:name].nil?\n \n attributes[:name] = 'Unnamed book' unless attributes.has_key?(:name)\n \n attributes.each do |name, value|\n raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)\n \n setter_method = \"\#{name}=\"\n setter_method = \"_\#{setter_method}\" unless self.class.method_defined?(setter_method)\n \n send(setter_method, value)\n end\n end\n \n def name=(value)\n raise MissingNameError if value.nil?\n raise InvalidNameError unless value.is_a?(String) || value.respond_to?(:to_s)\n value = value.to_i unless value.is_a?(Integer)\n \n @name = value\n end\nend\n```\n\n## Contributing\n\n* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet\n* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it\n* Fork the project\n* Start a feature/bugfix branch\n* Commit and push until you are happy with your contribution\n* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.\n* Please try not to mess with the Rakefile, VERSION, or Gemfile. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.\n\n## Copyright\n\nCopyright \u{a9} 2012 Ryan Scott Lewis <ryan@rynet.us>.\n\nThe MIT License (MIT) - See LICENSE for further details.\n\n[docs]: http://rubydoc.info/gems/trope/frames\n[gems]: https://rubygems.org/gems/trope\n[source]: https://github.com/RyanScottLewis/trope"
|
11
|
+
s.email = "ryan@rynet.us"
|
12
|
+
s.files = ["Gemfile", "LICENSE", "README.md", "Rakefile", "VERSION", "examples/1.output.rb", "examples/1.rb", "lib/trope.rb", "lib/trope/base.treetop", "lib/trope/error.rb", "lib/trope/language.rb", "lib/trope/language/attribute_keyword.rb", "lib/trope/language/body.rb", "lib/trope/language/constant.rb", "lib/trope/language/end_keyword.rb", "lib/trope/language/float_literal.rb", "lib/trope/language/integer_literal.rb", "lib/trope/language/literal.rb", "lib/trope/language/object_keyword.rb", "lib/trope/language/string_literal.rb", "lib/trope/language_parser.treetop", "lib/trope/node.rb", "lib/trope/parser.rb", "lib/trope/parser/error.rb", "trope.gemspec"]
|
13
|
+
s.homepage = "http://github.com/RyanScottLewis/trope"
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
s.rubygems_version = "1.8.24"
|
16
|
+
s.summary = "Prototyping language that transcompiles into pure Ruby code."
|
17
|
+
|
18
|
+
if s.respond_to? :specification_version then
|
19
|
+
s.specification_version = 3
|
20
|
+
|
21
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
22
|
+
s.add_runtime_dependency(%q<version>, ["~> 1.0.0"])
|
23
|
+
s.add_runtime_dependency(%q<treetop>, ["~> 1.4.12"])
|
24
|
+
s.add_runtime_dependency(%q<polyglot>, ["~> 0.3.3"])
|
25
|
+
s.add_development_dependency(%q<guard-rspec>, ["~> 2.1"])
|
26
|
+
s.add_development_dependency(%q<guard-yard>, ["~> 2.0"])
|
27
|
+
s.add_development_dependency(%q<rb-fsevent>, ["~> 0.9"])
|
28
|
+
s.add_development_dependency(%q<fuubar>, ["~> 1.1"])
|
29
|
+
s.add_development_dependency(%q<redcarpet>, ["~> 2.2.2"])
|
30
|
+
s.add_development_dependency(%q<github-markup>, ["~> 0.7"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<version>, ["~> 1.0.0"])
|
33
|
+
s.add_dependency(%q<treetop>, ["~> 1.4.12"])
|
34
|
+
s.add_dependency(%q<polyglot>, ["~> 0.3.3"])
|
35
|
+
s.add_dependency(%q<guard-rspec>, ["~> 2.1"])
|
36
|
+
s.add_dependency(%q<guard-yard>, ["~> 2.0"])
|
37
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9"])
|
38
|
+
s.add_dependency(%q<fuubar>, ["~> 1.1"])
|
39
|
+
s.add_dependency(%q<redcarpet>, ["~> 2.2.2"])
|
40
|
+
s.add_dependency(%q<github-markup>, ["~> 0.7"])
|
41
|
+
end
|
42
|
+
else
|
43
|
+
s.add_dependency(%q<version>, ["~> 1.0.0"])
|
44
|
+
s.add_dependency(%q<treetop>, ["~> 1.4.12"])
|
45
|
+
s.add_dependency(%q<polyglot>, ["~> 0.3.3"])
|
46
|
+
s.add_dependency(%q<guard-rspec>, ["~> 2.1"])
|
47
|
+
s.add_dependency(%q<guard-yard>, ["~> 2.0"])
|
48
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9"])
|
49
|
+
s.add_dependency(%q<fuubar>, ["~> 1.1"])
|
50
|
+
s.add_dependency(%q<redcarpet>, ["~> 2.2.2"])
|
51
|
+
s.add_dependency(%q<github-markup>, ["~> 0.7"])
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trope
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Scott Lewis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: version
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: treetop
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.4.12
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.4.12
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: polyglot
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.3.3
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.3.3
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard-rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.1'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.1'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-yard
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '2.0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rb-fsevent
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.9'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.9'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: fuubar
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.1'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.1'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: redcarpet
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 2.2.2
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 2.2.2
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: github-markup
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0.7'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0.7'
|
158
|
+
description: ! "# Trope\n\n**[Documentation][docs] - [Gem][gems] - [Source][source]**\n\nPrototyping
|
159
|
+
language that transcompiles into pure Ruby code.\n\n1. Build your concept in Trope.\n2.
|
160
|
+
Write specs.\n3. Transcompile into Ruby.\n4. Destroy Trope files.\n5. Red, green,
|
161
|
+
refactor.\n\n## Install\n\n> NOTE: Trope is not released yet, the gem is just a
|
162
|
+
placeholder.\n\n### Bundler: `gem 'trope'`\n\n### RubyGems: `gem install trope`\n\n##
|
163
|
+
Example\n\nCreate `library.trope`:\n\n```ruby\nobject Book\n attr name <String>
|
164
|
+
-!wd 'Unnamed book'\n attr isbn <Integer> -w\n attr library <Library> -w do\n
|
165
|
+
\ before write { @library.books.delete(self) unless @library.nil? }\n after
|
166
|
+
\ write { @library.books.push(self) unless @library.books.include?(self) }\n end\nend\n\nobject
|
167
|
+
Library\n attr books <Array> -d Array.new\n meth add_book do |attributes_or_book
|
168
|
+
<Hash, Book>|\n book = attributes_or_book.is_a?(Book) ? attributes_or_book :
|
169
|
+
Book.new(attributes_or_book)\n book.library = self\n \n @books << book\n
|
170
|
+
\ end\nend\n```\n\nNow generate the Ruby code:\n\n```sh\n$ trope compile libary.trope\n```\n\nThose
|
171
|
+
15 lines will be transcompiled into the following pure Ruby code in `library.rb`:\n\n```ruby\nclass
|
172
|
+
Book\n class Error < RuntimeError; end\n \n class InvalidAttributesError < Error\n
|
173
|
+
\ def to_s\n 'attributes must be a Hash or respond to #to_h'\n end\n end\n
|
174
|
+
\ \n class MissingAttributeError < Error\n def initialize(attr_name, attr_class)\n
|
175
|
+
\ @name, @class = attr_name.to_s, attr_class.to_s\n end\n \n def to_s\n
|
176
|
+
\ \"attribute '#@name' does not exist for #@class\"\n end\n end\n \n class
|
177
|
+
MissingNameError < Error\n def to_s\n 'name cannot be nil'\n end\n end\n
|
178
|
+
\ \n class InvalidNameError < Error\n def to_s\n 'name must be an instance
|
179
|
+
of String or respond to :to_s'\n end\n end\n \n class InvalidIsbnError < Error\n
|
180
|
+
\ def to_s\n 'isbn must be an instance of Integer or respond to :to_i'\n
|
181
|
+
\ end\n end\n \n class MissingLibraryError < Error\n def to_s\n 'library
|
182
|
+
cannot be nil'\n end\n end\n \n class InvalidLibraryError < Error\n def
|
183
|
+
to_s\n 'library must be an instance of Library'\n end\n end\n \n attr_reader
|
184
|
+
*(@@_attributes = [:name, :isbn, :library])\n \n def initialize(attributes={})\n
|
185
|
+
\ raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)\n
|
186
|
+
\ attributes = attributes.to_h unless attributes.is_a?(Hash)\n \n raise
|
187
|
+
MissingNameError if attributes.has_key?(:name) && attributes[:name].nil?\n attributes[:name]
|
188
|
+
= 'Unnamed book' unless attributes.has_key?(:name)\n \n attributes.each do
|
189
|
+
|name, value|\n raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)\n
|
190
|
+
\ \n setter_method = \"#{name}=\"\n setter_method = \"_#{setter_method}\"
|
191
|
+
unless self.class.method_defined?(setter_method)\n \n send(setter_method,
|
192
|
+
value)\n end\n end\n \n def name=(value)\n raise MissingNameError if value.nil?\n
|
193
|
+
\ raise InvalidNameError unless value.is_a?(String) || value.respond_to?(:to_s)\n
|
194
|
+
\ value = value.to_i unless value.is_a?(Integer)\n \n @name = value\n end\n
|
195
|
+
\ \n def isbn=(value)\n raise InvalidIsbnError unless value.is_a?(Integer) ||
|
196
|
+
value.respond_to?(:to_i)\n value = value.to_i unless value.is_a?(Integer)\n \n
|
197
|
+
\ @isbn = value\n end\n \n def library=(value)\n raise InvalidLibraryError
|
198
|
+
unless value.is_a?(Library) || value.nil?\n \n @library.books.delete(self)
|
199
|
+
unless @library.nil?\n \n @library = value\n \n @library.books.push(self)
|
200
|
+
unless @library.books.include?(self)\n \n @library\n end\nend\n\nclass Library\n
|
201
|
+
\ class Error < RuntimeError; end\n \n class InvalidAttributesError < Error\n
|
202
|
+
\ def to_s\n 'attributes must be an instance of Hash or respond to #to_h'\n
|
203
|
+
\ end\n end\n \n class MissingAttributeError < Error\n def initialize(attr_name,
|
204
|
+
attr_class)\n @name, @class = attr_name.to_s, attr_class.to_s\n end\n \n
|
205
|
+
\ def to_s\n \"attribute '#@name' does not exist for #@class\"\n end\n
|
206
|
+
\ end\n \n class InvalidBooksError < Error\n def to_s\n 'books must be
|
207
|
+
an instance of Array or respond to #to_a'\n end\n end\n \n attr_reader *(@@_attributes
|
208
|
+
= [:books])\n \n def initialize(attributes={})\n raise InvalidAttributesError
|
209
|
+
unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)\n attributes =
|
210
|
+
attributes.to_h unless attributes.is_a?(Hash)\n \n attributes[:books] = Array.new
|
211
|
+
unless attributes.has_key?(:books)\n \n attributes.each do |name, value|\n
|
212
|
+
\ raise MissingAttributeError.new(name, self.class) unless @@_attributes.include?(name.to_sym)\n
|
213
|
+
\ \n setter_method = \"#{name}=\"\n setter_method = \"_#{setter_method}\"
|
214
|
+
unless self.class.method_defined?(setter_method)\n \n send(setter_method,
|
215
|
+
value)\n end\n end\n \n def add_book(attributes_or_book={})\n raise InvalidAttributesError
|
216
|
+
unless attributes_or_book.is_a?(Hash) || attributes_or_book.respond_to?(:to_h) ||
|
217
|
+
attributes_or_book.is_a?(Book)\n attributes_or_book = attributes_or_book.to_h
|
218
|
+
unless attributes_or_book.is_a?(Hash) || attributes_or_book.is_a?(Book)\n \n
|
219
|
+
\ book = attributes_or_book.is_a?(Book) ? attributes_or_book : Book.new(attributes_or_book)\n
|
220
|
+
\ book.library = self\n \n @books << book\n end\n \n protected\n \n
|
221
|
+
\ def _books=(value)\n raise InvalidBooksError unless value.is_a?(Array) || value.respond_to?(:to_a)\n
|
222
|
+
\ value = value.to_a unless value.is_a?(Array)\n \n @books = value\n end\nend\n```\n\nUsing
|
223
|
+
the transcompiled Ruby code will produce the expected results:\n\n```ruby\np library
|
224
|
+
= Library.new # => #<Library:0x007fc55c0ce418 @books=[]>\np
|
225
|
+
library.add_book name: 'Book 1', isbn: 1 # => [#<Book:0x007fc55c0cde78 @name=0,
|
226
|
+
@isbn=1, @library=#<Library:0x007fc55c0ce418 @books=[...]>>]\np library #
|
227
|
+
=> #<Library:0x007fc55c0ce418 @books=[#<Book:0x007fc55c0cde78 @name=0, @isbn=1,
|
228
|
+
@library=#<Library:0x007fc55c0ce418 ...>>]>\np library.books.first #
|
229
|
+
=> #<Book:0x007fc55c0cde78 @name=0, @isbn=1, @library=#<Library:0x007fc55c0ce418
|
230
|
+
@books=[#<Book:0x007fc55c0cde78 ...>]>>\np library.books.first.isbn = nil #
|
231
|
+
=> nil\np library.books.first.name = nil # => Book::MissingNameError:
|
232
|
+
name cannot be nil\np library.books.first.library = nil # => Book::MissingLibraryError:
|
233
|
+
library cannot be nil\np library.books.first.isbn = ['array'] # => Book::InvalidIsbnError:
|
234
|
+
isbn must be an instance of Integer or respond to :to_i\np library = Library.new(books:
|
235
|
+
123) # => Library::InvalidBooksError: books must be an instance of Array
|
236
|
+
or respond to #to_a\n```\n\n### Breakdown\n\n```ruby\nobject Book\n attr name <String>
|
237
|
+
-!wd 'Unnamed book'\nend\n```\n\nThis says that I have an object `Book` that has
|
238
|
+
an attribute `name` (`attr name`) that \nmust either be an instance/subclass of
|
239
|
+
`String` or be able to convert to an instance of \n`String` using `#to_s` (`<String>`).
|
240
|
+
It is a required attribute that can never be set to nil (`!`), has a writer method
|
241
|
+
(`w`),\nand defaults to 'Unnamed book'.\n\nThe minus sign (`-`) indicates a 'switch'
|
242
|
+
or 'option', must like most *nix command line \nprograms. The example could also
|
243
|
+
have been written like so:\n\n```ruby\nobject Book\n attr name <String> -! -w -d
|
244
|
+
'Unnamed book'\nend\n```\n\nThe above examples will transcompile into the following:\n\n```ruby\nclass
|
245
|
+
Book\n class Error < RuntimeError; end\n \n class InvalidAttributesError < Error\n
|
246
|
+
\ def to_s\n 'attributes must be a Hash or respond to #to_h'\n end\n end\n
|
247
|
+
\ \n class MissingAttributeError < Error\n def initialize(attr_name, attr_class)\n
|
248
|
+
\ @name, @class = attr_name.to_s, attr_class.to_s\n end\n \n def to_s\n
|
249
|
+
\ \"attribute '#@name' does not exist for #@class\"\n end\n end\n \n class
|
250
|
+
MissingNameError < Error\n def to_s\n 'name cannot be nil'\n end\n end\n
|
251
|
+
\ \n class InvalidNameError < Error\n def to_s\n 'name must be an instance
|
252
|
+
of String or respond to :to_s'\n end\n end\n \n attr_reader *(@@_attributes
|
253
|
+
= [:name])\n @@_required_attributes = [:name]\n \n def initialize(attributes={})\n
|
254
|
+
\ raise InvalidAttributesError unless attributes.is_a?(Hash) || attributes.respond_to?(:to_h)\n
|
255
|
+
\ attributes = attributes.to_h unless attributes.is_a?(Hash)\n \n raise
|
256
|
+
MissingNameError if attributes.has_key?(:name) && attributes[:name].nil?\n \n
|
257
|
+
\ attributes[:name] = 'Unnamed book' unless attributes.has_key?(:name)\n \n
|
258
|
+
\ attributes.each do |name, value|\n raise MissingAttributeError.new(name,
|
259
|
+
self.class) unless @@_attributes.include?(name.to_sym)\n \n setter_method
|
260
|
+
= \"#{name}=\"\n setter_method = \"_#{setter_method}\" unless self.class.method_defined?(setter_method)\n
|
261
|
+
\ \n send(setter_method, value)\n end\n end\n \n def name=(value)\n
|
262
|
+
\ raise MissingNameError if value.nil?\n raise InvalidNameError unless value.is_a?(String)
|
263
|
+
|| value.respond_to?(:to_s)\n value = value.to_i unless value.is_a?(Integer)\n
|
264
|
+
\ \n @name = value\n end\nend\n```\n\n## Contributing\n\n* Check out the latest
|
265
|
+
master to make sure the feature hasn't been implemented or the bug hasn't been fixed
|
266
|
+
yet\n* Check out the issue tracker to make sure someone already hasn't requested
|
267
|
+
it and/or contributed it\n* Fork the project\n* Start a feature/bugfix branch\n*
|
268
|
+
Commit and push until you are happy with your contribution\n* Make sure to add tests
|
269
|
+
for it. This is important so I don't break it in a future version unintentionally.\n*
|
270
|
+
Please try not to mess with the Rakefile, VERSION, or Gemfile. If you want to have
|
271
|
+
your own version, or is otherwise necessary, that is fine, but please isolate to
|
272
|
+
its own commit so I can cherry-pick around it.\n\n## Copyright\n\nCopyright © 2012
|
273
|
+
Ryan Scott Lewis <ryan@rynet.us>.\n\nThe MIT License (MIT) - See LICENSE for further
|
274
|
+
details.\n\n[docs]: http://rubydoc.info/gems/trope/frames\n[gems]: https://rubygems.org/gems/trope\n[source]:
|
275
|
+
https://github.com/RyanScottLewis/trope"
|
276
|
+
email: ryan@rynet.us
|
277
|
+
executables: []
|
278
|
+
extensions: []
|
279
|
+
extra_rdoc_files: []
|
280
|
+
files:
|
281
|
+
- Gemfile
|
282
|
+
- LICENSE
|
283
|
+
- README.md
|
284
|
+
- Rakefile
|
285
|
+
- VERSION
|
286
|
+
- examples/1.output.rb
|
287
|
+
- examples/1.rb
|
288
|
+
- lib/trope.rb
|
289
|
+
- lib/trope/base.treetop
|
290
|
+
- lib/trope/error.rb
|
291
|
+
- lib/trope/language.rb
|
292
|
+
- lib/trope/language/attribute_keyword.rb
|
293
|
+
- lib/trope/language/body.rb
|
294
|
+
- lib/trope/language/constant.rb
|
295
|
+
- lib/trope/language/end_keyword.rb
|
296
|
+
- lib/trope/language/float_literal.rb
|
297
|
+
- lib/trope/language/integer_literal.rb
|
298
|
+
- lib/trope/language/literal.rb
|
299
|
+
- lib/trope/language/object_keyword.rb
|
300
|
+
- lib/trope/language/string_literal.rb
|
301
|
+
- lib/trope/language_parser.treetop
|
302
|
+
- lib/trope/node.rb
|
303
|
+
- lib/trope/parser.rb
|
304
|
+
- lib/trope/parser/error.rb
|
305
|
+
- trope.gemspec
|
306
|
+
homepage: http://github.com/RyanScottLewis/trope
|
307
|
+
licenses: []
|
308
|
+
post_install_message:
|
309
|
+
rdoc_options: []
|
310
|
+
require_paths:
|
311
|
+
- lib
|
312
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
313
|
+
none: false
|
314
|
+
requirements:
|
315
|
+
- - ! '>='
|
316
|
+
- !ruby/object:Gem::Version
|
317
|
+
version: '0'
|
318
|
+
segments:
|
319
|
+
- 0
|
320
|
+
hash: 4024963032574925327
|
321
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
322
|
+
none: false
|
323
|
+
requirements:
|
324
|
+
- - ! '>='
|
325
|
+
- !ruby/object:Gem::Version
|
326
|
+
version: '0'
|
327
|
+
requirements: []
|
328
|
+
rubyforge_project:
|
329
|
+
rubygems_version: 1.8.24
|
330
|
+
signing_key:
|
331
|
+
specification_version: 3
|
332
|
+
summary: Prototyping language that transcompiles into pure Ruby code.
|
333
|
+
test_files: []
|