fluid 1.0.0
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.
- data/History.txt +3 -0
- data/LICENSE.txt +34 -0
- data/Manifest.txt +13 -0
- data/README.txt +126 -0
- data/Rakefile +35 -0
- data/Rakefile.hoe +22 -0
- data/examples/a-bit-more-clever-fluid-tracing.rb +60 -0
- data/examples/globals.rb +16 -0
- data/examples/simple-fluid-tracing.rb +38 -0
- data/homepage/index.html +15 -0
- data/lib/fluid.rb +389 -0
- data/setup.rb +1585 -0
- data/test/fluid-tests.rb +469 -0
- metadata +70 -0
data/History.txt
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
This software is Copyright (C) 2007 by Brian Marick <marick@exampler.com>. It is licensed according to "the Ruby license". Specifically:
|
2
|
+
|
3
|
+
You can redistribute it and/or modify it under either the terms of the GNU General Public License, version 2, <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html> or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) make other distribution arrangements with the author.
|
21
|
+
|
22
|
+
3. You may modify and include part of the software into any other
|
23
|
+
software (possibly commercial).
|
24
|
+
|
25
|
+
4. Text or files supplied as input to or produced as output from
|
26
|
+
the software do not automatically fall under the copyright of the
|
27
|
+
software, but belong to whomever generated them, and may be sold
|
28
|
+
commercially, and may be aggregated with this software.
|
29
|
+
|
30
|
+
5. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
31
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
32
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
33
|
+
PURPOSE.
|
34
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
Rakefile.hoe
|
7
|
+
examples/a-bit-more-clever-fluid-tracing.rb
|
8
|
+
examples/globals.rb
|
9
|
+
examples/simple-fluid-tracing.rb
|
10
|
+
homepage/index.html
|
11
|
+
lib/fluid.rb
|
12
|
+
setup.rb
|
13
|
+
test/fluid-tests.rb
|
data/README.txt
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
Class Fluid provides dynamically scoped ("fluid") variables modeled
|
2
|
+
after those of Common Lisp. It also gives you a convenient way to
|
3
|
+
reversibly change the values of globals.
|
4
|
+
|
5
|
+
== An Alternative to Globals
|
6
|
+
|
7
|
+
Here's an incredibly simple example:
|
8
|
+
|
9
|
+
require 's4t-utils/fluid'
|
10
|
+
|
11
|
+
Fluid.let(:var, 1) {
|
12
|
+
puts Fluid.var # prints 1
|
13
|
+
}
|
14
|
+
puts Fluid.var # error - Fluid.var has disappeared at this point.
|
15
|
+
|
16
|
+
Fluid.var is a "pseudovariable" that only exists within the block. What's
|
17
|
+
interesting about fluid variables is what "within the block" means.
|
18
|
+
Here's another example:
|
19
|
+
|
20
|
+
require 's4t-utils/fluid'
|
21
|
+
|
22
|
+
def putter
|
23
|
+
puts Fluid.var
|
24
|
+
end
|
25
|
+
|
26
|
+
Fluid.let(:var, 1) {
|
27
|
+
putter
|
28
|
+
}
|
29
|
+
putter
|
30
|
+
|
31
|
+
The first call of +putter+ will successfully print 1. The second
|
32
|
+
will fail because Fluid.var doesn't exist any more.
|
33
|
+
|
34
|
+
Fluid variables are useful in relatively few situations. They're good when you have
|
35
|
+
some number of interconnected objects that need to share some
|
36
|
+
value. You could pass the value around, but then intermediate methods
|
37
|
+
that didn't care about it would have to pass it along to methods
|
38
|
+
that did. You could use a global, but then the value would be visible
|
39
|
+
to objects that ought not to be able to see it. Fluid variables let you
|
40
|
+
"scope" the value to all methods called (directly or indirectly) from
|
41
|
+
an entry point to the collection of connected objects.
|
42
|
+
|
43
|
+
This is especially handy when you want to change the value of the
|
44
|
+
variable according to the depth of processing, as when you want to
|
45
|
+
increase the indentation level of some sort of tracing output.
|
46
|
+
Like this:
|
47
|
+
|
48
|
+
fact(5)
|
49
|
+
fact(4)
|
50
|
+
fact(3)
|
51
|
+
fact(2)
|
52
|
+
fact(1)
|
53
|
+
fact(1) => 1
|
54
|
+
fact(2) => 2
|
55
|
+
fact(3) => 6
|
56
|
+
fact(4) => 24
|
57
|
+
fact(5) => 120
|
58
|
+
|
59
|
+
One implementation of that would look like the following implementation
|
60
|
+
of fact. It uses two utility methods that reduce clutter.
|
61
|
+
|
62
|
+
def fact(n)
|
63
|
+
trace "fact(#{n})" # trace() produces output with appropriate indentation.
|
64
|
+
result =
|
65
|
+
if n <= 1
|
66
|
+
n
|
67
|
+
else
|
68
|
+
deeper { n * fact(n-1) } # deeper() executes block with increased indentation.
|
69
|
+
end
|
70
|
+
trace "fact(#{n}) => #{result}"
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
Here's the support code.
|
76
|
+
|
77
|
+
Fluid.defvar(:indent_prefix, '') # Initial value of indentation.
|
78
|
+
|
79
|
+
def trace(text)
|
80
|
+
puts Fluid.indent_prefix + text # Use the value even though it wasn't passed in.
|
81
|
+
end
|
82
|
+
|
83
|
+
def deeper # "Rebind" the value of the variable for the scope of a block.
|
84
|
+
Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
|
85
|
+
yield
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
For an even less intrusive example, see the examples directory.
|
90
|
+
|
91
|
+
Here's a final example that shows the syntax and behavior of
|
92
|
+
Fluid.let. See also the more formal description at Fluid.let.
|
93
|
+
|
94
|
+
Fluid.let([:var1, 1],
|
95
|
+
[:var2, 2]) {
|
96
|
+
puts(Fluid.var1) # prints 1
|
97
|
+
puts(Fluid.var2) # prints 2
|
98
|
+
Fluid.let([:var2, "new 2"],
|
99
|
+
[:var3, "new 3"]) {
|
100
|
+
puts(Fluid.var1) # prints 1, as above.
|
101
|
+
puts(Fluid.var2) # prints "new 2"
|
102
|
+
puts(Fluid.var3) # prints "new 3"
|
103
|
+
}
|
104
|
+
puts(Fluid.var1) # prints 1
|
105
|
+
puts(Fluid.var2) # Back to printing 2
|
106
|
+
puts(Fluid.var3) # error - that variable no longer exists.
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
== Globals
|
111
|
+
|
112
|
+
If you wish, you can also use Fluid.let to temporarily change the value of
|
113
|
+
globals. For example, the global #, is used as an implicit argument to
|
114
|
+
Array#join. You could do the following:
|
115
|
+
|
116
|
+
Fluid.let("$,", "-") do
|
117
|
+
puts [1, 2, 3].join #=> "1-2-3"
|
118
|
+
end
|
119
|
+
|
120
|
+
puts [1, 2, 3].join #=> "123"
|
121
|
+
|
122
|
+
The advantage of this over just setting $, and then resetting it is that
|
123
|
+
Fluid takes care of dealing with exceptions.
|
124
|
+
|
125
|
+
You're not allowed to rebind all globals. The I/O globals ($stdout, etc.)
|
126
|
+
have special-case behavior that's either impossible or not safe to cope with.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# This is a Rakefile that would live in the root folder for any package
|
2
|
+
# that follows the Scripting for Testers conventions. You should not need
|
3
|
+
# to edit it.
|
4
|
+
|
5
|
+
$started_from_rakefile=true
|
6
|
+
|
7
|
+
require 'pathname'
|
8
|
+
PACKAGE_ROOT = Dir.pwd
|
9
|
+
$:.unshift("#{PACKAGE_ROOT}/lib")
|
10
|
+
|
11
|
+
# The tests require my S4TUtils to run, but the library itself
|
12
|
+
# doesn't require it, so it's not listed as a dependency in the gem.
|
13
|
+
require 's4t-utils/load-path-auto-adjuster'
|
14
|
+
|
15
|
+
require 's4t-utils'
|
16
|
+
include S4tUtils
|
17
|
+
|
18
|
+
# The following two lines are used to tell generic Rake tasks about
|
19
|
+
# this particular project. If you installed using the s4t-utils
|
20
|
+
# installation tool, they should have already been set correctly.
|
21
|
+
|
22
|
+
MyFileSystemName='fluid' # No ".rb" extension.
|
23
|
+
MyModuleName='Fluid'
|
24
|
+
|
25
|
+
|
26
|
+
# These are the Ruby files 'rake rdoc' searchs for documentation.
|
27
|
+
# The following includes all ruby files except for third-party
|
28
|
+
# libraries. Change at will.
|
29
|
+
|
30
|
+
MyRdocFiles = FileList.new("lib/fluid.rb",
|
31
|
+
"lib/fluid/**/*.rb").exclude("**/third-party/**")
|
32
|
+
|
33
|
+
|
34
|
+
require 's4t-utils/rakefile-common'
|
35
|
+
|
data/Rakefile.hoe
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-03.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require 'hoe'
|
7
|
+
require 'lib/fluid'
|
8
|
+
|
9
|
+
|
10
|
+
Hoe.new("fluid", Fluid::Version) do |p|
|
11
|
+
p.rubyforge_name = "fluid"
|
12
|
+
p.changes = "See History.txt"
|
13
|
+
p.author = "Brian Marick"
|
14
|
+
p.description = "Fluid (dynamically scoped) variables"
|
15
|
+
p.summary = p.description
|
16
|
+
p.email = "marick@exampler.com"
|
17
|
+
p.extra_deps = []
|
18
|
+
p.test_globs = "test/**/*-tests.rb"
|
19
|
+
p.rdoc_pattern = %r{README.txt|lib/}
|
20
|
+
p.url = "http://fluid.rubyforge.org"
|
21
|
+
p.remote_rdoc_dir = 'rdoc'
|
22
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-22.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# This is a slightly cleverer implementation of logging with
|
7
|
+
# varying indentation than the one in simple-fluid-tracing.rb.
|
8
|
+
|
9
|
+
require 'fluid'
|
10
|
+
|
11
|
+
def trace(text)
|
12
|
+
puts Fluid.indent_prefix + text
|
13
|
+
end
|
14
|
+
|
15
|
+
# Trace all calls to _methodname_ in class _klass_. By default, printing
|
16
|
+
# is in this format:
|
17
|
+
# message(arg, arg, ...)
|
18
|
+
# If _show_receiver_ is true, this format is used:
|
19
|
+
# receiver.message(arg, arg, ...)
|
20
|
+
def indenting_trace(methodname, klass = self.class, show_receiver = false)
|
21
|
+
methodname = methodname.to_s # ensure string.
|
22
|
+
original = '_untraced_'+methodname
|
23
|
+
klass.send(:alias_method, original, methodname)
|
24
|
+
|
25
|
+
klass.send(:define_method, methodname) do | *actual_args |
|
26
|
+
Fluid.defvar(:indent_prefix, '') # only has effect the first time
|
27
|
+
Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
|
28
|
+
arglist_description = "(#{actual_args.collect {|a| a.inspect }.join(', ')})"
|
29
|
+
invocation = methodname + arglist_description
|
30
|
+
invocation = "#{self.inspect}.#{invocation}" if show_receiver
|
31
|
+
trace invocation
|
32
|
+
result = self.send(original, *actual_args)
|
33
|
+
trace invocation + ' => ' + result.inspect
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Fixnum
|
40
|
+
def times(other) # Use this to show alternate printing.
|
41
|
+
self * other
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def fact(n)
|
46
|
+
if (n <= 1)
|
47
|
+
1
|
48
|
+
else
|
49
|
+
n.times(fact(n-1))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
puts "factorial of 5 is #{fact(5).to_s}."
|
54
|
+
|
55
|
+
indenting_trace :fact
|
56
|
+
indenting_trace :times, Fixnum, :show_receiver
|
57
|
+
|
58
|
+
puts "factorial of 5 is #{fact(5).to_s}."
|
59
|
+
|
60
|
+
|
data/examples/globals.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-22.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# This example shows using Fluid with global variables. Essentially,
|
7
|
+
# it's a slightly more compact way of changing a global within a
|
8
|
+
# begin... ensure... end loop that makes sure to undo the change.
|
9
|
+
|
10
|
+
require 'fluid'
|
11
|
+
|
12
|
+
Fluid.let("$,", "-") do
|
13
|
+
puts [1, 2, 3].join #=> "1-2-3"
|
14
|
+
end
|
15
|
+
|
16
|
+
puts [1, 2, 3].join #=> "123"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-22.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
# This example shows how a set of functions (in this case, recursive
|
7
|
+
# calls of the factorial function) can control indentation of log
|
8
|
+
# output without having to pass around a variable containing the
|
9
|
+
# depth of the log.
|
10
|
+
|
11
|
+
require 'fluid'
|
12
|
+
|
13
|
+
Fluid.defvar(:indent_prefix, '')
|
14
|
+
|
15
|
+
def trace(text)
|
16
|
+
puts Fluid.indent_prefix + text
|
17
|
+
end
|
18
|
+
|
19
|
+
def deeper
|
20
|
+
Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def fact(n)
|
26
|
+
trace "fact(#{n})"
|
27
|
+
result =
|
28
|
+
if n <= 1
|
29
|
+
n
|
30
|
+
else
|
31
|
+
deeper { n * fact(n-1) }
|
32
|
+
end
|
33
|
+
trace "fact(#{n}) => #{result}"
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
puts fact(5)
|
data/homepage/index.html
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
5
|
+
<title>s4t-utils</title>
|
6
|
+
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<h1>Fluid (dynamically scoped) variables</h1>
|
10
|
+
<p></p>
|
11
|
+
<p><a href="http://fluid.rubyforge.org/rdoc/" title="Fluid.rb documentation">Documentation</a> (rdoc)</p>
|
12
|
+
<p><a href="" title="RubyForge: Fluid: Project Filelist">Download</a></p>
|
13
|
+
|
14
|
+
|
15
|
+
</body>
|
data/lib/fluid.rb
ADDED
@@ -0,0 +1,389 @@
|
|
1
|
+
# Fluid variables for Ruby.
|
2
|
+
# Originally 2004/03/08 20:16:13
|
3
|
+
# Gemified 2007/07
|
4
|
+
|
5
|
+
# Fluid (dynamically scoped) variables for Ruby. See README.txt[link:files/README_txt.html].
|
6
|
+
class Fluid
|
7
|
+
Version="1.0.0"
|
8
|
+
### Environment ###
|
9
|
+
|
10
|
+
# An environment holds variable->value bindings. Each
|
11
|
+
# variable name is an entry in a hash table. It is
|
12
|
+
# associated with a stack of values. Environments are
|
13
|
+
# manipulated by objects of class Var.
|
14
|
+
class Environment < Hash # :nodoc:
|
15
|
+
def create(var_name) self[var_name] = []; end
|
16
|
+
def destroy(var_name) delete(var_name); end
|
17
|
+
def has?(var_name) has_key?(var_name); end
|
18
|
+
def unbound?(var_name) self[var_name] == []; end
|
19
|
+
def set(var_name, value) self[var_name][-1] = value; end
|
20
|
+
def get(var_name) self[var_name][-1]; end
|
21
|
+
def push_binding(var_name, var_value) self[var_name].push(var_value); end
|
22
|
+
def pop_binding(var_name) self[var_name].pop; end
|
23
|
+
end
|
24
|
+
|
25
|
+
# All fluid variables are managed by one environment.
|
26
|
+
@@environment = Environment.new
|
27
|
+
|
28
|
+
|
29
|
+
### Var ###
|
30
|
+
|
31
|
+
# There are different kinds of variables. They use the environment
|
32
|
+
# differently.
|
33
|
+
class Var # :nodoc:
|
34
|
+
attr_reader :name, # Canonicalize all names to symbols.
|
35
|
+
:original_name # Retain original name for error messages.
|
36
|
+
|
37
|
+
# Factory method that returns subclasses of Var.
|
38
|
+
def Var.build(original_name, environment, value_destructor=nil)
|
39
|
+
klass = (global?(original_name)) ? GlobalVar : FluidVar
|
40
|
+
klass.build(original_name, environment, value_destructor)
|
41
|
+
end
|
42
|
+
|
43
|
+
def Var.global?(string_or_symbol)
|
44
|
+
?$ == string_or_symbol.to_s[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(original_name, environment, value_destructor)
|
48
|
+
@original_name = original_name
|
49
|
+
@name = Var.ensure_symbol(original_name)
|
50
|
+
@environment = environment
|
51
|
+
@value_destructor = value_destructor
|
52
|
+
end
|
53
|
+
|
54
|
+
# These two methods are the ones used to manipulate the
|
55
|
+
# environment. Note subclasses.
|
56
|
+
def push_binding(value)
|
57
|
+
create unless @environment.has?(name)
|
58
|
+
@environment.push_binding(name, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def pop_binding
|
62
|
+
previous = @environment.pop_binding(name)
|
63
|
+
apply_value_destructor(previous) if @value_destructor
|
64
|
+
destroy if @environment.unbound?(name)
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
def create
|
69
|
+
# I would prefer acceptability of the name to be checked in the
|
70
|
+
# class.build method, before other work is done. However, some
|
71
|
+
# acceptability is most cleanly checked when the variable is
|
72
|
+
# created, so I decided to do all checking here.
|
73
|
+
assert_acceptable_name
|
74
|
+
@environment.create(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def destroy
|
78
|
+
@environment.destroy(name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def apply_value_destructor(value)
|
82
|
+
if @value_destructor.respond_to? :call
|
83
|
+
@value_destructor.call(value)
|
84
|
+
else
|
85
|
+
value.send(@value_destructor)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def assert_acceptable_name
|
90
|
+
subclass_responsibility
|
91
|
+
end
|
92
|
+
|
93
|
+
def Var.ensure_symbol(original_name)
|
94
|
+
original_name.to_s.intern
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
### FluidVar ###
|
99
|
+
|
100
|
+
# FluidVars are those accessed via "Fluid.varname". This subclass
|
101
|
+
# contains the code for adding those getters and setters.
|
102
|
+
class FluidVar < Var # :nodoc:
|
103
|
+
|
104
|
+
def FluidVar.build(*args)
|
105
|
+
new(*args) # No subclasses to build specially.
|
106
|
+
end
|
107
|
+
|
108
|
+
def create
|
109
|
+
super
|
110
|
+
Fluid.create_getter(name)
|
111
|
+
Fluid.create_setter(name)
|
112
|
+
end
|
113
|
+
|
114
|
+
def destroy
|
115
|
+
super
|
116
|
+
Fluid.delete_getter(name)
|
117
|
+
Fluid.delete_setter(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
def assert_acceptable_name
|
121
|
+
unless name.to_s =~ /^[a-z_]\w*$/
|
122
|
+
raise NameError, "'#{original_name}' is not a good fluid variable name. It can't be used as a method name."
|
123
|
+
end
|
124
|
+
|
125
|
+
if Fluid.methods.include?(name.to_s)
|
126
|
+
raise NameError, "'#{original_name}' cannot be a fluid variable. It's already a method of Fluid's."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
### GlobalVar ###
|
133
|
+
|
134
|
+
# GlobalVars are the subclass that handles binding and unbinding of
|
135
|
+
# Ruby globals.
|
136
|
+
#
|
137
|
+
class GlobalVar < Var # :nodoc:
|
138
|
+
def GlobalVar.build(original_name, environment, value_destructor)
|
139
|
+
new(original_name, environment, value_destructor)
|
140
|
+
end
|
141
|
+
|
142
|
+
# The original value of the global is, in effect, an outermost
|
143
|
+
# binding, one not created with Fluid.let. A binding has to be made
|
144
|
+
# here so that unbinding works on exit from the Fluid.let block.
|
145
|
+
def create
|
146
|
+
super
|
147
|
+
push_binding(instance_eval("#{name.to_s}"))
|
148
|
+
end
|
149
|
+
|
150
|
+
# The environment really just holds values for unbinding.
|
151
|
+
# Access to the newly-bound variable within the Fluid.let block
|
152
|
+
# is through the global itself. So it needs to be set and reset
|
153
|
+
# by these methods.
|
154
|
+
def push_binding(value)
|
155
|
+
super
|
156
|
+
set_global
|
157
|
+
end
|
158
|
+
|
159
|
+
def pop_binding
|
160
|
+
super
|
161
|
+
set_global
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_global
|
165
|
+
evalme = "#{name.to_s} = @environment.get(#{name.inspect})"
|
166
|
+
# puts evalme
|
167
|
+
instance_eval evalme
|
168
|
+
end
|
169
|
+
|
170
|
+
def assert_acceptable_name
|
171
|
+
if [:$!, :$stdin, :$stdout, :$stderr].include?(name)
|
172
|
+
raise NameError,
|
173
|
+
"'#{name}' is not allowed in Fluid.let. It's too iffy to get it right."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# Puts the variable specifications in effect, then executes the block,
|
180
|
+
# undoes the variable specifications, and returns the block's value.
|
181
|
+
#
|
182
|
+
# The simplest form of variable specification is a variable name (symbol or string).
|
183
|
+
# In this case, the variable starts out with the value nil:
|
184
|
+
#
|
185
|
+
# Fluid.let(:will_have_value_nil) {...}
|
186
|
+
#
|
187
|
+
# You can also specify the initial value of the variable:
|
188
|
+
#
|
189
|
+
# Fluid.let(:will_have_value_1, 1) {...}
|
190
|
+
#
|
191
|
+
# Multiple variables can be specified in a single let. Each one must
|
192
|
+
# be in an array, <i>even if given no value</i>:
|
193
|
+
#
|
194
|
+
# Fluid.let([:will_have_value_1, 1],
|
195
|
+
# [:starts_as_nil]) {...}
|
196
|
+
#
|
197
|
+
#
|
198
|
+
# From the moment the block begins to execute until the moment it returns,
|
199
|
+
# getters and setters for the variables are made class methods of Fluid:
|
200
|
+
#
|
201
|
+
# Fluid.let(:var, 1) {
|
202
|
+
# Fluid.var # has value 1
|
203
|
+
# Fluid.var = 2 # change value to 2
|
204
|
+
# }
|
205
|
+
#
|
206
|
+
# References to the variable needn't be in the lexical scope of the
|
207
|
+
# Fluid.let. They can be anywhere in the program. (To be more precise:
|
208
|
+
# references can be made whenever the original Fluid.let is on the stack.)
|
209
|
+
#
|
210
|
+
# When Fluid.let's block exits, the value of the variable is no longer accessible
|
211
|
+
# through Fluid.var. An attempt to access it will raise a NameError.
|
212
|
+
# If, however, there's another Fluid.let binding the same name still on the
|
213
|
+
# stack, that version's value is back in effect. For example:
|
214
|
+
#
|
215
|
+
# Fluid.let(:var, 1) { # Fluid.var => 1
|
216
|
+
# Fluid.var = 2 # Fluid.var => 2
|
217
|
+
# Fluid.let(:var, "hello") { # Fluid.var => "hello"
|
218
|
+
# Fluid.var *= 2 # Fluid.var => "hellohello"
|
219
|
+
# } # Fluid.var => 2
|
220
|
+
# } # Fluid.var => raises NameError
|
221
|
+
#
|
222
|
+
# Variable values are undone even if the block exits with a
|
223
|
+
# throw or an exception.
|
224
|
+
#
|
225
|
+
# If a variable name begins with '$', Fluid.let realizes
|
226
|
+
# it's a global variable and gives that variable a new value.
|
227
|
+
# No getters or setters are created. The old
|
228
|
+
# value is still restored when the block exits.
|
229
|
+
#
|
230
|
+
#
|
231
|
+
#
|
232
|
+
# === Destructors
|
233
|
+
#
|
234
|
+
# There may be an argument past the initial value, the <i>value destructor</i>.
|
235
|
+
# It may be either a Proc or the name of a method. If it's the name of a method,
|
236
|
+
# it's sent as a message to the value of the second argument. Like this:
|
237
|
+
#
|
238
|
+
# Fluid.let(out, File.open("logfile", "w"), :close)
|
239
|
+
#
|
240
|
+
# (You wouldn't really do that, though, since File.open does the same
|
241
|
+
# thing for you.)
|
242
|
+
#
|
243
|
+
# If the third argument is a block, it's called with the value of the
|
244
|
+
# second argument as its single parameter.
|
245
|
+
#
|
246
|
+
# Fluid.let(:out, File.open("logfile", 'w'),
|
247
|
+
# proc {|io| io.close}) {...}
|
248
|
+
|
249
|
+
def Fluid.let(*var_specs)
|
250
|
+
unwind_list = create_dynamic_context(var_specs)
|
251
|
+
|
252
|
+
begin
|
253
|
+
return yield if block_given?
|
254
|
+
ensure
|
255
|
+
unwind_dynamic_context unwind_list
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# A global declaration of a fluid variable. After executing this code:
|
260
|
+
#
|
261
|
+
# Fluid.defvar(:global, 5)
|
262
|
+
#
|
263
|
+
# Fluid.global will normally everywhere have the value 5, unless it's
|
264
|
+
# changed by assignment or Fluid.let.
|
265
|
+
#
|
266
|
+
# However, Fluid.defvar has effect only the first time it's executed.
|
267
|
+
# That is, given this sequence:
|
268
|
+
#
|
269
|
+
# Fluid.defvar(:global, 5)
|
270
|
+
# Fluid.defvar(:global, 6666666)
|
271
|
+
#
|
272
|
+
# Fluid.global has value 5. The second Fluid.defvar is ignored.
|
273
|
+
# The "a-bit-more-clever-fluid-tracing.rb" example shows how
|
274
|
+
# this can be useful.
|
275
|
+
#
|
276
|
+
# A Fluid.defvar executed while a Fluid.let block is in effect will
|
277
|
+
# have no effect:
|
278
|
+
#
|
279
|
+
# Fluid.let(:not_global, 1) {
|
280
|
+
# Fluid.defvar(:not_global, 5555) # Fluid.not_global == 1
|
281
|
+
# }
|
282
|
+
# # The defvar has had no effect, so Fluid.not_global
|
283
|
+
# # has no value after the block.
|
284
|
+
#
|
285
|
+
# Fluid.defvar can take a block as an argument:
|
286
|
+
#
|
287
|
+
# Fluid.defvar(:var) { long_and_expensive_computation }
|
288
|
+
#
|
289
|
+
# The block is only executed if its value would be assigned to the
|
290
|
+
# variable.
|
291
|
+
#
|
292
|
+
# The first argument to Fluid.defvar may not be the name of
|
293
|
+
# a global variable.
|
294
|
+
|
295
|
+
def Fluid.defvar(name, value = nil)
|
296
|
+
if Var.global?(name)
|
297
|
+
raise NameError, "Fluid.defvar of a global can never have an effect, so it's not allowed."
|
298
|
+
end
|
299
|
+
|
300
|
+
var = Var.build(name, @@environment)
|
301
|
+
unless @@environment.has?(var.name)
|
302
|
+
value = yield if block_given?
|
303
|
+
var.push_binding(value)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Answers whether _name_ is already a fluid variable.
|
308
|
+
def Fluid.has?(name)
|
309
|
+
return true if Var.global?(name)
|
310
|
+
@@environment.has?(Var.ensure_symbol(name))
|
311
|
+
end
|
312
|
+
|
313
|
+
def Fluid.method_missing(symbol, *args) # :nodoc:
|
314
|
+
symbol = symbol.to_s.gsub(/=/, '')
|
315
|
+
raise NameError, "'#{symbol}' has not been defined with Fluid.let or Fluid.defvar."
|
316
|
+
end
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
def Fluid.create_dynamic_context(var_specs)
|
321
|
+
var_list = []
|
322
|
+
return var_list if var_specs.empty?
|
323
|
+
|
324
|
+
var_specs = [var_specs] unless var_specs[0].is_a? Array
|
325
|
+
|
326
|
+
var_specs.each { |one_spec|
|
327
|
+
name = one_spec[0]
|
328
|
+
value = one_spec[1]
|
329
|
+
value_destructor = one_spec[2]
|
330
|
+
|
331
|
+
var = Var.build(name, @@environment, value_destructor)
|
332
|
+
assert_variable_name_is_not_duplicate(var.name, var_list)
|
333
|
+
var.push_binding(value)
|
334
|
+
var_list.push(var)
|
335
|
+
}
|
336
|
+
var_list
|
337
|
+
end
|
338
|
+
|
339
|
+
def Fluid.unwind_dynamic_context(var_list)
|
340
|
+
var_list.each { | var | var.pop_binding }
|
341
|
+
end
|
342
|
+
|
343
|
+
def Fluid.create_getter(var)
|
344
|
+
evalme = %Q{
|
345
|
+
def Fluid.#{var.to_s}
|
346
|
+
@@environment.get(#{var.inspect})
|
347
|
+
end
|
348
|
+
}
|
349
|
+
# puts evalme
|
350
|
+
class_eval evalme
|
351
|
+
end
|
352
|
+
|
353
|
+
def Fluid.create_setter(var)
|
354
|
+
evalme = %Q{
|
355
|
+
def Fluid.#{var.to_s}=(value)
|
356
|
+
@@environment.set(#{var.inspect}, value)
|
357
|
+
end
|
358
|
+
}
|
359
|
+
# puts evalme
|
360
|
+
class_eval evalme
|
361
|
+
end
|
362
|
+
|
363
|
+
def Fluid.delete_fluid_method(name)
|
364
|
+
evalme = %Q{
|
365
|
+
class << Fluid
|
366
|
+
remove_method(#{name.inspect})
|
367
|
+
end
|
368
|
+
}
|
369
|
+
#puts evalme
|
370
|
+
class_eval evalme
|
371
|
+
end
|
372
|
+
|
373
|
+
def Fluid.delete_getter(var)
|
374
|
+
delete_fluid_method(var)
|
375
|
+
end
|
376
|
+
def Fluid.delete_setter(var)
|
377
|
+
delete_fluid_method("#{var}=".intern)
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
def Fluid.assert_variable_name_is_not_duplicate(name_symbol,
|
382
|
+
already_defined = [])
|
383
|
+
if already_defined.detect { | e | e.name == name_symbol }
|
384
|
+
raise NameError, "'#{name_symbol}' is defined twice in the same Fluid.let."
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
|