AmberVM 0.0.19
Sign up to get free protection for your applications and to get access to all the features.
- data/README +38 -0
- data/bin/ambervm +278 -0
- data/lib/amber/acts_as_rvm_type.rb +157 -0
- data/lib/amber/classes/association.rb +36 -0
- data/lib/amber/classes/block.rb +52 -0
- data/lib/amber/classes/boolean.rb +40 -0
- data/lib/amber/classes/class.rb +50 -0
- data/lib/amber/classes/error.rb +22 -0
- data/lib/amber/classes/list.rb +96 -0
- data/lib/amber/classes/null.rb +35 -0
- data/lib/amber/classes/number.rb +95 -0
- data/lib/amber/classes/object.rb +56 -0
- data/lib/amber/classes/string.rb +79 -0
- data/lib/amber/classes.rb +113 -0
- data/lib/amber/environment.rb +251 -0
- data/lib/amber/fukubukuro/ecma_core.rb +409 -0
- data/lib/amber/fukubukuro.rb +866 -0
- data/lib/amber/functions/all.rb +3 -0
- data/lib/amber/functions/array/append.rb +50 -0
- data/lib/amber/functions/array/at.rb +50 -0
- data/lib/amber/functions/array/set_at.rb +50 -0
- data/lib/amber/functions/array.rb +30 -0
- data/lib/amber/functions/association/assoc_get.rb +55 -0
- data/lib/amber/functions/association/assoc_set.rb +56 -0
- data/lib/amber/functions/bitwise/bitwise_and.rb +41 -0
- data/lib/amber/functions/bitwise/bitwise_not.rb +41 -0
- data/lib/amber/functions/bitwise/bitwise_or.rb +41 -0
- data/lib/amber/functions/bitwise/bitwise_xor.rb +41 -0
- data/lib/amber/functions/bitwise.rb +3 -0
- data/lib/amber/functions/collection/get.rb +66 -0
- data/lib/amber/functions/collection/set.rb +67 -0
- data/lib/amber/functions/collection/size.rb +54 -0
- data/lib/amber/functions/general/cmp.rb +43 -0
- data/lib/amber/functions/general/eq.rb +45 -0
- data/lib/amber/functions/general/gt.rb +45 -0
- data/lib/amber/functions/general/gte.rb +45 -0
- data/lib/amber/functions/general/lt.rb +45 -0
- data/lib/amber/functions/general/lte.rb +45 -0
- data/lib/amber/functions/general/neq.rb +45 -0
- data/lib/amber/functions/general/type.rb +43 -0
- data/lib/amber/functions/general.rb +3 -0
- data/lib/amber/functions/io/print.rb +45 -0
- data/lib/amber/functions/io.rb +3 -0
- data/lib/amber/functions/list/align.rb +73 -0
- data/lib/amber/functions/list/join.rb +45 -0
- data/lib/amber/functions/list/map.rb +58 -0
- data/lib/amber/functions/list/split.rb +55 -0
- data/lib/amber/functions/list.rb +3 -0
- data/lib/amber/functions/logic/and.rb +55 -0
- data/lib/amber/functions/logic/not.rb +40 -0
- data/lib/amber/functions/logic/or.rb +50 -0
- data/lib/amber/functions/logic.rb +3 -0
- data/lib/amber/functions/math/abs.rb +39 -0
- data/lib/amber/functions/math/acos.rb +39 -0
- data/lib/amber/functions/math/add.rb +40 -0
- data/lib/amber/functions/math/asin.rb +39 -0
- data/lib/amber/functions/math/atan.rb +39 -0
- data/lib/amber/functions/math/ceil.rb +39 -0
- data/lib/amber/functions/math/cos.rb +39 -0
- data/lib/amber/functions/math/dec.rb +39 -0
- data/lib/amber/functions/math/div.rb +44 -0
- data/lib/amber/functions/math/exp.rb +39 -0
- data/lib/amber/functions/math/floor.rb +39 -0
- data/lib/amber/functions/math/inc.rb +39 -0
- data/lib/amber/functions/math/log.rb +39 -0
- data/lib/amber/functions/math/mod.rb +41 -0
- data/lib/amber/functions/math/mul.rb +43 -0
- data/lib/amber/functions/math/neg.rb +43 -0
- data/lib/amber/functions/math/power.rb +43 -0
- data/lib/amber/functions/math/rand.rb +36 -0
- data/lib/amber/functions/math/round.rb +39 -0
- data/lib/amber/functions/math/shl.rb +41 -0
- data/lib/amber/functions/math/shr.rb +41 -0
- data/lib/amber/functions/math/sin.rb +39 -0
- data/lib/amber/functions/math/sub.rb +43 -0
- data/lib/amber/functions/math/tan.rb +39 -0
- data/lib/amber/functions/math.rb +3 -0
- data/lib/amber/functions/objects/send.rb +22 -0
- data/lib/amber/functions/rails/print.rb +44 -0
- data/lib/amber/functions/rails.rb +3 -0
- data/lib/amber/functions/string/ansi.rb +24 -0
- data/lib/amber/functions/string/capstr.rb +23 -0
- data/lib/amber/functions/string/center.rb +25 -0
- data/lib/amber/functions/string/chr.rb +16 -0
- data/lib/amber/functions/string/ljust.rb +26 -0
- data/lib/amber/functions/string/regmatch.rb +34 -0
- data/lib/amber/functions/string/rjust.rb +26 -0
- data/lib/amber/functions/string.rb +3 -0
- data/lib/amber/functions.rb +103 -0
- data/lib/amber/interpreter.rb +1380 -0
- data/lib/amber/languages/brainfuck.rb +153 -0
- data/lib/amber/languages/ecma/compiler.rb +1661 -0
- data/lib/amber/languages/ecma/core-math.js +67 -0
- data/lib/amber/languages/ecma/core-objects.js +57 -0
- data/lib/amber/languages/ecma.rb +9 -0
- data/lib/amber/languages/ecma_fuku/compiler.rb +1622 -0
- data/lib/amber/languages/ecma_fuku/core-math.js +67 -0
- data/lib/amber/languages/ecma_fuku/core-objects.js +56 -0
- data/lib/amber/languages/ecma_fuku.rb +13 -0
- data/lib/amber/languages/math/compiler.rb +70 -0
- data/lib/amber/languages/math/tokenizer.rb +69 -0
- data/lib/amber/languages/math/tree.rb +110 -0
- data/lib/amber/languages/math.rb +26 -0
- data/lib/amber/languages.rb +99 -0
- data/lib/amber/library.rb +79 -0
- data/lib/amber/optimisation.rb +299 -0
- data/lib/amber/plugin.rb +337 -0
- data/lib/amber/rails.rb +90 -0
- data/lib/amber.rb +106 -0
- data/spec/amber/class_spec.rb +27 -0
- data/spec/amber/enviroment_spec.rb +61 -0
- data/spec/amber/function_spec.rb +25 -0
- data/spec/amber/functions/association/assoc_get_spec.rb +41 -0
- data/spec/amber/functions/association/assoc_set_spec.rb +43 -0
- data/spec/amber/functions/collection/get_spec.rb +12 -0
- data/spec/amber/functions/collection/set_spec.rb +10 -0
- data/spec/amber/functions/collection/size_spec.rb +10 -0
- data/spec/amber/functions/list/split_spec.rb +47 -0
- data/spec/amber/functions/string/ansi_spec.rb +44 -0
- data/spec/amber/functions/string/capstr_spec.rb +42 -0
- data/spec/amber/functions/string/center_spec.rb +49 -0
- data/spec/amber/functions/string/ljust_spec.rb +49 -0
- data/spec/amber/functions/string/regmatch_spec.rb +52 -0
- data/spec/amber/functions/string/rjust_spec.rb +49 -0
- data/spec/amber/interpreter/assignment_spec.rb +22 -0
- data/spec/amber/interpreter/condition_spec.rb +103 -0
- data/spec/amber/interpreter/constant_spec.rb +31 -0
- data/spec/amber/interpreter/core_call_spec.rb +72 -0
- data/spec/amber/interpreter/interpreter_spec.rb +11 -0
- data/spec/amber/interpreter/parameter_spec.rb +24 -0
- data/spec/amber/interpreter/sequence_spec.rb +47 -0
- data/spec/amber/interpreter/variable_spec.rb +24 -0
- data/spec/amber/plugin_spec.rb +10 -0
- data/spec/classes/atom/association_spec.rb +39 -0
- data/spec/classes/atom/block_spec.rb +25 -0
- data/spec/classes/atom/boolean_spec.rb +67 -0
- data/spec/classes/atom/error_spec.rb +43 -0
- data/spec/classes/atom/list_spec.rb +68 -0
- data/spec/classes/atom/number_spec.rb +132 -0
- data/spec/classes/atom/string_spec.rb +175 -0
- data/spec/languages/ecma/ecma_array_spec.rb +79 -0
- data/spec/languages/ecma/ecma_closure_spec.rb +38 -0
- data/spec/languages/ecma/ecma_literals_spec.rb +71 -0
- data/spec/languages/ecma/ecma_objects_spec.rb +165 -0
- data/spec/languages/ecma/ecma_old_spec.rb +540 -0
- data/spec/languages/ecma/ecma_spec.rb +64 -0
- data/spec/languages/ecma_fuku/ecma_array_spec.rb +61 -0
- data/spec/languages/ecma_fuku/ecma_closure_spec.rb +33 -0
- data/spec/languages/ecma_fuku/ecma_function_spec.rb +84 -0
- data/spec/languages/ecma_fuku/ecma_literals_spec.rb +55 -0
- data/spec/languages/ecma_fuku/ecma_objects_spec.rb +133 -0
- data/spec/languages/ecma_fuku/ecma_old_spec.rb +415 -0
- data/spec/languages/ecma_fuku/ecma_operator_spec.rb +33 -0
- data/spec/languages/ecma_fuku/ecma_spec.rb +52 -0
- data/spec/languages/math/compiler_spec.rb +49 -0
- data/spec/languages/math/tokenizer_spec.rb +73 -0
- data/spec/languages/math/tree_spec.rb +153 -0
- metadata +225 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
module AmberVM
|
2
|
+
module Interpreter
|
3
|
+
|
4
|
+
# Version of the AmberVM::Interpreter::Variable that handles static variable names
|
5
|
+
class SimpleVariable < Variable
|
6
|
+
# A SimpleVariable is initialized wiht 1 to 2 parameters.
|
7
|
+
#
|
8
|
+
# name:: The name of the variable to get, it will be executed as long as
|
9
|
+
# it is no Sybol in which case it is treated as a special variable.
|
10
|
+
#
|
11
|
+
# pos:: The position within the source code of the definition - for
|
12
|
+
# deugging purpose.
|
13
|
+
def initialize name, pos = ['-', '-', '-'], require_declaration = false
|
14
|
+
super(name.to_s,pos, require_declaration)
|
15
|
+
end
|
16
|
+
|
17
|
+
# The name is a sting and we simple get what is written in the env
|
18
|
+
def execute env
|
19
|
+
AmberVM::debug "Executing SimpleVariable at #{@pos}..." if $DEBUG
|
20
|
+
begin
|
21
|
+
r = env[@name]
|
22
|
+
if not r
|
23
|
+
if @require_declaration
|
24
|
+
raise RuntimeError.new("Variable #{@name} was not declared.")
|
25
|
+
else
|
26
|
+
r = env.declare(@name, AmberVM::Classes::Null.new(nil))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@type = r.val.data_type if r.val.respond_to?(:data_type)
|
30
|
+
r
|
31
|
+
rescue Exception => e
|
32
|
+
raise RuntimeError.new("Failed to get Varialbe #{e}", @pos[0], @pos[1], @pos[2])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def optimize variables = {}
|
37
|
+
variables[@name] ||= VariableStorage.new(AmberVM::Classes[:null].new(nil))
|
38
|
+
StaticVariable.new(variables[@name], pos, @require_declaration)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Version of the AmberVM::Interpreter::Variable that handles static variable names
|
43
|
+
class StaticVariable < Variable
|
44
|
+
# A SimpleVariable is initialized wiht 1 to 2 parameters.
|
45
|
+
#
|
46
|
+
# name:: The name of the variable to get, it will be executed as long as
|
47
|
+
# it is no Sybol in which case it is treated as a special variable.
|
48
|
+
#
|
49
|
+
# pos:: The position within the source code of the definition - for
|
50
|
+
# deugging purpose.
|
51
|
+
def initialize var, pos = ['-', '-', '-'], require_declaration = false
|
52
|
+
var = AmberVM::Interpreter::makevs(var)
|
53
|
+
super(var, pos, require_declaration)
|
54
|
+
end
|
55
|
+
|
56
|
+
def value
|
57
|
+
@name
|
58
|
+
end
|
59
|
+
# The name is a sting and we simple get what is written in the env
|
60
|
+
def execute env
|
61
|
+
AmberVM::debug "Executing Static at #{@pos}..." if $DEBUG
|
62
|
+
@name
|
63
|
+
end
|
64
|
+
|
65
|
+
def optimize variables = {}
|
66
|
+
StaticVariable.new(@name, @pos, @require_declaration)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# class SimpleFunctionDefinition < FunctionDefinition
|
71
|
+
#
|
72
|
+
# # Initializes a new function definition
|
73
|
+
# #
|
74
|
+
# # name:: is the name of the function to define, if it is not
|
75
|
+
# # unique it will overwrite earlyer definitions
|
76
|
+
# # (unless override is false)
|
77
|
+
# #
|
78
|
+
# # body:: is the body of the cuntion. this will be executed
|
79
|
+
# # when the defined function is called.
|
80
|
+
# #
|
81
|
+
# # override:: when true (default) earlyer definitions are
|
82
|
+
# # replaced when it is called a second time.
|
83
|
+
# # When false a exception is thrown
|
84
|
+
# #
|
85
|
+
# # pos:: The position within the source code of the definition - for
|
86
|
+
# # deugging purpose.
|
87
|
+
# def initialize name, body, override = true, pos = nil
|
88
|
+
# super(name, body, override, pos)
|
89
|
+
#
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# # When executed the FunctionDefinition first checks if a function with the
|
93
|
+
# # same name is already defined. If it is and +override+ wasn't set to ture
|
94
|
+
# # it trows a Exception. Otherwise it defines the function, deleting the
|
95
|
+
# # old definition when still repsent.
|
96
|
+
# #
|
97
|
+
# # It returns the body of the newly defined function.
|
98
|
+
# def execute env
|
99
|
+
# if (not @override) and env.function(@name)
|
100
|
+
# raise RuntimeError.new("Function #{@name} already defined", @pos[0], @pos[1], @pos[2])
|
101
|
+
# end
|
102
|
+
# if not env
|
103
|
+
# raise RuntimeError.new("No env in #{@name}", @pos[0], @pos[1], @pos[2])
|
104
|
+
# end
|
105
|
+
# env.def_function(@name,@body)
|
106
|
+
# @body
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# def optimize variables = {}
|
110
|
+
# local_vars = {}
|
111
|
+
# @body = @body.optimize local_vars
|
112
|
+
# self
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
|
116
|
+
class SimpleBlock < Block
|
117
|
+
|
118
|
+
def execute env
|
119
|
+
@content.execute tenv
|
120
|
+
end
|
121
|
+
|
122
|
+
def optimize variables = {}
|
123
|
+
SimpleBlock.new(@content.optimize(variables))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class SimpleCoreCall < CoreCall
|
128
|
+
|
129
|
+
def initialize function, arguments, pos = nil
|
130
|
+
@pos = pos
|
131
|
+
@function = function
|
132
|
+
@arguments = arguments
|
133
|
+
end
|
134
|
+
# When executed the CoreCall it will call one of the AmberVM's library functions
|
135
|
+
def execute env
|
136
|
+
AmberVM::debug "Executing SimpleCoreCall... args: #{@arguments.inspect}" if $DEBUG
|
137
|
+
args = nil
|
138
|
+
# The function is a anonymous function
|
139
|
+
|
140
|
+
# Get the function from he globals
|
141
|
+
# Test if the arguments should be executed
|
142
|
+
if @function.execargs
|
143
|
+
# The arges get executed
|
144
|
+
args = @arguments.map do |arg|
|
145
|
+
a = arg.execute env
|
146
|
+
end
|
147
|
+
else
|
148
|
+
args = @arguments
|
149
|
+
end
|
150
|
+
args = args.map do |a|
|
151
|
+
a.is_a?(AmberVM::Interpreter::VariableStorage) ? a.val : a
|
152
|
+
end
|
153
|
+
# Call the function
|
154
|
+
begin
|
155
|
+
f = @function.call(args, env, @pos)
|
156
|
+
f
|
157
|
+
rescue Exception => e
|
158
|
+
pp self
|
159
|
+
raise e
|
160
|
+
raise RuntimeError.new("Function failed to execute: #{e}", @pos[0], @pos[1], @pos[2])
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def optimize env
|
165
|
+
optimized = true
|
166
|
+
@arguments.each do |a|
|
167
|
+
if optimized
|
168
|
+
if a.is_a?(AmberVM::Interpreter::Constant)
|
169
|
+
elsif a.is_a?(AmberVM::Interpreter::VariableValue)
|
170
|
+
if a.variable.is_a?(AmberVM::Interpreter::StaticVariable)
|
171
|
+
else
|
172
|
+
optimized = false
|
173
|
+
end
|
174
|
+
else
|
175
|
+
optimized = false
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
if optimized and @function.execargs
|
180
|
+
newargs = arguments.map! do |a|
|
181
|
+
a = if a.is_a?(AmberVM::Interpreter::VariableValue)
|
182
|
+
a.variable.value
|
183
|
+
else
|
184
|
+
AmberVM::Interpreter::VariableStorage.new(a.execute(nil))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
AmberVM::Interpreter::TrivialCoreCall.new(@function, @arguments, @pos)
|
188
|
+
else
|
189
|
+
self
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class TrivialCoreCall < SimpleCoreCall
|
195
|
+
def initialize function, arguments, pos = nil
|
196
|
+
super
|
197
|
+
end
|
198
|
+
|
199
|
+
def execute env
|
200
|
+
AmberVM::debug "Executing TrivialCoreCall... args: #{@arguments.inspect}" if $DEBUG
|
201
|
+
args = nil
|
202
|
+
# The function is a anonymous function
|
203
|
+
|
204
|
+
args = @arguments.map do |a|
|
205
|
+
a.val
|
206
|
+
end
|
207
|
+
# Call the function
|
208
|
+
begin
|
209
|
+
f = @function.call(args, env, @pos)
|
210
|
+
f
|
211
|
+
rescue Exception => e
|
212
|
+
pp self
|
213
|
+
raise e
|
214
|
+
raise RuntimeError.new("Function failed to execute: #{e}", @pos[0], @pos[1], @pos[2])
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def optimize variables
|
219
|
+
self
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# For details see AmberVM::Interpreter::Declration, this is a
|
225
|
+
# optimized version vor simple cases.
|
226
|
+
#
|
227
|
+
# Only #value# is evaluated before the assignment is done.
|
228
|
+
|
229
|
+
class SimpleDeclaration < Declaration
|
230
|
+
# A Declaration is initialized wiht 2 to 3 parameters.
|
231
|
+
#
|
232
|
+
# name:: The name of the variable to store, it will be executed, usually
|
233
|
+
# this will be a Constant, unless dynamic naming is needed by the
|
234
|
+
# implemented language.
|
235
|
+
#
|
236
|
+
# value:: The value that will be assigned to the variable, for a = 1 + 1
|
237
|
+
# '1+1' would be the value to assign, so as this already suggests
|
238
|
+
# the value will be executed.
|
239
|
+
#
|
240
|
+
# pos:: The position within the source code of the definition - for
|
241
|
+
# deugging purpose.
|
242
|
+
def initialize name, value, pos = nil
|
243
|
+
@pos = pos
|
244
|
+
@name = name.to_s
|
245
|
+
@value = value
|
246
|
+
end
|
247
|
+
|
248
|
+
# Only the value is evaluated before assigning it, the name is taken
|
249
|
+
# to be a string.
|
250
|
+
#
|
251
|
+
# For details see AmberVM::Interpreter::Declration
|
252
|
+
def execute env
|
253
|
+
AmberVM::debug "Executing SimpleAssignment at #{@pos}..." if $DEBUG
|
254
|
+
env.declare(@name,@value.execute(env)).val
|
255
|
+
end
|
256
|
+
|
257
|
+
def optimize variables = {}
|
258
|
+
variables[@name] = VariableStorage.new(AmberVM::Classes[:null].new(nil))
|
259
|
+
value = @value.optimize variables
|
260
|
+
Assignment.new(StaticVariable.new(variables[@name]), value)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class OptimizedClosures < Closures
|
265
|
+
|
266
|
+
class OptimizedBinding < Binding
|
267
|
+
|
268
|
+
def initialize content, vars
|
269
|
+
@content = content
|
270
|
+
@vars = vars
|
271
|
+
end
|
272
|
+
|
273
|
+
def call params, env, pos = nil
|
274
|
+
@vars.each do |var,value|
|
275
|
+
var.val = value
|
276
|
+
end
|
277
|
+
@content.call(params, env, pos)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def initialize content, variables
|
282
|
+
@content = content
|
283
|
+
@variables = variables
|
284
|
+
end
|
285
|
+
|
286
|
+
def optimize variables = {}
|
287
|
+
OptimizedClosures.new(@content, @var)
|
288
|
+
end
|
289
|
+
|
290
|
+
def execute env
|
291
|
+
local_variables = {}
|
292
|
+
new_content=@content.optimize(local_variables)
|
293
|
+
vars_to_copy=Set.new(@variables.keys).intersection(Set.new(local_variables.keys)).to_a.map{|k| [local_variables[k],@variables[k]]}
|
294
|
+
OptimizedBinding.new(new_content, vars_to_copy.map{|inner,outer| [inner,outer.val]})
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
data/lib/amber/plugin.rb
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
module AmberVM
|
2
|
+
|
3
|
+
# = PluginHost
|
4
|
+
#
|
5
|
+
# $Id: plugin.rb 255 2008-09-21 16:25:44Z murphy $
|
6
|
+
#
|
7
|
+
# A simple subclass plugin system.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# class Generators < PluginHost
|
11
|
+
# plugin_path 'app/generators'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Generator
|
15
|
+
# extend Plugin
|
16
|
+
# PLUGIN_HOST = Generators
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# class FancyGenerator < Generator
|
20
|
+
# register_for :fancy
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Generators[:fancy] #-> FancyGenerator
|
24
|
+
# # or
|
25
|
+
# AmberVM.require_plugin 'Generators/fancy'
|
26
|
+
module PluginHost
|
27
|
+
|
28
|
+
# Raised if Encoders::[] fails because:
|
29
|
+
# * a file could not be found
|
30
|
+
# * the requested Encoder is not registered
|
31
|
+
PluginNotFound = Class.new Exception
|
32
|
+
HostNotFound = Class.new Exception
|
33
|
+
|
34
|
+
PLUGIN_HOSTS = []
|
35
|
+
PLUGIN_HOSTS_BY_ID = {} # dummy hash
|
36
|
+
|
37
|
+
# Loads all plugins using list and load.
|
38
|
+
def load_all
|
39
|
+
for plugin in list
|
40
|
+
load plugin
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the Plugin for +id+.
|
45
|
+
#
|
46
|
+
# Example:
|
47
|
+
# yaml_plugin = MyPluginHost[:yaml]
|
48
|
+
def [] id, *args, &blk
|
49
|
+
plugin = validate_id(id)
|
50
|
+
begin
|
51
|
+
plugin = plugin_hash.[] plugin, *args, &blk
|
52
|
+
end while plugin.is_a? Symbol
|
53
|
+
plugin
|
54
|
+
end
|
55
|
+
|
56
|
+
# Alias for +[]+.
|
57
|
+
alias load []
|
58
|
+
|
59
|
+
def require_helper plugin_id, helper_name
|
60
|
+
path = path_to File.join(plugin_id, helper_name)
|
61
|
+
require path
|
62
|
+
end
|
63
|
+
|
64
|
+
def has? id
|
65
|
+
plugin_hash.has_key? validate_id(id)
|
66
|
+
end
|
67
|
+
|
68
|
+
class << self
|
69
|
+
|
70
|
+
# Adds the module/class to the PLUGIN_HOSTS list.
|
71
|
+
def extended mod
|
72
|
+
PLUGIN_HOSTS << mod
|
73
|
+
end
|
74
|
+
|
75
|
+
# Warns you that you should not #include this module.
|
76
|
+
def included mod
|
77
|
+
warn "#{name} should not be included. Use extend."
|
78
|
+
end
|
79
|
+
|
80
|
+
# Find the PluginHost for host_id.
|
81
|
+
def host_by_id host_id
|
82
|
+
unless PLUGIN_HOSTS_BY_ID.default_proc
|
83
|
+
ph = Hash.new do |h, a_host_id|
|
84
|
+
for host in PLUGIN_HOSTS
|
85
|
+
h[host.host_id] = host
|
86
|
+
end
|
87
|
+
h.fetch a_host_id, nil
|
88
|
+
end
|
89
|
+
PLUGIN_HOSTS_BY_ID.replace ph
|
90
|
+
end
|
91
|
+
PLUGIN_HOSTS_BY_ID[host_id]
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# The path where the plugins can be found.
|
97
|
+
def plugin_path *args
|
98
|
+
unless args.empty?
|
99
|
+
@plugin_path = File.expand_path File.join(*args)
|
100
|
+
load_map
|
101
|
+
end
|
102
|
+
@plugin_path or raise 'No plugin_path given for %p %p' % [args, self]
|
103
|
+
end
|
104
|
+
|
105
|
+
# The host's ID.
|
106
|
+
#
|
107
|
+
# If PLUGIN_HOST_ID is not set, it is simply the class name.
|
108
|
+
def host_id
|
109
|
+
if self.const_defined? :PLUGIN_HOST_ID
|
110
|
+
self::PLUGIN_HOST_ID
|
111
|
+
else
|
112
|
+
name
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Map a plugin_id to another.
|
117
|
+
#
|
118
|
+
# Usage: Put this in a file plugin_path/_map.rb.
|
119
|
+
#
|
120
|
+
# class MyColorHost < PluginHost
|
121
|
+
# map :navy => :dark_blue,
|
122
|
+
# :maroon => :brown,
|
123
|
+
# :luna => :moon
|
124
|
+
# end
|
125
|
+
def map hash
|
126
|
+
for from, to in hash
|
127
|
+
from = validate_id from
|
128
|
+
to = validate_id to
|
129
|
+
plugin_hash[from] = to unless plugin_hash.has_key? from
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Define the default plugin to use when no plugin is found
|
134
|
+
# for a given id.
|
135
|
+
#
|
136
|
+
# See also map.
|
137
|
+
#
|
138
|
+
# class MyColorHost < PluginHost
|
139
|
+
# map :navy => :dark_blue
|
140
|
+
# default :gray
|
141
|
+
# end
|
142
|
+
def default id = nil
|
143
|
+
if id
|
144
|
+
id = validate_id id
|
145
|
+
plugin_hash[nil] = id
|
146
|
+
else
|
147
|
+
plugin_hash[nil]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Every plugin must register itself for one or more
|
152
|
+
# +ids+ by calling register_for, which calls this method.
|
153
|
+
#
|
154
|
+
# See Plugin#register_for.
|
155
|
+
def register plugin, *ids
|
156
|
+
for id in ids
|
157
|
+
unless id.is_a? Symbol
|
158
|
+
raise ArgumentError,
|
159
|
+
"id must be a Symbol, but it was a #{id.class}"
|
160
|
+
end
|
161
|
+
plugin_hash[validate_id(id)] = plugin
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# A Hash of plugion_id => Plugin pairs.
|
166
|
+
def plugin_hash
|
167
|
+
@plugin_hash ||= create_plugin_hash
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns an array of all .rb files in the plugin path.
|
171
|
+
#
|
172
|
+
# The extension .rb is not included.
|
173
|
+
def list
|
174
|
+
Dir[path_to('*')].select do |file|
|
175
|
+
File.basename(file)[/^(?!_)\w+\.rb$/]
|
176
|
+
end.map do |file|
|
177
|
+
File.basename file, '.rb'
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Makes a map of all loaded plugins.
|
182
|
+
def inspect
|
183
|
+
map = plugin_hash.dup
|
184
|
+
map.each do |id, plugin|
|
185
|
+
map[id] = plugin.to_s[/(?>\w+)$/]
|
186
|
+
end
|
187
|
+
"#{name}[#{host_id}]#{map.inspect}"
|
188
|
+
end
|
189
|
+
|
190
|
+
protected
|
191
|
+
# Created a new plugin list and stores it to @plugin_hash.
|
192
|
+
def create_plugin_hash
|
193
|
+
@plugin_hash =
|
194
|
+
Hash.new do |h, plugin_id|
|
195
|
+
id = validate_id(plugin_id)
|
196
|
+
path = path_to id
|
197
|
+
begin
|
198
|
+
require path
|
199
|
+
rescue LoadError => boom
|
200
|
+
if h.has_key? nil # default plugin
|
201
|
+
h[id] = h[nil]
|
202
|
+
else
|
203
|
+
raise PluginNotFound, 'Could not load plugin %p: %s' % [id, boom]
|
204
|
+
end
|
205
|
+
else
|
206
|
+
# Plugin should have registered by now
|
207
|
+
unless h.has_key? id
|
208
|
+
raise PluginNotFound,
|
209
|
+
"No #{self.name} plugin for #{id.inspect} found in #{path}."
|
210
|
+
end
|
211
|
+
end
|
212
|
+
h[id]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Loads the map file (see map).
|
217
|
+
#
|
218
|
+
# This is done automatically when plugin_path is called.
|
219
|
+
def load_map
|
220
|
+
mapfile = path_to '_map'
|
221
|
+
if File.exist? mapfile
|
222
|
+
require mapfile
|
223
|
+
elsif $DEBUG
|
224
|
+
warn 'no _map.rb found for %s' % name
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns the Plugin for +id+.
|
229
|
+
# Use it like Hash#fetch.
|
230
|
+
#
|
231
|
+
# Example:
|
232
|
+
# yaml_plugin = MyPluginHost[:yaml, :default]
|
233
|
+
def fetch id, *args, &blk
|
234
|
+
plugin_hash.fetch validate_id(id), *args, &blk
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns the expected path to the plugin file for the given id.
|
238
|
+
def path_to plugin_id
|
239
|
+
File.join plugin_path, "#{plugin_id}.rb"
|
240
|
+
end
|
241
|
+
|
242
|
+
# Converts +id+ to a Symbol if it is a String,
|
243
|
+
# or returns +id+ if it already is a Symbol.
|
244
|
+
#
|
245
|
+
# Raises +ArgumentError+ for all other objects, or if the
|
246
|
+
# given String includes non-alphanumeric characters (\W).
|
247
|
+
def validate_id id
|
248
|
+
if id.is_a? Symbol or id.nil?
|
249
|
+
id
|
250
|
+
elsif id.is_a? String
|
251
|
+
if id[/\w+/] == id
|
252
|
+
id.to_sym
|
253
|
+
else
|
254
|
+
raise ArgumentError, "Invalid id: '#{id}' given."
|
255
|
+
end
|
256
|
+
else
|
257
|
+
raise ArgumentError,
|
258
|
+
"String or Symbol expected, but #{id.class} given."
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
# = Plugin
|
266
|
+
#
|
267
|
+
# Plugins have to include this module.
|
268
|
+
#
|
269
|
+
# IMPORTANT: use extend for this module.
|
270
|
+
#
|
271
|
+
# Example: see PluginHost.
|
272
|
+
module Plugin
|
273
|
+
|
274
|
+
def included mod
|
275
|
+
warn "#{name} should not be included. Use extend."
|
276
|
+
end
|
277
|
+
|
278
|
+
# Register this class for the given langs.
|
279
|
+
# Example:
|
280
|
+
# class MyPlugin < PluginHost::BaseClass
|
281
|
+
# register_for :my_id
|
282
|
+
# ...
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# See PluginHost.register.
|
286
|
+
def register_for *ids
|
287
|
+
plugin_host.register self, *ids
|
288
|
+
end
|
289
|
+
|
290
|
+
# The host for this Plugin class.
|
291
|
+
def plugin_host host = nil
|
292
|
+
if host and not host.is_a? PluginHost
|
293
|
+
raise ArgumentError,
|
294
|
+
"PluginHost expected, but #{host.class} given."
|
295
|
+
end
|
296
|
+
self.const_set :PLUGIN_HOST, host if host
|
297
|
+
self::PLUGIN_HOST
|
298
|
+
end
|
299
|
+
|
300
|
+
# Require some helper files.
|
301
|
+
#
|
302
|
+
# Example:
|
303
|
+
#
|
304
|
+
# class MyPlugin < PluginHost::BaseClass
|
305
|
+
# register_for :my_id
|
306
|
+
# helper :my_helper
|
307
|
+
#
|
308
|
+
# The above example loads the file myplugin/my_helper.rb relative to the
|
309
|
+
# file in which MyPlugin was defined.
|
310
|
+
def helper *helpers
|
311
|
+
for helper in helpers
|
312
|
+
self::PLUGIN_HOST.require_helper plugin_id, helper.to_s
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Returns the pulgin id used by the engine.
|
317
|
+
def plugin_id
|
318
|
+
name[/\w+$/].downcase
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
# Convenience method for plugin loading.
|
324
|
+
# The syntax used is:
|
325
|
+
#
|
326
|
+
# AmberVM.require_plugin '<Host ID>/<Plugin ID>'
|
327
|
+
#
|
328
|
+
# Returns the loaded plugin.
|
329
|
+
def self.require_plugin path
|
330
|
+
host_id, plugin_id = path.split '/', 2
|
331
|
+
host = PluginHost.host_by_id(host_id)
|
332
|
+
raise PluginHost::HostNotFound,
|
333
|
+
"No host for #{host_id.inspect} found." unless host
|
334
|
+
host.load plugin_id
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|