talk 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +6 -0
- data/Gemfile +10 -0
- data/README.md +6 -0
- data/Rakefile +9 -0
- data/features/class-field.feature +226 -0
- data/features/class.feature +95 -0
- data/features/enumeration-constant.feature +76 -0
- data/features/enumeration.feature +35 -0
- data/features/glossary-term.feature +46 -0
- data/features/glossary.feature +35 -0
- data/features/step_definitions/class-field.rb +74 -0
- data/features/step_definitions/class.rb +50 -0
- data/features/step_definitions/enumeration-constant.rb +42 -0
- data/features/step_definitions/enumeration.rb +29 -0
- data/features/step_definitions/glossary-term.rb +31 -0
- data/features/step_definitions/glossary.rb +23 -0
- data/features/support/env.rb +261 -0
- data/lib/context.rb +282 -0
- data/lib/context_class.rb +224 -0
- data/lib/contexts/README.md +274 -0
- data/lib/contexts/base.rb +6 -0
- data/lib/contexts/boolean.rb +5 -0
- data/lib/contexts/class.rb +11 -0
- data/lib/contexts/constant.rb +5 -0
- data/lib/contexts/enumeration.rb +20 -0
- data/lib/contexts/field.rb +47 -0
- data/lib/contexts/glossary.rb +7 -0
- data/lib/contexts/inherits.rb +3 -0
- data/lib/contexts/map.rb +7 -0
- data/lib/contexts/meta.rb +2 -0
- data/lib/contexts/method.rb +15 -0
- data/lib/contexts/numeric.rb +5 -0
- data/lib/contexts/protocol.rb +8 -0
- data/lib/contexts/reference.rb +6 -0
- data/lib/contexts/string.rb +5 -0
- data/lib/contexts/target.rb +11 -0
- data/lib/contexts/term.rb +11 -0
- data/lib/languages/java/java.rb +145 -0
- data/lib/languages/java/templates/class.java.erb +22 -0
- data/lib/languages/java/templates/enumeration.java.erb +10 -0
- data/lib/languages/java/templates/glossary.java.erb +8 -0
- data/lib/languages/language.rb +172 -0
- data/lib/languages/objc/objc.rb +162 -0
- data/lib/languages/objc/templates/TalkClasses.h.erb +3 -0
- data/lib/languages/objc/templates/TalkClassesForward.h.erb +2 -0
- data/lib/languages/objc/templates/TalkConstants.h.erb +22 -0
- data/lib/languages/objc/templates/TalkObjectList.h.erb +4 -0
- data/lib/languages/objc/templates/class.h.erb +21 -0
- data/lib/languages/objc/templates/class.m.erb +43 -0
- data/lib/parse_error.rb +4 -0
- data/lib/parser.rb +119 -0
- data/lib/registry.rb +151 -0
- data/lib/talk.rb +5 -0
- data/talk.gemspec +18 -0
- 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,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,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
|
+
|
data/lib/contexts/map.rb
ADDED
@@ -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,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
|
+
}
|