talk 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +10 -0
  5. data/README.md +6 -0
  6. data/Rakefile +9 -0
  7. data/features/class-field.feature +226 -0
  8. data/features/class.feature +95 -0
  9. data/features/enumeration-constant.feature +76 -0
  10. data/features/enumeration.feature +35 -0
  11. data/features/glossary-term.feature +46 -0
  12. data/features/glossary.feature +35 -0
  13. data/features/step_definitions/class-field.rb +74 -0
  14. data/features/step_definitions/class.rb +50 -0
  15. data/features/step_definitions/enumeration-constant.rb +42 -0
  16. data/features/step_definitions/enumeration.rb +29 -0
  17. data/features/step_definitions/glossary-term.rb +31 -0
  18. data/features/step_definitions/glossary.rb +23 -0
  19. data/features/support/env.rb +261 -0
  20. data/lib/context.rb +282 -0
  21. data/lib/context_class.rb +224 -0
  22. data/lib/contexts/README.md +274 -0
  23. data/lib/contexts/base.rb +6 -0
  24. data/lib/contexts/boolean.rb +5 -0
  25. data/lib/contexts/class.rb +11 -0
  26. data/lib/contexts/constant.rb +5 -0
  27. data/lib/contexts/enumeration.rb +20 -0
  28. data/lib/contexts/field.rb +47 -0
  29. data/lib/contexts/glossary.rb +7 -0
  30. data/lib/contexts/inherits.rb +3 -0
  31. data/lib/contexts/map.rb +7 -0
  32. data/lib/contexts/meta.rb +2 -0
  33. data/lib/contexts/method.rb +15 -0
  34. data/lib/contexts/numeric.rb +5 -0
  35. data/lib/contexts/protocol.rb +8 -0
  36. data/lib/contexts/reference.rb +6 -0
  37. data/lib/contexts/string.rb +5 -0
  38. data/lib/contexts/target.rb +11 -0
  39. data/lib/contexts/term.rb +11 -0
  40. data/lib/languages/java/java.rb +145 -0
  41. data/lib/languages/java/templates/class.java.erb +22 -0
  42. data/lib/languages/java/templates/enumeration.java.erb +10 -0
  43. data/lib/languages/java/templates/glossary.java.erb +8 -0
  44. data/lib/languages/language.rb +172 -0
  45. data/lib/languages/objc/objc.rb +162 -0
  46. data/lib/languages/objc/templates/TalkClasses.h.erb +3 -0
  47. data/lib/languages/objc/templates/TalkClassesForward.h.erb +2 -0
  48. data/lib/languages/objc/templates/TalkConstants.h.erb +22 -0
  49. data/lib/languages/objc/templates/TalkObjectList.h.erb +4 -0
  50. data/lib/languages/objc/templates/class.h.erb +21 -0
  51. data/lib/languages/objc/templates/class.m.erb +43 -0
  52. data/lib/parse_error.rb +4 -0
  53. data/lib/parser.rb +119 -0
  54. data/lib/registry.rb +151 -0
  55. data/lib/talk.rb +5 -0
  56. data/talk.gemspec +18 -0
  57. metadata +71 -3
