rtml 2.0.3 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. data/History.txt +3 -0
  2. data/Manifest.txt +51 -13
  3. data/Rakefile +6 -1
  4. data/builtin/controllers/rtml_controller.rb +12 -1
  5. data/builtin/models/rtml/document.rb +24 -18
  6. data/builtin/models/rtml/document_model_object.rb +6 -0
  7. data/builtin/models/rtml/dom/collections/element_set.rb +6 -0
  8. data/builtin/models/rtml/dom/collections/property_set.rb +11 -0
  9. data/builtin/models/rtml/dom/element.rb +79 -27
  10. data/builtin/models/rtml/dom/frontend_element.rb +41 -20
  11. data/builtin/models/rtml/dom/property.rb +43 -26
  12. data/builtin/models/rtml/dom/screen_element.rb +13 -0
  13. data/builtin/widgets/document_variable_processing.rb +42 -18
  14. data/builtin/widgets/element_builder.rb +4 -4
  15. data/builtin/widgets/screen_variable_processing.rb +2 -2
  16. data/builtin/widgets/screen_variants.rb +31 -4
  17. data/builtin/widgets/screens.rb +13 -1
  18. data/builtin/widgets/static_content.rb +20 -6
  19. data/do_profile.rb +15 -0
  20. data/lib/extensions/action_controller/response.rb +0 -18
  21. data/lib/extensions/action_controller/routing/route_set.rb +28 -18
  22. data/lib/extensions/hpricot/doc.rb +3 -3
  23. data/lib/extensions/hpricot/elem.rb +3 -3
  24. data/lib/extensions/string.rb +2 -18
  25. data/lib/rtml.rb +0 -12
  26. data/lib/rtml/assigns.rb +32 -0
  27. data/lib/rtml/controller/document_generator.rb +9 -0
  28. data/lib/rtml/controller/render_helpers.rb +42 -18
  29. data/lib/rtml/controller/state.rb +2 -1
  30. data/lib/rtml/dependencies.rb +20 -15
  31. data/lib/rtml/dsl.rb +10 -10
  32. data/lib/rtml/environment.rb +13 -1
  33. data/lib/rtml/errors/application_error.rb +5 -0
  34. data/lib/rtml/errors/simulation_error.rb +4 -0
  35. data/lib/rtml/errors/variable_error.rb +5 -0
  36. data/lib/rtml/high_level/variable_manager.rb +11 -7
  37. data/lib/rtml/inherited_instance_variables.rb +8 -1
  38. data/lib/rtml/links.rb +17 -0
  39. data/lib/rtml/rules/dom_validation.rb +1 -0
  40. data/lib/rtml/test/builtin_variables.rb +33 -0
  41. data/lib/rtml/test/resemblance_test.rb +97 -0
  42. data/lib/rtml/test/screen.rb +126 -0
  43. data/lib/rtml/test/simulator.rb +240 -0
  44. data/lib/rtml/test/simulator_post_processors/base.rb +7 -0
  45. data/lib/rtml/test/simulator_post_processors/card_parsers.rb +32 -0
  46. data/lib/rtml/test/simulator_post_processors/receipt.rb +15 -0
  47. data/lib/rtml/test/simulator_post_processors/submit.rb +15 -0
  48. data/lib/rtml/test/spec.rb +14 -7
  49. data/lib/rtml/test/spec/matchers.rb +24 -0
  50. data/lib/rtml/test/tml_application.rb +331 -0
  51. data/lib/rtml/test/unit.rb +13 -0
  52. data/lib/rtml/test/variable_scope.rb +146 -0
  53. data/lib/rtml/version.rb +1 -1
  54. data/lib/rtml/widget.rb +26 -14
  55. data/lib/rtml/widget_core/class_methods.rb +8 -4
  56. data/lib/rtml/widget_core/widget_accessor_instance_methods.rb +6 -6
  57. data/lib/rtml/widgets.rb +22 -3
  58. data/lib/rtml_routes.rb +1 -1
  59. data/rails_generators/rtml/rtml_generator.rb +3 -0
  60. data/rails_generators/rtml/templates/db/migrate/20100513165226_add_options_to_rtml_documents.rb +9 -0
  61. data/rails_generators/rtml/templates/db/migrate/20100513165242_remove_dom_elements_mirror.rb +16 -0
  62. data/rails_generators/rtml/templates/db/migrate/20100513165249_remove_dom_properties_mirror.rb +16 -0
  63. data/rails_generators/rtml/templates/lib/tasks/rtml.rake +1 -1
  64. data/rtml.gemspec +65 -0
  65. data/spec/controllers/rtml_controller_spec.rb +1 -1
  66. data/spec/integration/post_tests_spec.rb +8 -0
  67. data/spec/lib/rtml/high_level/variable_manager_spec.rb +8 -0
  68. data/spec/lib/rtml/routes_spec.rb +23 -22
  69. data/spec/lib/rtml/test/simulator/receipt_spec.rb +18 -0
  70. data/spec/lib/rtml/test/simulator_spec.rb +185 -0
  71. data/spec/lib/rtml/test/tml_application_spec.rb +119 -0
  72. data/spec/lib/rtml/test/variable_scope_spec.rb +65 -0
  73. data/spec/lib/rtml/widget_spec.rb +1 -0
  74. data/spec/lib/rtml/widgets_spec.rb +30 -0
  75. data/spec/models/rtml/document_spec.rb +8 -0
  76. data/spec/models/rtml/dom/screen_element_spec.rb +15 -0
  77. data/spec/models/rtml/instruction_spec.rb +2 -2
  78. data/spec/rtml_action_spec.rb +25 -0
  79. data/spec/spec_helper.rb +31 -1
  80. data/spec/support/app/controllers/post_tests_controller.rb +11 -0
  81. data/spec/support/app/views/inherited/instance_variables_test/display.rtml.erb +1 -0
  82. data/spec/support/config/boot.rb +1 -0
  83. data/spec/support/config/routes.rb +3 -2
  84. data/spec/support/db/rtml_test_db.sqlite3 +0 -0
  85. data/spec/support/raw_tml/avs.tml +27 -0
  86. data/spec/support/raw_tml/document_level_events.tml +18 -0
  87. data/spec/support/raw_tml/empty_screen.tml +15 -0
  88. data/spec/support/raw_tml/enter_amount.tml +40 -0
  89. data/spec/support/raw_tml/foreign_receiver.tml +10 -0
  90. data/spec/support/raw_tml/foreign_reference.tml +10 -0
  91. data/spec/support/raw_tml/hello_world.tml +13 -0
  92. data/spec/support/raw_tml/loop_x_times.tml +39 -0
  93. data/spec/support/raw_tml/one_screen_with_setvar.tml +8 -0
  94. data/spec/support/raw_tml/receipt.tml +15 -0
  95. data/spec/support/raw_tml/simulator.tml +122 -0
  96. data/spec/support/raw_tml/tmlvar_reference.tml +34 -0
  97. data/spec/support/raw_tml/user_input.tml +47 -0
  98. data/spec/support/raw_tml/valid_document.tml +6 -0
  99. data/spec/support/rspec/example_groups.rb +1 -1
  100. data/spec/support/rspec/matchers.rb +0 -11
  101. data/spec/widgets/document_variable_processing_spec.rb +25 -39
  102. data/spec/widgets/element_builder_spec.rb +4 -0
  103. data/spec/widgets/event_listener_spec.rb +9 -0
  104. data/spec/widgets/highlevel_variable_processing_spec.rb +27 -2
  105. data/spec/widgets/screen_variable_processing_spec.rb +34 -0
  106. data/spec/widgets/screens_spec.rb +22 -0
  107. data/spec/widgets/simulator_post_processors/card_parsers_spec.rb +70 -0
  108. data/spec/widgets/simulator_post_processors/submit_spec.rb +44 -0
  109. data/tasks/stats.rake +10 -0
  110. data/test/test_rtml_generator.rb +3 -0
  111. metadata +55 -49
  112. data/builtin/widgets/subroutine.rb +0 -54
  113. data/lib/rtml/high_level/subroutine.rb +0 -22
  114. data/lib/rtml/reverse_engineering/crawler.rb +0 -58
  115. data/lib/rtml/reverse_engineering/simulator.rb +0 -269
  116. data/lib/rtml/reverse_engineering/simulator/casting.rb +0 -9
  117. data/lib/rtml/reverse_engineering/simulator/snapshot.rb +0 -18
  118. data/lib/rtml/reverse_engineering/simulator/variable_lookup.rb +0 -32
  119. data/lib/rtml/reverse_engineering/simulator/variable_value.rb +0 -105
  120. data/spec/lib/rtml/reverse_engineering/crawler_spec.rb +0 -24
  121. data/spec/lib/rtml/reverse_engineering/simulator/variable_value_spec.rb +0 -120
  122. data/spec/lib/rtml/reverse_engineering/simulator_spec.rb +0 -96
  123. data/spec/support/config/tml_dom_ruleset.rb +0 -82
  124. data/spec/widgets/subroutine_spec.rb +0 -109
