ctioga2 0.8 → 0.9

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.
@@ -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 a Dataset.
105
+ A point from an already-loaded Dataset. You have two ways to choose the point:
106
106
 
107
- \todo document ;-)...
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
- \todo document !
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($3),"\n", false)
121
- rec = (($2 == "=") ? nil : interpreter)
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 which the
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)
@@ -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 = plotmaker.data_stack.stored_dataset(which)
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
- # @todo functions returning xy values + slope for the given
66
- # datapoint. For each, possibility to average over several points.
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 @curve_style.clipped
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 @curve_style.clipped
117
+ if can_clip?
107
118
  f = func.bound_values(*bnds.extrema)
108
119
  else
109
120
  f = func