@@ -0,0 +1,274 @@
1
+ # About Talk::Context
2
+
3
+ ## The Context Stack
4
+
5
+ `Talk::Context` is a class to manage parsing Talk files. Each `Context` corresponds to a given tag. For example, `@class` tags are managed by a `Context` subclass defined in `contexts/class.rb`.
6
+
7
+ When the Talk parser hits a given tag, it instantiates a new `Context` object of the appropriate subclass to manage parsing that tag. Since the parser was already in a context, it adds the new context to a stack.
8
+
9
+ As the parser reads in new text, it passes that text to the current context (i.e. the topmost context on the stack). There are two kinds of data that each context will manage
10
+
11
+ 1. Children (i.e. tags within the current tag)
12
+ 2. Properties (i.e. data belonging to the tag itself)
13
+
14
+ As an example, consider the following Talk definition:
15
+
16
+ ```
17
+ @class SomeClass
18
+ @description This is a description of the class
19
+ @field int32 someField
20
+ This is a field
21
+ @end
22
+ @field string anotherField
23
+ @description This is another field
24
+ @end
25
+ @end
26
+ ```
27
+
28
+ In this example, we see that the `@class` tag has one piece of data about the tag itself (the name, `SomeClass`), and 3 child tags (an `@description` and two `@field` tags).
29
+
30
+ The `Context` class defined in `contexts/class.rb` knows to expect a single word for a name, and that it is legal to have `@field` and `@description` tags, and it knows which context classes to use for each of them.
31
+
32
+ ## Context subclasses
33
+ Defining a context subclass is easy. Here's an example from `contexts/class.rb` for parsing `@class` tags:
34
+
35
+ ```
36
+ register :classes, :delimiter => '.'
37
+ reference :inherits, :classes
38
+
39
+ property :name
40
+
41
+ tag_description
42
+ tag :version, :default => "0", :class => :string
43
+ tag :field, :multi => true, :unique => :name
44
+ tag :implement, :class => :boolean, :default => true
45
+ tag :inherits, :class => :string
46
+ tag_end
47
+ ```
48
+
49
+ This tells us to expect the following:
50
+
51
+ 1. A property called 'name'
52
+ 2. An `@description` tag with the default behavior
53
+ 3. An `@version` tag, whose default value is `"0"` if the user doesn't specify a 4. value
54
+ 4. An `@field` tag, which can appear multiple times, but must have a unique name each time
55
+ 4. An `@implement` tag, interpreted by `contexts/boolean.rb` whose default value is `true`
56
+ 5. An `@inherits` tag, interpreted by `contexts/string.rb`
57
+ 6. Every time we create a tag in this context, we'll register its name property in the `classes` namespace, using `"."` as a delimiter
58
+ 7. A requirement that if a tag parsed in this context uses `@inherits`, then the value specified must be the name of a class we define somewhere else
59
+
60
+ This sounds very complex, doesn't it? It's a bit domain-specific, but I hope you'll find it's actually quite easy to wrap your head around once you know what's going on, which is what this document is meant to help you do.
61
+
62
+ ## Lifecycle
63
+
64
+ To see how a `Context` works, it helps to walk through the life of an instance from start to finish.
65
+
66
+ ### Definition
67
+ Before we can instantiate an instance, we need to define the class. All `Context` subclasses inherit the class methods from `lib/context_class.rb` and the instance methods from `lib/context.rb`, and the subclasses themselves live in `lib/contexts`.
68
+
69
+ If you open up that directory, you'll see that there is no boilerplate to any of these files -- not even a formal statement that they inherit from `Context`! This is because the code you write into these files will automatically be evaluated into a new `Context` subclass created at run-time.
70
+
71
+ You might also notice that we never refer to any `Context` subclass definition files by name in the source, nor do we glob for them. The only subclass we explicitly look for is `contexts/base.rb` which defines the top-level context for parsing Talk files.
72
+
73
+ When `Context` loads this file, it will see that it declares tags, like `tag :class`. By default, `Context` will now look for `contexts/class.rb` to provide the context for tags instantiated into this context. But, we can override that behavior using parameters. Recall this line from `contexts/class.rb`:
74
+
75
+ `tag :implement, :class => :boolean`
76
+
77
+ This tells `Context` to use the subclass defined in `contexts/boolean.rb` instead.
78
+
79
+ ### Instantiation
80
+
81
+ An actual `Context` instance begins when a parent context sees a tag and creates a new instance to parse it. As discussed above, the parent context knows which subclass to look for based on the actual tag definition itself.
82
+
83
+ Once the new context is created, it is pushed to the top of the context stack and receives every token from the input.
84
+
85
+ ### Parsing
86
+
87
+ One-by-one, words are fed to the `Context` instance by the parser via the `Context#parse` method. These words are held in an array until the `Context` instance is closed, and then parsed for property data. If the parser sees a new tag, it will call `Context#start_tag` and the instance will return the appropriate new context to add to the context stack. This new context will now receive the data from the parser, and the current context will receive nothing until the new context ends.
88
+
89
+ #### Tag transformation
90
+
91
+ When a tag closes, it has the opportunity to have a transformation applied to it, via the `:transform` parameter. This provides `Context` classes an ability to perform additional processing on tag data that is specific to the needs of the parent.
92
+
93
+ #### Tag validation
94
+
95
+ After any and all transformations have been applied to the tag, the `Context` instance may run optional validation tests against it to ensure that it contains an acceptable value.
96
+
97
+ ### Closure
98
+
99
+ A `Context` can be closed when it either starts a tag whose handler class is defined to be `nil` (as is the case with the `tag_end` macro for generating `@end` tag support), or if the parser encounters a tag that the active `Context` subclass does not handle but a parent `Context` does.
100
+
101
+ Once a context closes, it begins closure processing, which includes property parsing, registration and post-processing.
102
+
103
+ #### Property parsing
104
+
105
+ Recall that all non-tag data is accumulated into the `Context` instance. During the property parsing phase, the `Context` instance assigns this data to individual properties. Each property may have transformations and validations applied to it, exactly as tags do.
106
+
107
+ #### Post-processing
108
+
109
+ `Context` subclasses may supply optional post-processing blocks using the `postprocess` method. These blocks serve as a final transformation of the entire class.
110
+
111
+ #### Registration
112
+
113
+ If the tag registers any of its properties in a namespace, that registration is now done.
114
+
115
+ ### Finalization
116
+
117
+ After all input has been parsed, the parser will close all open contexts and call the `finalize` method on each `Context` instance. Instances will be finalized in the order they were closed in, so that the first instance to close will be the first to finalize, and the base instance will be the last to finalize.
118
+
119
+ Finalization is a phase in which validations are performed that may depend upon an inter-relation between tags, e.g. dependencies between @glossary names and @see references.
120
+
121
+ #### Final validation
122
+
123
+ A `Context` instance may supply a final validation block to perform last-minute custom validation prior to cross-referencing, or to implement its own form of cross-referencing that goes beyond the sophistication of the built-in cross-referencing facility.
124
+
125
+ #### Cross-referencing
126
+
127
+ If a `Context` subclass references symbols (like class names), the parser will now check that these symbols are actually defined. For instance, the @see tag will make references to things like glossaries. In this phase, a parse error is generated if these references point to things we haven't defined yet.
128
+
129
+ ## Writing Subclasses
130
+ ### Creating the subclass
131
+
132
+ To create a subclass to support a new tag, you need to do 3 things:
133
+
134
+ 1. Refer to your new tag somewhere in the hierarchy. For example, if you're creating a new `@foobar` tag that is valid at the base level, then open `contexts/base.rb` and write a line like `tag :foobar`.
135
+ 2. Create the implementation for your new tag. By default, `Context` will use the tag name itself as the basis of the filename, so it will look for `contexts/foobar.rb` in our example.
136
+ 3. Write the implementation. This is the fun part!
137
+
138
+ ### Writing the implementation
139
+
140
+ Your subclass file does not need any boiler plate. You **do not** need to use any class or module definitions. When your code is executed, it will be in a scope that looks like this:
141
+
142
+ ```ruby
143
+ module Talk
144
+ class YourSubclass << Context
145
+ class << self
146
+ # your code will be injected here
147
+ end
148
+ end
149
+ end
150
+ ```
151
+
152
+ Context has most, or all of the machinery you'll need to define your subclass, using a handful of methods.
153
+
154
+ | method | description
155
+ |--|--
156
+ |**property**|Define a new property
157
+ |**tag**|Define a new tag
158
+ |**tag_description**|Shorthand to create a typical @description tag
159
+ |**tag_end**|Shorthand to create typical @end tag
160
+ |**register**|Register a property of the instance into a namespace, ensuring uniqueness and allowing cross-referencing
161
+ |**reference**|Require that a property of the instance match a key registered into a namespace by another context
162
+ |**validate**|Perform a validation of a property or tag immediately upon parsing
163
+ |**postprocess**|Perform a transformation on the entire object after parsing all files, but before final validation
164
+ |**validate_final**|Perform a validation of the entire object after parsing all files, but before cross-referencing
165
+ |**bridge_tag_to_property**|Allow a child tag to get its property data from the property data of this tag
166
+
167
+ #### property(name, params={})
168
+ Ex.: `property :name`
169
+
170
+ Generate support for a property with a given name and optional parameters.
171
+
172
+ Parameters:
173
+
174
+ | key | description | default
175
+ |--|--|--
176
+ | :allowed | Array of allowable values. Throws parse error if a value is set that is not within the allowed values. | nil
177
+ | :length | Integer or array. If integer, number of words to expect in a property. If array, then array must have 2 integers with minimum and maximum number of words to expect in a property. The maximum may be nil to indicate an unbounded property. | 1
178
+ | :required | Boolean. If true, throws parse error if property is not set. | true
179
+ | :transform | Block, taking \|context, value\| as input, returning modified value as output. Invoked prior to validating value. | nil
180
+
181
+ The `:allowed` property has a bit of extra magic to it: you can use nested arrays to normalize a range of allowed values to the first element of the subarray. Check out this example from `contexts/boolean.rb`:
182
+
183
+ `property :value, :transform => lambda { |ctx,v| v.downcase }, :allowed => [ ["0", "no", "false", "off"], ["1", "yes", "true", "on"] ]`
184
+
185
+ This says that I'm allowed to specify "0", "NO", "no", "False" or "oFF", but it'll all get stored as "0". Likewise, "1", "yes", "true" and "on" get stored as "1".
186
+
187
+ #### tag(name, params={})
188
+ Ex.: `tag :version, :class => :string`
189
+
190
+ Generate support for a tag with a given name and optional parameters.
191
+
192
+ Parameters:
193
+
194
+ | key | description | default
195
+ |--|--|--
196
+ | :class | Symbol. Name of `Context` subclass to use for parsing this tag | Tag name
197
+ | :multi | Boolean. Do not generate parse error if tag appears twice. | false
198
+ | :required | Boolean. Generate parse error if tag is omitted. | false
199
+
200
+ #### tag_description
201
+ Ex.: `tag_description`
202
+
203
+ Generate support for a @description tag with typical sematics. See source for details.
204
+
205
+ #### tag_end
206
+ Ex.: `tag_end`
207
+
208
+ Generate support for a @end tag with typical semantics. See source for details.
209
+
210
+ #### register(namespace, params={})
211
+ Ex.: `register :classes`
212
+
213
+ Ensure the uniqueness of a given name or identifier in an object by registering it in a namespace. By default, registers the :name property.
214
+
215
+ Parameters:
216
+
217
+ | key | description | default
218
+ |--|--|--
219
+ | :name | Symbol. Name of property containing string to be registered in namespace | :name
220
+ | :delimiter | String. Character to use in splitting multi-level identifiers. | nil
221
+
222
+ The `:delimiter` field is a helpful bit of shorthand. For instance, `context/class.rb` uses `delimiter => '.'`. This allows us to register a class like `@class com.example.some.long.ClassName`, but reference it later as `@field ClassName someField`, or even `@field some.long.ClassName someField`.
223
+
224
+ #### reference(name, namespace, params={})
225
+ Ex.: `reference :request, :classes, :skip => ["none"]`
226
+
227
+ Ensure that a string specified in a given tag or property is registered in the given namespace. Causes a parse error to be generated if the symbol is not registered after all files are parsed.
228
+
229
+ namespace can be a symbol, or a block. If namespace is a block, it takes the form `{ |ctx| :some_namespace }`. That is, it takes in the `Context` instance as a parameter and returns the namespace as a result.
230
+
231
+ If a tag is named as a reference, the `:value` property of the tag will be used as the string.
232
+
233
+ Parameters:
234
+
235
+ | key | description | default
236
+ |--|--|--
237
+ | :skip | Array. Contains strings of values that are permitted regardless of whether or not they are registered in any namespace. | []
238
+
239
+
240
+ #### validate(message, name, block)
241
+ Ex. `validate("Field name cannot start with __", :name, lambda { |ctx, name| not name.start_with?("__") })`
242
+
243
+ Invokes the given block when a tag or property is set. Generates a parse error with the supplied message if the block returns false.
244
+
245
+ The block receives two arguments: a reference to the `Context` instance, and the proposed value being validated. The value is **not** set into the `Context` at the time the validate block is called.
246
+
247
+ Because the error message is compiled at the time the class is defined, you cannot place references to the offending value inside the message. If this isn't suitable, consider just generating a parse error yourself inside the block.
248
+
249
+ A given property or tag can have multiple `validate` blocks attached to it.
250
+
251
+ #### postprocess(block)
252
+ Ex. `postprocess lambda { |ctx| do_stuff; }`
253
+
254
+ Invokes a given block after ALL files have been parsed, but before final validation and cross-referencing.
255
+
256
+ #### validate_final(message, block)
257
+ Ex. `validate_final("An error message", lambda { |ctx| test_something; }
258
+
259
+ Invokes the given block after ALL files have been parsed, but before final validation and cross-referencing. Generates a parse error with the supplied message if the block returns false.
260
+
261
+ #### bridge_tag_to_property(name)
262
+ Ex. `bridge_tag_to_property :description`
263
+
264
+ Allows the trailing property data of this tag to be passed as property data to a child tag. This is used in `@description` tags to make both of these variants parse:
265
+
266
+ ```
267
+ @class SomeClass
268
+ @description This is a class
269
+ @end
270
+ ```
271
+
272
+ ```
273
+ @class SomeClass This is a class
274
+ ```
@@ -0,0 +1,6 @@
1
+ tag :class, :multi => true
2
+ tag :method, :multi => true
3
+ tag :enumeration, :multi => true
4
+ tag :protocol, :multi => true
5
+ tag :target, :multi => true
6
+ tag :glossary, :multi => true
@@ -0,0 +1,5 @@
1
+ property :value, :transform => lambda { |ctx,v| v.downcase }, :allowed => [ ["0", "no", "false", "off"], ["1", "yes", "true", "on"] ]
2
+
3
+ def to_val
4
+ self[:value] == "0" ? false : true
5
+ end
@@ -0,0 +1,11 @@
1
+ register :classes, :delimiter => '.'
2
+ reference :inherits, :classes
3
+
4
+ property :name
5
+
6
+ tag_description
7
+ tag :version, :default => "0", :class => :string
8
+ tag :field, :multi => true, :unique => :name
9
+ tag :implement, :class => :boolean, :default => true
10
+ tag :inherits, :class => :string
11
+ tag_end
@@ -0,0 +1,5 @@
1
+ property :name
2
+ property :value, :transform => lambda { |c,v| eval(v.to_s).to_f }, :length => [0,nil]
3
+
4
+ tag_description :required => false, :bridge => false
5
+ tag_end
@@ -0,0 +1,20 @@
1
+ register :enumerations, :delimiter => '.'
2
+ property :name
3
+
4
+ tag_description
5
+ tag :constant, :multi => true, :unique => :name
6
+ tag_end
7
+
8
+ postprocess lambda { |ctx|
9
+ # If we don't specify a constant value, use C-like implicit values
10
+ # (i.e. constant[n] = {
11
+ # constant[n-1] + 1 n > 0
12
+ # 0 otherwise
13
+ # }
14
+ return if ctx[:constant].nil?
15
+ ctx[:constant].inject(0) do |value, constant|
16
+ value = constant[:value] unless constant[:value].nil?
17
+ constant[:value] = value
18
+ value += 1
19
+ end
20
+ }
@@ -0,0 +1,47 @@
1
+ def dissect_type(type)
2
+ containers = []
3
+ while is_container?(type)
4
+ containers.push type[-2..-1] # last two characters
5
+ type = type[0..-3] # all but last two
6
+ end
7
+
8
+ containers.push type
9
+ containers.reverse
10
+ end
11
+
12
+ def is_primitive?(type)
13
+ primitives = [
14
+ "uint8", "uint16", "uint32", "uint64",
15
+ "int8", "int16", "int32", "int64",
16
+ "string", "real", "bool", "object", "talkobject" ]
17
+ primitives.include? type
18
+ end
19
+
20
+ def is_container?(type)
21
+ type.end_with? "[]" or type.end_with? "{}"
22
+ end
23
+
24
+ property :type, :transform => lambda { |c,v| c.dissect_type(v) }
25
+ property :name
26
+
27
+ tag_description
28
+ tag :version, :class => :string
29
+ tag :caveat, :class => :string, :multi => true
30
+ tag :deprecated, :class => :string
31
+ tag :see, :class => :reference, :multi => true
32
+ tag_end
33
+
34
+ postprocess(lambda do |ctx|
35
+ registrations = Talk::Registry.get_registrations(ctx[:type].first, :classes)
36
+ ctx[:type][0] = registrations[0].name unless registrations.nil? or registrations.empty?
37
+ end)
38
+
39
+ validate("Field name cannot start with __", :name, lambda { |ctx, name| not name.start_with?("__") })
40
+ validate_final("Field name is not a recognized primitive or class", lambda do |ctx|
41
+ t = ctx[:type]
42
+ return true if ctx.is_primitive?(t.first)
43
+ ctx.crossreference_value(t.first, :classes)
44
+
45
+ true
46
+ end)
47
+
@@ -0,0 +1,7 @@
1
+ register :glossaries, :delimiter => '.'
2
+
3
+ property :name
4
+
5
+ tag_description
6
+ tag :term, :multi => true, :unique => :name
7
+ tag_end
@@ -0,0 +1,3 @@
1
+ reference :name, :classes
2
+
3
+ property :name
@@ -0,0 +1,7 @@
1
+ reference :class_name, :classes
2
+ # TODO: cross-reference field
3
+
4
+ property :type, :class => :string, :allowed => ['field']
5
+ property :class_name, :class => :string
6
+ property :field_name, :class => :string
7
+ property :new_field_name, :class => :string
@@ -0,0 +1,2 @@
1
+ property :name
2
+ property :value, :length => [1,nil]
@@ -0,0 +1,15 @@
1
+ # register :methods
2
+ reference :response, :classes, :skip => ["none"]
3
+ reference :request, :classes, :skip => ["none"]
4
+ reference :followup, :classes, :skip => ["none"]
5
+
6
+ property :name
7
+
8
+ tag_description
9
+ tag :response, :class => :string
10
+ tag :request, :class => :string
11
+ tag :followup, :class => :string
12
+ tag :requirements, :class => :string
13
+ tag :origin, :class => :string, :allowed => [ "client", "server", "both" ]
14
+
15
+ tag_end
@@ -0,0 +1,5 @@
1
+ property :value, :transform => lambda { |v| v.to_f }
2
+
3
+ def to_val
4
+ self[:value]
5
+ end
@@ -0,0 +1,8 @@
1
+ register :protocols
2
+
3
+ property :name
4
+
5
+ tag_description
6
+ tag :method, :class => :string, :required => true, :multi => true
7
+ tag :source, :class => :string
8
+ tag_end
@@ -0,0 +1,6 @@
1
+ property :type, :transform => lambda { |c, v| v.downcase }, :allowed => [ "class", ["enumeration", "enum"], "glossary" ]
2
+ property :name
3
+
4
+ reference :name, lambda { |ctx|
5
+ { "class" => :classes, "glossary" => :glossaries, "enumeration" => :enumerations, "enum" => :enumerations }[ctx[:type]]
6
+ }
@@ -0,0 +1,5 @@
1
+ property :value, :length => [1, nil]
2
+
3
+ def to_val
4
+ self[:value]
5
+ end