@@ -1,22 +0,0 @@
1
- class Rtml::HighLevel::Subroutine
2
- def initialize(name, parent, options = {}, &blk)
3
- @block = blk
4
- @name = name
5
- @parent = parent
6
- end
7
-
8
- def call(context, args)
9
- method_name = @name
10
- block = @block
11
-
12
- (class << context; self; end).instance_eval do
13
- define_method method_name, &block
14
- new_method = instance_method(method_name)
15
- define_method method_name do |arguments|
16
- new_method.bind(self).call(arguments)
17
- end
18
- end
19
-
20
- context.send(method_name, args)
21
- end
22
- end
@@ -1,58 +0,0 @@
1
- class Rtml::ReverseEngineering::Crawler
2
- attr_reader :simulator, :session
3
- delegate :tml, :route, :set, :to => :simulator
4
- delegate :response, :request, :controller, :get, :post, :put, :delete, :to => :session
5
-
6
- def tml=(tml)
7
- @path = nil
8
- simulator.tml = tml
9
- end
10
-
11
- def initialize(options = {})
12
- @simulator = Rtml::ReverseEngineering::Simulator.new(options.delete(:tml))
13
- @session = ActionController::Integration::Session.new
14
- end
15
-
16
- def visit(path, screen = nil)
17
- path, screen = split_path_and_screen_name_out_of(path) unless screen
18
- unless path.blank? or path == @path
19
- get @path = path
20
- format = response.template.template_format
21
- if format == :rtml then simulator.tml = response.body
22
- else raise Rtml::Errors::InvalidFormat, "Response must be in :rtml format; found #{format.inspect}:\n#{response.body}"
23
- end
24
- end
25
-
26
- @simulator.visit screen
27
- end
28
-
29
- def split_path_and_screen_name_out_of(path)
30
- path, screen = path.split(/#/)
31
- if screen =~ /\?/
32
- screen, query = screen.split(/\?/)
33
- path = "#{path}?#{query}"
34
- end
35
- [path, screen]
36
- end
37
-
38
- def current_screen
39
- route.last
40
- end
41
-
42
- private
43
- # Since we're using ActionController::Integration::Session instead of including ActionController::TestProcess,
44
- # this method isn't used any more and should really be removed.
45
- def setup_controller_request_and_response
46
- # How to get this info into a TestResult so we don't have to create 2 requests?! TestResult seems to always override
47
- # the env with Rack::MockRequest.env_for("/"), even though it looks like it SHOULD be merging the two. Bug?
48
- temp_request = ActionController::Request.new(Rack::MockRequest.env_for(@path))
49
- @path_params = ActionController::Routing::Routes.recognize_path(temp_request.path)
50
- @request = ActionController::TestRequest.new
51
- @request.remote_addr = '123.231.12.3'
52
- @response = ActionController::TestResponse.new
53
- @controller = @path_params[:controller].to_controller.new
54
- @controller.request = @request
55
- @controller.params = temp_request.parameters
56
- @controller.send(:initialize_current_url)
57
- end
58
- end
@@ -1,269 +0,0 @@
1
- class Rtml::ReverseEngineering::Simulator
2
- include Rtml::Rules::DomValidation
3
- include Rtml::ReverseEngineering::Simulator::VariableLookup
4
- attr_reader :tml
5
-
6
- def initialize(tml = nil)
7
- self.tml = tml if tml
8
- end
9
-
10
- def set(values)
11
- variables.merge!(values.with_indifferent_access)
12
- end
13
-
14
- def route
15
- @stack.last ? @stack.last[4] : []
16
- end
17
-
18
- # Visits the specified screen. Evaluates variable assignments and follows TML logical flow, but does not interact
19
- # with hotkeys, hyperlinks, etc.
20
- #
21
- # Timeouts are not evaluated because they *usually* indicate that some interaction is required by the user.
22
- #
23
- # The return value is the last screen to be visited with the above exceptions. Circular paths return the last
24
- # screen in the path before it would loop over; external references return the URL of the reference.
25
- def visit(screen_name = nil)
26
- raise Rtml::Errors::ScreenNotFound, "No screens found to visit in RTML document!" unless first_screen
27
- screen_name = first_screen['id'] if screen_name.blank?
28
- response = analyze screen_name, :ignore_pathlist => true, :interaction => :none
29
- response.kind_of?(Symbol) ? route.last || response : response
30
- end
31
-
32
- # Analyzes the specified path (which can be one or more symbols and/or strings) and verifies
33
- # that screens with the specified IDs are followed, in the specified order. If any screen is encountered
34
- # which is NOT in the list, no error is raised as long as the next screen in the list is visited at some point.
35
- # Returns one of the following values:
36
- #
37
- # :completion - each screen was visited in the specified order.
38
- # :assertion - a TML assertion error was raised in the form of the "assert" screen being visited.
39
- # :no_route - The TML pathway did not result in the specified screens being visited in the expected order.
40
- # :dead_end - The TML pathway ended abruptly, and did not visit all of the specified screens before doing so.
41
- # :circular - The TML pathway was a circular route which never touched one or more nodes.
42
- # :error - Processing ended abruptly, for an unknown reason.
43
- # a String - The TML pathway encountered a link to an external document, and not an anchor. The String returned
44
- # is the reference to the external document.
45
- #
46
- # This method also builds the #route up, so that you can analyze the order in which screens were visited.
47
- #
48
- def analyze(*path)
49
- raise Rtml::Errors::ScreenNotFound, "No screens found to visit in RTML document!" unless first_screen
50
- options = path.extract_options!
51
- path.flatten!
52
- raise ArgumentError, "Expected at least one screen ID as an argument" unless path.length > 0
53
- @ignore_pathlist = path.empty? || options.delete(:ignore_pathlist)
54
- @interaction = options.delete(:interaction) || options.delete(:interactions) || :all
55
- @recursion_limit = options.delete(:recursion_limit) || options.delete(:max_depth) || 150
56
-
57
- r = catch(:completion) do
58
- current = path.shift || first_screen['id']
59
- #route << current
60
- @pathlist = path
61
- catch(:abort) do
62
- trace(find_screen(current))
63
- :error
64
- end
65
- end
66
- r
67
- end
68
-
69
- def tml=(value)
70
- reset!
71
- case value
72
- when String then @tml = Hpricot::XML(value).root
73
- when Hpricot::Doc then @tml = value.root
74
- when Hpricot::Elem then @tml = value
75
- else raise ArgumentError, "Expected TML to be a String, Hpricot::Doc or Hpricot::Elem"
76
- end
77
-
78
- parse_tml
79
- end
80
-
81
- def find_screen(id)
82
- rt = screens.select { |scr| scr['id'] == id.to_s }
83
- raise Rtml::Errors::ScreenNotFound, "Could not find screen with ID '#{id}'" if rt.empty?
84
- scr = rt.shift
85
- raise Rtml::Errors::ScreenNotFound, "Multiple ambiguous screens found with ID '#{id}'" unless rt.empty?
86
- scr
87
- end
88
-
89
- private
90
- # Returns all interactions that the simulator is allowed to perform in the form of an array.
91
- def interactions
92
- if @interaction.kind_of?(Symbol)
93
- case @interaction
94
- when :all then [:all, :key, :timeout, :href]
95
- when :none then []
96
- when :key, :timeout, :href then [@interaction]
97
- end
98
- else
99
- @interaction
100
- end
101
- end
102
-
103
-
104
- def trace(screen)
105
- route << screen['id']
106
- take_snapshot(screen)
107
- unless @ignore_pathlist
108
- @pathlist.shift if screen['id'] == @pathlist.first.to_s
109
- throw :completion, :completion if @pathlist.empty?
110
- end
111
-
112
- process_variable_assignments(screen)
113
-
114
- candidates = href_candidates(screen)
115
- throw :completion, :completion unless candidates.empty? || interactions.include?(:href)
116
-
117
- candidates.concat next_screen_for(screen)
118
- throw :abort, :dead_end if candidates.empty?
119
-
120
- abort_with = nil
121
- anchors_for(candidates).each do |next_screen|
122
- if next_screen.kind_of?(String)# == :external
123
- throw :abort, next_screen # :external
124
- elsif next_screen['id'] == 'assert'
125
- throw :abort, :assertion
126
- else
127
- push_stack
128
- abort_with = catch(:abort) do
129
- trace(next_screen)
130
- nil
131
- end
132
- throw :abort, abort_with if @ignore_pathlist
133
- pop_stack if abort_with
134
- end
135
- end
136
- throw :abort, abort_with if abort_with
137
- end
138
-
139
- # Finds the various +setvar+ elements in the specified screen and updates the simulator's variable states to match.
140
- def process_variable_assignments(screen)
141
- setvars = screen / "setvar"
142
- setvars.each do |setvar|
143
- variables[setvar['name']] = Rtml::ReverseEngineering::Simulator::VariableValue.new(setvar, variables, vardecs).value
144
- end
145
- end
146
-
147
- # Finds all hyperlinks within the given screen and returns them.
148
- #
149
- def href_candidates(screen)
150
- (screen / "a").select { |elem| elem['href'] }.collect { |elem| elem['href'] }
151
- end
152
-
153
- # Takes a "snapshot" of the state of the simulator, with the given screen as the index.
154
- # This is used to track visited screens; it is a safe assumption that if all variables are identical
155
- # to a given snapshot with the given screen, then this will result in a circular TML path.
156
- # This method will throw :abort, :circular if this condition is true.
157
- def take_snapshot(screen)
158
- snapshot = Rtml::ReverseEngineering::Simulator::Snapshot.new(screen, variables)
159
- if snapshots.length > @recursion_limit
160
- raise Rtml::Errors::ProcessingError, "Recursion error: Probable infinite loop (or :recursion_limit is too low)"
161
- end
162
- if snapshots.include?(snapshot)
163
- throw :abort, :circular
164
- end
165
- snapshots << snapshot
166
- end
167
-
168
- # returns the list of possible screenflow branches following the specified screen.
169
- def next_screen_for(screen)
170
- candidates = screen['next'] ? [screen['next']] : []
171
- next_element = (screen / "next").shift
172
- if next_element
173
- candidates = find_adequate_variants(next_element) + [next_element['uri']]
174
- end
175
-
176
- candidates
177
- end
178
-
179
- # Returns array containing the screen element for each string in the array, or the original string if it is not found.
180
- def anchors_for(candidates)
181
- candidates.collect do |screen_id|
182
- if screen_id =~ /^#/
183
- find_screen(screen_id[1..-1]) # to drop the '#'
184
- else
185
- screen_id
186
- #:external
187
- end
188
- end
189
- end
190
-
191
- # Returns the first variant in the Hpricot::Elem which matches the current variable values, or nil if none match.
192
- def find_adequate_variants(elem)
193
- variants = elem / "variant"
194
-
195
- candidates = []
196
- variants.each do |variant|
197
- uri, lo, op, ro, key, timeout =
198
- variant['uri'], variant['lo'], variant['op'] || "equal", variant['ro'], variant['key'], variant['timeout']
199
- %w(uri lo op ro key timeout).each { |lv| eval("#{lv} = resolve(#{lv}, variables, vardecs)") }
200
- if (interactions.include?(:key) && key) || (interactions.include?(:timeout) && timeout)
201
- candidates << uri
202
- elsif key || timeout # if we're not interacting with them, pause to allow user interaction
203
- throw :completion, :completion
204
- else
205
- # only follow if it's a match
206
- ro = lo.kind_of?(Fixnum) ? ro.to_i : ro.to_s
207
-
208
- match = case op
209
- when 'equal' then lo == ro
210
- when 'not_equal' then lo != ro
211
- when 'less' then lo < ro
212
- when 'less_or_equal' then lo <= ro
213
- when 'contains' then lo.to_s[ro.to_s]
214
- else raise Rtml::InvalidOperation, "Invalid operation: #{op}"
215
- end
216
- candidates << uri and break if match
217
- end
218
- end
219
-
220
- candidates #candidates.empty? ? nil : candidates
221
- end
222
-
223
- def first_screen
224
- (tml / "screen").first
225
- end
226
-
227
- # resets the various tracking variables
228
- def reset!
229
- @stack = [ ]
230
- #@stack.clear
231
- push_stack
232
- end
233
-
234
- def push_stack
235
- @stack.push [ screens.dup, snapshots.dup, variables.dup, vardecs.dup, route.dup ]
236
- end
237
-
238
- def pop_stack
239
- @stack.pop
240
- end
241
-
242
- def screens
243
- @stack.last ? @stack.last[0] : []
244
- end
245
-
246
- def snapshots
247
- @stack.last ? @stack.last[1] : []
248
- end
249
-
250
- def variables
251
- @stack.last ? @stack.last[2] : {}
252
- end
253
-
254
- def vardecs
255
- @stack.last ? @stack.last[3] : {}
256
- end
257
-
258
- # Validates the given TML, and then finds the +screen+ elements and tracks them.
259
- def parse_tml
260
- validate_tml(@tml)
261
- (@tml / "vardcl").each do |elem|
262
- vardecs[elem['name']] = elem['type'].to_sym
263
- end
264
-
265
- (@tml / "screen").each do |elem|
266
- screens << elem
267
- end
268
- end
269
- end
@@ -1,9 +0,0 @@
1
- module Rtml::ReverseEngineering::Simulator::Casting
2
- # Returns an instance of +Rtml::ReverseEngineering::Simulator+ with self as the initialization argument.
3
- def to_tml_simulator
4
- Rtml::ReverseEngineering::Simulator.new(self)
5
- end
6
-
7
- alias to_rtml_simulator to_tml_simulator
8
- alias to_simulator to_tml_simulator
9
- end
@@ -1,18 +0,0 @@
1
- # This class is used to contain a single +screen+ element (an instance of Hpricot::Elem) as well as any variable
2
- # assignments that have been made. Basically, it is simply used by +Rtml::ReverseEngineering::Simulator+ to check
3
- # for circular pathways. If two identical snapshots would be created, then it stands to reason that the resultant
4
- # pathways for those two snapshots will also be identical. If you're following a path and creating snapshots at every
5
- # step of the way, and two snapshots match, then using that logic, those two snapshots would indicate a circular
6
- # pathway. (It comes down to either that, or waiting to catch a stack overflow error, which is less than optimal.)
7
- #
8
- class Rtml::ReverseEngineering::Simulator::Snapshot
9
- attr_reader :screen, :variables
10
-
11
- def initialize(screen, variables)
12
- @screen, @variables = screen, variables.dup
13
- end
14
-
15
- def ==(other_snapshot)
16
- @screen == other_snapshot.screen && variables == other_snapshot.variables
17
- end
18
- end
@@ -1,32 +0,0 @@
1
- module Rtml::ReverseEngineering::Simulator::VariableLookup
2
- # Checks a value to see if it references a TML variable; if so, it resolves that variable into its value;
3
- # otherwise, the original value is returned.
4
- def resolve(value, all_variables, known_variable_types)
5
- if value =~ /^tmlvar:(.*)$/
6
- lookup_variable_value($~[1], all_variables, known_variable_types)
7
- else
8
- value
9
- end
10
- end
11
-
12
- # Finds the variable in the all_variables hash whose name matches the specified name (string or symbol), and then
13
- # returns its value. If that value is an instance of +Rtml::ReverseEngineering::Simulator::VariableValue+, calls
14
- # the #value method on that object instead.
15
- #
16
- # If the variable is not found, then a default value will be returned depending on its type.
17
- def lookup_variable_value(name, all_variables, known_variable_types)
18
- all_variables.each do |var_name, varval|
19
- if var_name.to_s == name
20
- return(varval.kind_of?(Rtml::ReverseEngineering::Simulator::VariableValue) ? varval.value : varval)
21
- end
22
- end
23
- default_value_for_type(known_variable_types[name])
24
- end
25
-
26
- def default_value_for_type(type)
27
- case type
28
- when :integer then 1
29
- else "generic"
30
- end
31
- end
32
- end
@@ -1,105 +0,0 @@
1
- # Used by +Rtml::ReverseEngineering::Simulator+ to track the value of a variable. Attempts
2
- # to build a logical value by processing the setvar's :lo, :op and :ro attributes. References to
3
- # other variables are automatically resolved. References to nonexistent values assume built-in variables
4
- # and assign generic values to them.
5
- #
6
- # Implements a #== method to see if one variable's value is equal to another's, though this may not always be accurate.
7
- #
8
- class Rtml::ReverseEngineering::Simulator::VariableValue
9
- include Rtml::ReverseEngineering::Simulator::VariableLookup
10
- attr_reader :value
11
-
12
- def initialize(setvar, all_variables, known_variable_types)
13
- @setvar, @all_variables, @known_variable_types = setvar, all_variables, known_variable_types
14
- @value = setvar.to_s
15
-
16
- %w(lo op ro).each { |i| instance_variable_set("@#{i}", resolve(self.send(i), all_variables, known_variable_types)) }
17
- catch(:done) do
18
- check_for_assignment
19
- check_for_operation
20
- end
21
-
22
- do_casting
23
- check_boundaries
24
- end
25
-
26
- def ==(other)
27
- self.value == other.value
28
- end
29
-
30
- private
31
- attr_reader :setvar, :all_variables, :known_variable_types
32
-
33
- def type
34
- @type ||= known_variable_types[setvar['name']]
35
- end
36
-
37
- def check_boundaries
38
- case type
39
- when :integer
40
- unless (-999999999..999999999).include? @value
41
- raise RulesViolationError, "TML numbers must be no more than 9 digits in length: #{@value}"
42
- end
43
- end
44
- end
45
-
46
- def do_casting
47
- @value = case type
48
- when :integer then value.to_i
49
- else value.to_s
50
- end
51
- end
52
-
53
- def check_for_operation
54
- unless setvar['op'].blank?
55
- method_name = "perform_operation_#{setvar['op']}"
56
- @value = self.send(method_name)
57
- throw :done
58
- end
59
- rescue NoMethodError
60
- raise Rtml::Error::InvalidOperation
61
- end
62
-
63
- def check_for_assignment
64
- if setvar['op'].blank? && !setvar['lo'].blank?
65
- @value = setvar['lo']
66
- throw :done
67
- end
68
- end
69
-
70
- def lo; @lo ||= setvar['lo']; end
71
- def op; @op ||= setvar['op']; end
72
- def ro; @ro ||= setvar['ro']; end
73
-
74
- def perform_operation_plus
75
- case type
76
- when :integer then lo.to_i + ro.to_i
77
- when :string then lo.to_s + ro.to_s
78
- when :date then "#{lo.to_s} + #{ro.to_s}"
79
- else raise Rtml::Errors::InvalidOperation
80
- end
81
- end
82
-
83
- def perform_operation_minus
84
- case type
85
- when :integer then lo.to_i - ro.to_i
86
- when :string then lo.to_s[0..-(ro.to_i+1)]
87
- when :date then "#{lo.to_s} - #{ro.to_s}"
88
- else raise Rtml::Errors::InvalidOperation
89
- end
90
- end
91
-
92
- def perform_operation_number
93
- case type
94
- when :integer then lo.split(/;/).length
95
- else raise Rtml::Errors::InvalidOperation
96
- end
97
- end
98
-
99
- def perform_operation_item
100
- case type
101
- when :string then lo.split(/;/)[ro.to_i]
102
- else raise Rtml::Errors::InvalidOperation
103
- end
104
- end
105
- end