trope 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
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.
@@ -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
@@ -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
@@ -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__
@@ -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
@@ -0,0 +1,8 @@
1
+ module Trope
2
+
3
+ # The base class for all Trope errors.
4
+ class Error < StandardError
5
+
6
+ end
7
+
8
+ end
@@ -0,0 +1,14 @@
1
+ require 'pathname'
2
+
3
+ Dir[ Pathname.new(__FILE__).join('..', 'language').expand_path.join('**', '*.rb') ].each do |path|
4
+ require path
5
+ end
6
+
7
+ module Trope
8
+
9
+ # The container for language syntax nodes.
10
+ module Language
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'trope/node'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # The attribute keyword.
7
+ class AttributeKeyword < Node
8
+
9
+ # Get the value of the attribute.
10
+ def value
11
+ elements.first.value
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'trope/node'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # The body of the code.
7
+ class Body < Node
8
+
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'trope/node'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # A constant.
7
+ class Constant < Node
8
+
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'trope/node'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # The end keyword.
7
+ class EndKeyword < Node
8
+
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require 'trope/language/literal'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # A float.
7
+ class FloatLiteral < Literal
8
+
9
+ # The value as a Ruby object.
10
+ def value
11
+ super.to_f
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'trope/language/literal'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # An integer.
7
+ class IntegerLiteral < Literal
8
+
9
+ # The value as a Ruby object.
10
+ def value
11
+ super.to_i
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'trope/node'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # A literal.
7
+ class Literal < Node
8
+
9
+ # The value of the literal.
10
+ def value
11
+ elements_with_treetop[1].text_value # Skip the first node
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'trope/node'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # The object keyword.
7
+ class ObjectKeyword < Node
8
+
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'trope/language/literal'
2
+
3
+ module Trope
4
+ module Language
5
+
6
+ # A string.
7
+ class StringLiteral < Literal
8
+
9
+ end
10
+
11
+ end
12
+ end
@@ -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
@@ -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
@@ -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
@@ -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: []