ctioga2 0.8 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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