ctioga2 0.8 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +15 -0
- data/bin/ct2-make-movie +253 -0
- data/lib/ctioga2/commands/doc/doc.rb +4 -0
- data/lib/ctioga2/commands/doc/documentation-commands.rb +23 -8
- data/lib/ctioga2/commands/doc/html.rb +111 -60
- data/lib/ctioga2/commands/doc/markup.rb +2 -2
- data/lib/ctioga2/commands/function.rb +90 -0
- data/lib/ctioga2/commands/general-commands.rb +12 -0
- data/lib/ctioga2/commands/general-functions.rb +106 -0
- data/lib/ctioga2/commands/general-types.rb +8 -3
- data/lib/ctioga2/commands/interpreter.rb +11 -0
- data/lib/ctioga2/commands/parsers/file.rb +7 -5
- data/lib/ctioga2/commands/strings.rb +25 -4
- data/lib/ctioga2/commands/variables.rb +5 -1
- data/lib/ctioga2/data/point.rb +12 -4
- data/lib/ctioga2/graphics/elements/curve2d.rb +13 -2
- data/lib/ctioga2/graphics/elements/subplot.rb +13 -1
- data/lib/ctioga2/graphics/styles/axes.rb +12 -2
- data/lib/ctioga2/graphics/styles/map-axes.rb +1 -1
- data/lib/ctioga2/graphics/styles/plot.rb +48 -6
- data/lib/ctioga2/graphics/styles/texts.rb +9 -1
- data/lib/ctioga2/graphics/styles/ticks.rb +34 -0
- data/lib/ctioga2/graphics/subplot-commands.rb +35 -0
- data/lib/ctioga2/graphics/types.rb +30 -4
- data/lib/ctioga2/graphics/types/boxes.rb +13 -0
- data/lib/ctioga2/graphics/types/dimensions.rb +7 -0
- data/lib/ctioga2/plotmaker.rb +2 -0
- data/lib/ctioga2/postprocess.rb +15 -2
- data/lib/ctioga2/utils.rb +120 -0
- data/lib/ctioga2/version.rb +2 -2
- metadata +5 -2
@@ -108,7 +108,7 @@ module CTioga2
|
|
108
108
|
attr_accessor :target
|
109
109
|
|
110
110
|
# _target_ is the name of the target, which can be of _type_
|
111
|
-
# 'group', 'command', 'backend', 'type' and 'url'
|
111
|
+
# 'group', 'command', 'backend', 'type', 'function' and 'url'
|
112
112
|
def initialize(doc, target, type)
|
113
113
|
super(doc)
|
114
114
|
if type =~ /url/
|
@@ -279,7 +279,7 @@ module CTioga2
|
|
279
279
|
protected
|
280
280
|
|
281
281
|
# A few constants to help writing out the paragraph markup
|
282
|
-
LinkRE = /\{(group|type|command|backend|url):\s*([^}]+?)\s*\}/
|
282
|
+
LinkRE = /\{(group|type|command|backend|url|function):\s*([^}]+?)\s*\}/
|
283
283
|
|
284
284
|
MarkOnceRE = /@([^@]+)@/
|
285
285
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# function.rb: makefile-like functions
|
2
|
+
# copyright (c) 2014 by Vincent Fourmond
|
3
|
+
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details (in the COPYING file).
|
13
|
+
|
14
|
+
require 'ctioga2/utils'
|
15
|
+
require 'ctioga2/commands/strings'
|
16
|
+
|
17
|
+
module CTioga2
|
18
|
+
|
19
|
+
module Commands
|
20
|
+
|
21
|
+
# A Function is a makefile-like "macro" or "function" that takes
|
22
|
+
# one or more arguments (no argumentless functions for now).
|
23
|
+
#
|
24
|
+
# This class provides both the definition and handling of a
|
25
|
+
# function and the global registry of functions.
|
26
|
+
class Function
|
27
|
+
|
28
|
+
# The underlying proc object. The first argument to the code is
|
29
|
+
# *always* the plotmaker object.
|
30
|
+
attr_accessor :code
|
31
|
+
|
32
|
+
# The name of the function. Probably better lowercase ?
|
33
|
+
attr_accessor :name
|
34
|
+
|
35
|
+
# A short description
|
36
|
+
attr_accessor :short_description
|
37
|
+
|
38
|
+
# Long description, ie a help text like the rest
|
39
|
+
attr_accessor :description
|
40
|
+
|
41
|
+
# Registers a function.
|
42
|
+
#
|
43
|
+
# @todo Have self-documenting capacities !
|
44
|
+
def initialize(name, short_desc, &blk)
|
45
|
+
@code = blk
|
46
|
+
@name = name
|
47
|
+
@short_description = short_desc
|
48
|
+
|
49
|
+
Function.register(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def describe(txt)
|
53
|
+
@description = txt
|
54
|
+
end
|
55
|
+
|
56
|
+
# Expands the function, and returns the corresponding string.
|
57
|
+
def expand(string, interpreter)
|
58
|
+
if @code.arity == 2
|
59
|
+
args = [string.expand_to_string(interpreter)]
|
60
|
+
else
|
61
|
+
args = string.expand_and_split(/\s+/, interpreter)
|
62
|
+
end
|
63
|
+
if (@code.arity > 0) and (args.size != (@code.arity - 1))
|
64
|
+
raise "Function #{@name} expects #{@code.arity} arguments, but was given #{args.size}"
|
65
|
+
end
|
66
|
+
return @code.call(interpreter.plotmaker_target, *args).to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
# Registers the given function definition
|
70
|
+
def self.register(func)
|
71
|
+
@functions ||= {}
|
72
|
+
@functions[func.name] = func
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the named function definition, or nil if there isn't
|
76
|
+
# such.
|
77
|
+
def self.named_function(name)
|
78
|
+
return @functions[name]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the functions hash
|
82
|
+
def self.functions
|
83
|
+
return @functions
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
@@ -85,6 +85,18 @@ EOH
|
|
85
85
|
|
86
86
|
EvalCommand.describe("Runs the given commands", <<EOH, GeneralGroup)
|
87
87
|
Runs the given strings as commands, as if given from a command file.
|
88
|
+
EOH
|
89
|
+
|
90
|
+
# Evaluate a series of commands.
|
91
|
+
SetCommand = Cmd.new("set", nil, "--set",
|
92
|
+
[ CmdArg.new('text'),
|
93
|
+
CmdArg.new('text') ]) do |plotmaker, variable, value|
|
94
|
+
plotmaker.interpreter.variables.define_variable(variable, value)
|
95
|
+
end
|
96
|
+
|
97
|
+
SetCommand.describe("Sets the value of a variable", <<EOH, GeneralGroup)
|
98
|
+
Sets the value of the variable (first argument) to the given second argument.
|
99
|
+
No parsing is done.
|
88
100
|
EOH
|
89
101
|
|
90
102
|
# Increases verbosity
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# general-functions.rb: useful function definitions
|
2
|
+
# copyright (c) 2014 by Vincent Fourmond
|
3
|
+
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details (in the COPYING file).
|
13
|
+
|
14
|
+
require 'ctioga2/utils'
|
15
|
+
require 'ctioga2/data/point'
|
16
|
+
|
17
|
+
module CTioga2
|
18
|
+
|
19
|
+
module Commands
|
20
|
+
|
21
|
+
FuncEval = Function.new("eval", "Evaluate Ruby code") do |pm, code|
|
22
|
+
eval(code)
|
23
|
+
end
|
24
|
+
|
25
|
+
FuncEval.describe <<EOD
|
26
|
+
Evaluate its argument as Ruby code
|
27
|
+
|
28
|
+
# a := $(eval 2 + 2)
|
29
|
+
# # a is now 4
|
30
|
+
|
31
|
+
Keep in mind that variables in @ctioga2@ work by plain text replacement.
|
32
|
+
They have no type. In particular, while this will work:
|
33
|
+
|
34
|
+
# a := 3
|
35
|
+
# b := $(eval $(a) * 3)
|
36
|
+
# # b is now 9
|
37
|
+
|
38
|
+
Doing the same kind of things with text will be somewhat not satisfying:
|
39
|
+
|
40
|
+
# a := "two words"
|
41
|
+
# b := $(eval $(a).split(/ /).first)
|
42
|
+
|
43
|
+
Running this will give the following syntax error:
|
44
|
+
|
45
|
+
@ [FATAL] (eval):1: syntax error, unexpected $end, expecting ')'
|
46
|
+
@ two words.split(/ /
|
47
|
+
@ ^ while processing line 2 in file 'c.ct2'
|
48
|
+
|
49
|
+
Doing it right would require the use of a decent amount of quotes.
|
50
|
+
EOD
|
51
|
+
|
52
|
+
FuncPoint = Function.new("point", "Get dataset point information") do |pm, what, spec, *rest|
|
53
|
+
dataset = if rest.first
|
54
|
+
pm.data_stack.stored_dataset(rest.first)
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
point = Data::DataPoint::from_text(pm, spec, dataset)
|
60
|
+
|
61
|
+
case what
|
62
|
+
when "x", "X"
|
63
|
+
point.x.to_s
|
64
|
+
when "y", "Y"
|
65
|
+
point.y.to_s
|
66
|
+
when "xy", "XY"
|
67
|
+
"%g,%g" % point.point
|
68
|
+
else
|
69
|
+
# The \ are not strictly speaking necessary, but they make
|
70
|
+
# ruby-mode happier
|
71
|
+
raise "\'#{what}\' unkown: which coordinate(s) of the point do you want ?"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
FuncPoint.describe <<EOD
|
77
|
+
|
78
|
+
Returns the requested information about the given point in a
|
79
|
+
dataset. Run this way:
|
80
|
+
|
81
|
+
# $(point x @234)
|
82
|
+
|
83
|
+
The first argument, here @x@ tells what we want to know about the
|
84
|
+
given point: its @x@ value (passing @x@), its @y@ value (passing @y@),
|
85
|
+
both its @x@ and @y@ ready to be used as coordinates for drawing
|
86
|
+
commands using @xy@. For instance, to draw a circle marker right in
|
87
|
+
the middle of the last dataset plotted, just run
|
88
|
+
|
89
|
+
# draw-marker $(point xy 0.5) Circle
|
90
|
+
|
91
|
+
The second argument specifies a dataset point, just like for
|
92
|
+
{type: data-point}.
|
93
|
+
|
94
|
+
An optional third argument specifies the dataset from which one wants
|
95
|
+
the point information. Note that the dataset can also be specified
|
96
|
+
within the second argument, but it may be more readable to do it as an
|
97
|
+
optional third. It is parsed as {type: stored-dataset}
|
98
|
+
|
99
|
+
|
100
|
+
EOD
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -102,16 +102,21 @@ EOD
|
|
102
102
|
# afterwards, actually, since an access to a plotmaker object is
|
103
103
|
# necessary.
|
104
104
|
DataPointType = CmdType.new('data-point', :data_point, <<EOD)
|
105
|
-
A point from
|
105
|
+
A point from an already-loaded Dataset. You have two ways to choose the point:
|
106
106
|
|
107
|
-
|
107
|
+
* @@13@ takes the 13th point in the last dataset;
|
108
|
+
* @0.2@ takes the point the closest to 20% of the dataset.
|
109
|
+
|
110
|
+
If you need another dataset than the last one, give its number or
|
111
|
+
named within brackets: @{-2}0.2@ is the point closest to the 20% of
|
112
|
+
the one-before-last dataset.
|
108
113
|
EOD
|
109
114
|
|
110
115
|
# A LaTeX font
|
111
116
|
LaTeXFontType = CmdType.new('latex-font', :latex_font, <<EOD)
|
112
117
|
A LaTeX font.
|
113
118
|
|
114
|
-
|
119
|
+
@todo document !
|
115
120
|
EOD
|
116
121
|
|
117
122
|
# A color map
|
@@ -16,6 +16,8 @@ require 'ctioga2/commands/commands'
|
|
16
16
|
require 'ctioga2/commands/context'
|
17
17
|
require 'ctioga2/commands/variables'
|
18
18
|
require 'ctioga2/commands/strings'
|
19
|
+
require 'ctioga2/commands/function'
|
20
|
+
require 'ctioga2/commands/general-functions'
|
19
21
|
require 'ctioga2/commands/parsers/command-line'
|
20
22
|
require 'ctioga2/commands/doc/doc'
|
21
23
|
|
@@ -192,6 +194,15 @@ module CTioga2
|
|
192
194
|
@context = ParsingContext.new
|
193
195
|
end
|
194
196
|
|
197
|
+
# Calls the given function and returns the result
|
198
|
+
def call_function(name, args)
|
199
|
+
func = Function.named_function(name)
|
200
|
+
if ! func
|
201
|
+
raise "Unkown function #{name}"
|
202
|
+
end
|
203
|
+
return func.expand(args, self)
|
204
|
+
end
|
205
|
+
|
195
206
|
|
196
207
|
# Parses and run the given command-line, sending the commands to
|
197
208
|
# the #plotmaker_target.
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# file.rb: new style file parser for ctioga2
|
2
|
-
# copyright (c) 2013 by Vincent Fourmond
|
2
|
+
# copyright (c) 2013, 2014 by Vincent Fourmond
|
3
3
|
|
4
4
|
# This program is free software; you can redistribute it and/or modify
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
@@ -115,12 +115,14 @@ module CTioga2
|
|
115
115
|
for l in parsed_lines
|
116
116
|
idx += 1
|
117
117
|
interpreter.context.parsing_file(nil, io, lines_indices[idx])
|
118
|
-
if l =~ /^\s*([a-zA-Z0-9_-]+)\s*(=|:=)\s*(.*)/
|
118
|
+
if l =~ /^\s*([a-zA-Z0-9_-]+)\s*(\??)(=|:=)\s*(.*)/
|
119
119
|
symbol = $1
|
120
|
-
value = InterpreterString.parse_until_unquoted(StringIO.new($
|
121
|
-
|
120
|
+
value = InterpreterString.parse_until_unquoted(StringIO.new($4),"\n", false)
|
121
|
+
override = !($2 == '?')
|
122
|
+
rec = (($3 == "=") ? nil : interpreter)
|
123
|
+
|
122
124
|
|
123
|
-
interpreter.variables.define_variable(symbol, value, rec)
|
125
|
+
interpreter.variables.define_variable(symbol, value, rec, override)
|
124
126
|
elsif l =~ /^\s*#/
|
125
127
|
# comment...
|
126
128
|
else
|
@@ -137,6 +137,17 @@ module CTioga2
|
|
137
137
|
if ch == ")"
|
138
138
|
push_current_element
|
139
139
|
@state = (@state == :var ? :top : :double)
|
140
|
+
elsif ch =~ /\s/
|
141
|
+
# We don't have a variable, but a function...
|
142
|
+
@accessory = InterpreterString.parse_until_unquoted(@io, ")", true)
|
143
|
+
ch = @io.getc # Slurp the closing )
|
144
|
+
ns = (@state == :var ? :top : :double)
|
145
|
+
@state = (:var ? :funcall : :dq_funcall)
|
146
|
+
push_current_element
|
147
|
+
@state = ns
|
148
|
+
## @todo Optional: instead of having a space, use a ,
|
149
|
+
## or . or # to signify different separators ? (but
|
150
|
+
## quoting makes this more-or-less unnecessary, hey ?)
|
140
151
|
else
|
141
152
|
@current_string += ch
|
142
153
|
end
|
@@ -186,6 +197,10 @@ module CTioga2
|
|
186
197
|
@parsed << [:unquoted_variable, @current_string]
|
187
198
|
when :dq_var
|
188
199
|
@parsed << [:quoted_variable, @current_string]
|
200
|
+
when :funcall
|
201
|
+
@parsed << [:unquoted_funcall, @current_string, @accessory]
|
202
|
+
when :dq_funcall
|
203
|
+
@parsed << [:quoted_funcall, @current_string, @accessory]
|
189
204
|
when :dollar
|
190
205
|
@parsed << [:unquoted, @current_string + '$']
|
191
206
|
when :dq_dollar
|
@@ -272,12 +287,12 @@ module CTioga2
|
|
272
287
|
|
273
288
|
protected
|
274
289
|
|
275
|
-
# Returns a new InterpreterString object with all variables
|
276
|
-
# expanded. _interpreter_ is the Interpreter in
|
277
|
-
# expansion takes place.
|
290
|
+
# Returns a new InterpreterString object with all variables and
|
291
|
+
# function calls expanded. _interpreter_ is the Interpreter in
|
292
|
+
# which the expansion takes place.
|
278
293
|
def expand_all_variables(interpreter)
|
279
294
|
c = []
|
280
|
-
for type, value in @contents
|
295
|
+
for type, value, args in @contents
|
281
296
|
case type
|
282
297
|
when :quoted_variable
|
283
298
|
c << [:quoted, interpreter.variables.
|
@@ -285,6 +300,12 @@ module CTioga2
|
|
285
300
|
when :unquoted_variable
|
286
301
|
c << [:unquoted, interpreter.variables.
|
287
302
|
expand_variable(value, interpreter)]
|
303
|
+
when :quoted_funcall
|
304
|
+
c << [:quoted, interpreter.
|
305
|
+
call_function(value, args)]
|
306
|
+
when :unquoted_funcall
|
307
|
+
c << [:unquoted, interpreter.
|
308
|
+
call_function(value, args)]
|
288
309
|
else
|
289
310
|
c << [type, value]
|
290
311
|
end
|
@@ -59,7 +59,11 @@ module CTioga2
|
|
59
59
|
# expanded at the time of the definition, (immediate variable),
|
60
60
|
# whereas if it stays _nil_, the variable is defined as a
|
61
61
|
# recursively defined variable.
|
62
|
-
def define_variable(name, value, interpreter = nil)
|
62
|
+
def define_variable(name, value, interpreter = nil, override = true)
|
63
|
+
if (!override) && @variables.key?(name)
|
64
|
+
# Not redefining an already defined variable.
|
65
|
+
return
|
66
|
+
end
|
63
67
|
if value.respond_to? :expand_to_string
|
64
68
|
if interpreter
|
65
69
|
value = value.expand_to_string(interpreter)
|
data/lib/ctioga2/data/point.rb
CHANGED
@@ -40,7 +40,7 @@ module CTioga2
|
|
40
40
|
# accesses the data stack.
|
41
41
|
#
|
42
42
|
# Specification: ({_dataset_})?(_relative_|@_index_)
|
43
|
-
def self.from_text(plotmaker, text)
|
43
|
+
def self.from_text(plotmaker, text, dataset = nil)
|
44
44
|
if text =~ /^(?:\s*\{([^}]+)\})?\s*(?:([.\d]+)|@(\d+))\s*$/
|
45
45
|
which = $1 || -1
|
46
46
|
if $2
|
@@ -48,7 +48,7 @@ module CTioga2
|
|
48
48
|
else
|
49
49
|
idx = $3.to_i
|
50
50
|
end
|
51
|
-
dataset
|
51
|
+
dataset ||= plotmaker.data_stack.stored_dataset(which)
|
52
52
|
|
53
53
|
if ! dataset
|
54
54
|
raise "Invalid or empty dataset: #{which}"
|
@@ -62,9 +62,17 @@ module CTioga2
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
|
66
|
-
|
65
|
+
def x
|
66
|
+
return @dataset.x.values[@index]
|
67
|
+
end
|
68
|
+
|
69
|
+
def y
|
70
|
+
return @dataset.y.values[@index]
|
71
|
+
end
|
67
72
|
|
73
|
+
def point
|
74
|
+
return [self.x, self.y]
|
75
|
+
end
|
68
76
|
|
69
77
|
# Returns the averaged X value around the datapoint
|
70
78
|
def x_val(navg = 3)
|
@@ -74,6 +74,17 @@ module CTioga2
|
|
74
74
|
return Types::Boundaries.bounds(@function.x, @function.y)
|
75
75
|
end
|
76
76
|
|
77
|
+
def can_clip?
|
78
|
+
if @curve_style.clipped or
|
79
|
+
( @curve_style.fill && @curve_style.fill.fill?) or
|
80
|
+
( parent.is_a?(Region))
|
81
|
+
return false
|
82
|
+
else
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
77
88
|
# Creates a path for the given curve. This should be defined
|
78
89
|
# with care, as it will be used for instance for region
|
79
90
|
# coloring and stroking. The function should only append to
|
@@ -86,7 +97,7 @@ module CTioga2
|
|
86
97
|
case @curve_style.path_style
|
87
98
|
when /^splines/
|
88
99
|
for f in func.split_monotonic
|
89
|
-
new_f = if
|
100
|
+
new_f = if can_clip?
|
90
101
|
f.bound_values(*bnds.extrema)
|
91
102
|
else
|
92
103
|
f.dup
|
@@ -103,7 +114,7 @@ module CTioga2
|
|
103
114
|
|
104
115
|
# Hmmmm. This may get the wrong thing if you happen to
|
105
116
|
# draw something completely outside.
|
106
|
-
if
|
117
|
+
if can_clip?
|
107
118
|
f = func.bound_values(*bnds.extrema)
|
108
119
|
else
|
109
120
|
f = func
|