ctioga2 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/COPYING +339 -0
- data/Changelog +6 -0
- data/bin/ctioga2 +26 -0
- data/lib/ctioga2/commands/arguments.rb +58 -0
- data/lib/ctioga2/commands/commands.rb +258 -0
- data/lib/ctioga2/commands/doc/doc.rb +118 -0
- data/lib/ctioga2/commands/doc/documentation-commands.rb +119 -0
- data/lib/ctioga2/commands/doc/help.rb +95 -0
- data/lib/ctioga2/commands/doc/html.rb +230 -0
- data/lib/ctioga2/commands/doc/introspection.rb +211 -0
- data/lib/ctioga2/commands/doc/man.rb +279 -0
- data/lib/ctioga2/commands/doc/markup.rb +359 -0
- data/lib/ctioga2/commands/general-commands.rb +119 -0
- data/lib/ctioga2/commands/general-types.rb +118 -0
- data/lib/ctioga2/commands/groups.rb +73 -0
- data/lib/ctioga2/commands/interpreter.rb +257 -0
- data/lib/ctioga2/commands/parsers/command-line.rb +187 -0
- data/lib/ctioga2/commands/parsers/file.rb +186 -0
- data/lib/ctioga2/commands/strings.rb +303 -0
- data/lib/ctioga2/commands/type.rb +100 -0
- data/lib/ctioga2/commands/variables.rb +101 -0
- data/lib/ctioga2/data/backends/backend.rb +260 -0
- data/lib/ctioga2/data/backends/backends.rb +39 -0
- data/lib/ctioga2/data/backends/backends/gnuplot.rb +140 -0
- data/lib/ctioga2/data/backends/backends/math.rb +121 -0
- data/lib/ctioga2/data/backends/backends/text.rb +335 -0
- data/lib/ctioga2/data/backends/description.rb +405 -0
- data/lib/ctioga2/data/backends/factory.rb +73 -0
- data/lib/ctioga2/data/backends/parameter.rb +109 -0
- data/lib/ctioga2/data/datacolumn.rb +245 -0
- data/lib/ctioga2/data/dataset.rb +233 -0
- data/lib/ctioga2/data/filters.rb +131 -0
- data/lib/ctioga2/data/merge.rb +43 -0
- data/lib/ctioga2/data/point.rb +72 -0
- data/lib/ctioga2/data/stack.rb +294 -0
- data/lib/ctioga2/graphics/coordinates.rb +73 -0
- data/lib/ctioga2/graphics/elements.rb +111 -0
- data/lib/ctioga2/graphics/elements/containers.rb +111 -0
- data/lib/ctioga2/graphics/elements/curve2d.rb +155 -0
- data/lib/ctioga2/graphics/elements/element.rb +90 -0
- data/lib/ctioga2/graphics/elements/primitive.rb +256 -0
- data/lib/ctioga2/graphics/elements/subplot.rb +140 -0
- data/lib/ctioga2/graphics/generator.rb +68 -0
- data/lib/ctioga2/graphics/legends.rb +108 -0
- data/lib/ctioga2/graphics/legends/area.rb +199 -0
- data/lib/ctioga2/graphics/legends/items.rb +183 -0
- data/lib/ctioga2/graphics/legends/provider.rb +58 -0
- data/lib/ctioga2/graphics/legends/storage.rb +65 -0
- data/lib/ctioga2/graphics/root.rb +209 -0
- data/lib/ctioga2/graphics/styles.rb +30 -0
- data/lib/ctioga2/graphics/styles/axes.rb +247 -0
- data/lib/ctioga2/graphics/styles/background.rb +122 -0
- data/lib/ctioga2/graphics/styles/base.rb +115 -0
- data/lib/ctioga2/graphics/styles/carrays.rb +53 -0
- data/lib/ctioga2/graphics/styles/curve.rb +101 -0
- data/lib/ctioga2/graphics/styles/drawable.rb +87 -0
- data/lib/ctioga2/graphics/styles/factory.rb +351 -0
- data/lib/ctioga2/graphics/styles/legend.rb +63 -0
- data/lib/ctioga2/graphics/styles/plot.rb +410 -0
- data/lib/ctioga2/graphics/styles/sets.rb +64 -0
- data/lib/ctioga2/graphics/styles/texts.rb +277 -0
- data/lib/ctioga2/graphics/subplot-commands.rb +141 -0
- data/lib/ctioga2/graphics/types.rb +188 -0
- data/lib/ctioga2/graphics/types/bijection.rb +79 -0
- data/lib/ctioga2/graphics/types/boundaries.rb +170 -0
- data/lib/ctioga2/graphics/types/boxes.rb +157 -0
- data/lib/ctioga2/graphics/types/dimensions.rb +157 -0
- data/lib/ctioga2/graphics/types/point.rb +247 -0
- data/lib/ctioga2/log.rb +97 -0
- data/lib/ctioga2/metabuilder/type.rb +316 -0
- data/lib/ctioga2/metabuilder/types.rb +39 -0
- data/lib/ctioga2/metabuilder/types/coordinates.rb +124 -0
- data/lib/ctioga2/metabuilder/types/dates.rb +43 -0
- data/lib/ctioga2/metabuilder/types/lists.rb +188 -0
- data/lib/ctioga2/metabuilder/types/numbers.rb +97 -0
- data/lib/ctioga2/metabuilder/types/strings.rb +93 -0
- data/lib/ctioga2/metabuilder/types/styles.rb +178 -0
- data/lib/ctioga2/plotmaker.rb +677 -0
- data/lib/ctioga2/postprocess.rb +115 -0
- data/lib/ctioga2/utils.rb +120 -0
- data/setup.rb +1586 -0
- metadata +144 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
# command-line.rb: a command-line parser for ctioga2
|
2
|
+
# copyright (c) 2009 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/log'
|
16
|
+
require 'ctioga2/commands/commands'
|
17
|
+
|
18
|
+
module CTioga2
|
19
|
+
|
20
|
+
Version::register_svn_info('$Revision: 2 $', '$Date: 2009-04-25 14:03:30 +0200 (Sat, 25 Apr 2009) $')
|
21
|
+
|
22
|
+
module Commands
|
23
|
+
|
24
|
+
# In this modules are classes that parse a source of commands, and
|
25
|
+
# yield to the caller the commands (Command) along with their
|
26
|
+
# unprocessed arguments.
|
27
|
+
module Parsers
|
28
|
+
|
29
|
+
# An exception raised upon redefinition of a short or long
|
30
|
+
# option.
|
31
|
+
class OptionRedefined < Exception
|
32
|
+
end
|
33
|
+
|
34
|
+
# An exception raised when the parser encounters an unkown
|
35
|
+
# option.
|
36
|
+
class OptionUnkown < Exception
|
37
|
+
end
|
38
|
+
|
39
|
+
# This class is in charge of parsing a command-line against a
|
40
|
+
# list of known commands.
|
41
|
+
class CommandLineParser
|
42
|
+
include Log
|
43
|
+
|
44
|
+
# A hash 'short-option-letter' => [number of args, Command]
|
45
|
+
attr_reader :short_options
|
46
|
+
|
47
|
+
# A hash 'long-option-name' => [number of args, Command]
|
48
|
+
attr_reader :long_options
|
49
|
+
|
50
|
+
# The list of commands
|
51
|
+
attr_reader :commands
|
52
|
+
|
53
|
+
# A [number of args, Command] for the default command, ie
|
54
|
+
# the one that applies on non-command files.
|
55
|
+
attr_reader :default_command
|
56
|
+
|
57
|
+
# Creates a CommandLineParser that will understand the
|
58
|
+
# given _commands_
|
59
|
+
def initialize(commands, default = nil)
|
60
|
+
@commands = commands
|
61
|
+
prepare_option_hashes(default)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Takes an _argv_ array representing the command-line and a
|
66
|
+
# target _intepreter_, and runs the commands found on the
|
67
|
+
# command line. Yields arguments which are not part of a
|
68
|
+
# command, or feed them to the #default_command if it was
|
69
|
+
# specified.
|
70
|
+
def parse_command_line(argv, interpreter)
|
71
|
+
# We duplicate the original array
|
72
|
+
argv = argv.dup
|
73
|
+
options = nil # currently never used.
|
74
|
+
while argv.size > 0
|
75
|
+
current = argv.shift
|
76
|
+
if current =~ /^--(.*)/ # a long option
|
77
|
+
if @long_options.key?($1)
|
78
|
+
command, arguments, options =
|
79
|
+
extract_command_arguments(argv, @long_options[$1])
|
80
|
+
|
81
|
+
interpreter.run_command(command, arguments, options)
|
82
|
+
else
|
83
|
+
raise OptionUnkown, "Long option #{current} is not known"
|
84
|
+
end
|
85
|
+
elsif current =~ /^-(.*)/ # Short options
|
86
|
+
# We do the same as above, but splitting into letters first:
|
87
|
+
short_options = $1.split('')
|
88
|
+
for short in short_options
|
89
|
+
if @short_options.key?(short)
|
90
|
+
command, arguments, options =
|
91
|
+
extract_command_arguments(argv, @short_options[short])
|
92
|
+
interpreter.run_command(command, arguments, options)
|
93
|
+
else
|
94
|
+
raise OptionUnkown, "Short option -#{short} is not known"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
if @default_command
|
99
|
+
argv.unshift current
|
100
|
+
command, arguments, options =
|
101
|
+
extract_command_arguments(argv, @default_command)
|
102
|
+
interpreter.run_command(command, arguments, options)
|
103
|
+
else
|
104
|
+
yield current
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
# Prepares the #short_options and #long_options hashes for use
|
113
|
+
# in #parse_command_line
|
114
|
+
def prepare_option_hashes(default = nil)
|
115
|
+
@short_options = {}
|
116
|
+
@long_options = {}
|
117
|
+
for cmd in @commands
|
118
|
+
short = cmd.short_option
|
119
|
+
boolean = (cmd.argument_number == 1 &&
|
120
|
+
cmd.arguments.first.type.boolean?)
|
121
|
+
if short
|
122
|
+
if @short_options.key? short
|
123
|
+
raise OptionRedefined, "Short option #{short} was already defined as command #{cmd.name}"
|
124
|
+
end
|
125
|
+
if boolean
|
126
|
+
@short_options[short] = [-1, cmd]
|
127
|
+
else
|
128
|
+
@short_options[short] = [cmd.argument_number, cmd]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
long = cmd.long_option
|
132
|
+
if long
|
133
|
+
if @long_options.key? short
|
134
|
+
raise OptionRedefined, "Long option #{long} was already defined as command #{cmd.name}"
|
135
|
+
end
|
136
|
+
if boolean
|
137
|
+
@long_options[long] = [-1, cmd]
|
138
|
+
@long_options["no-#{long}"] = [-2, cmd]
|
139
|
+
else
|
140
|
+
@long_options[long] = [cmd.argument_number, cmd]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
if default
|
145
|
+
@default_command = [default.argument_number, default]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Extract command, arguments and potential options from the
|
150
|
+
# given _argv_ array. The second argument is what is stored in
|
151
|
+
# the #short_options and #long_options hashes.
|
152
|
+
#
|
153
|
+
# Returns an array
|
154
|
+
# [command, arguments, options]
|
155
|
+
def extract_command_arguments(argv, cmd_val)
|
156
|
+
number, command = cmd_val
|
157
|
+
options = {}
|
158
|
+
|
159
|
+
# Special case for boolean arguments
|
160
|
+
if number < 0
|
161
|
+
arguments = [number == -1]
|
162
|
+
else
|
163
|
+
arguments = argv.slice!(0,number)
|
164
|
+
end
|
165
|
+
|
166
|
+
# We try and go fishing for options, in the form
|
167
|
+
# /option=stuff.
|
168
|
+
while argv.first =~ /^\/([\w-]+)=(.*)/
|
169
|
+
if command.has_option? $1
|
170
|
+
options[$1] = $2
|
171
|
+
argv.shift
|
172
|
+
else
|
173
|
+
warn "Argument #{argv.first} looks like an option, but does not match any of the command #{command.name}"
|
174
|
+
break
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
return [command, arguments, options]
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# file.rb: a file parser for ctioga2
|
2
|
+
# copyright (c) 2009 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 'stringio'
|
15
|
+
require 'ctioga2/utils'
|
16
|
+
require 'ctioga2/log'
|
17
|
+
require 'ctioga2/commands/commands'
|
18
|
+
require 'ctioga2/commands/strings'
|
19
|
+
|
20
|
+
module CTioga2
|
21
|
+
|
22
|
+
Version::register_svn_info('$Revision: 2 $', '$Date: 2009-04-25 14:03:30 +0200 (Sat, 25 Apr 2009) $')
|
23
|
+
|
24
|
+
module Commands
|
25
|
+
|
26
|
+
module Parsers
|
27
|
+
|
28
|
+
# Raised when EOF is encountered during a symbol parsing
|
29
|
+
class UnterminatedSymbol < Exception
|
30
|
+
end
|
31
|
+
|
32
|
+
# Unexepected character.
|
33
|
+
class UnexpectedCharacter < Exception
|
34
|
+
end
|
35
|
+
|
36
|
+
# Syntax error
|
37
|
+
class ParserSyntaxError < Exception
|
38
|
+
end
|
39
|
+
|
40
|
+
# This class is in charge of parsing a command-line against a list
|
41
|
+
# of known commands.
|
42
|
+
class FileParser
|
43
|
+
|
44
|
+
include Log
|
45
|
+
|
46
|
+
# Runs a command file targeting the given _interpreter_.
|
47
|
+
def self.run_command_file(file, interpreter)
|
48
|
+
FileParser.new.run_command_file(file, interpreter)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Runs the given command strings
|
52
|
+
def self.run_commands(strings, interpreter)
|
53
|
+
FileParser.new.run_commands(strings, interpreter)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Runs a command file targeting the given _interpreter_.
|
57
|
+
def run_command_file(file, interpreter)
|
58
|
+
f = open(file)
|
59
|
+
parse_io_object(f, interpreter)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Runs the given command strings
|
63
|
+
def run_commands(strings, interpreter)
|
64
|
+
io = StringIO.new(strings)
|
65
|
+
parse_io_object(io, interpreter)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Parses a given _io_ object, sending commands/variable
|
69
|
+
# definitions to the given _interpreter_.
|
70
|
+
def parse_io_object(io, interpreter)
|
71
|
+
# The process is simple: we look for symbols and
|
72
|
+
# corresponding syntax element: parentheses or assignments
|
73
|
+
while(1)
|
74
|
+
symbol = up_to_next_symbol(io)
|
75
|
+
break if not symbol
|
76
|
+
|
77
|
+
while(1)
|
78
|
+
c = io.getc
|
79
|
+
if ! c # EOF
|
80
|
+
raise ParserSyntaxError, "Expecting something after symbol #{symbol}"
|
81
|
+
end
|
82
|
+
ch = c.chr
|
83
|
+
if ch =~ /\s/ # blank...
|
84
|
+
next
|
85
|
+
elsif ch == '(' # beginning of a function call
|
86
|
+
# Parse string:
|
87
|
+
str = InterpreterString.parse_until_unquoted(io,")")
|
88
|
+
# Now, we need to split str.
|
89
|
+
args = str.expand_and_split(/\s*,\s*/, interpreter)
|
90
|
+
|
91
|
+
cmd = interpreter.get_command(symbol)
|
92
|
+
real_args = args.slice!(0, cmd.argument_number)
|
93
|
+
# And now the options:
|
94
|
+
options = {}
|
95
|
+
|
96
|
+
# Problem: the space on the right of the = sign is
|
97
|
+
# *significant*.
|
98
|
+
for o in args
|
99
|
+
if o =~ /^\s*([\w-]+)\s*=(.*)/
|
100
|
+
if cmd.has_option? $1
|
101
|
+
options[$1] = $2
|
102
|
+
else
|
103
|
+
error "Command #{cmd.name} does not take option #{$1}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
interpreter.run_command(cmd, real_args, options)
|
109
|
+
io.getc # Slurp up the )
|
110
|
+
break
|
111
|
+
elsif ch == ':' # Assignment
|
112
|
+
c = io.getc
|
113
|
+
if ! c # EOF
|
114
|
+
raise ParserSyntaxError, "Expecting = after :"
|
115
|
+
end
|
116
|
+
ch = c.chr
|
117
|
+
if ch != '='
|
118
|
+
raise ParserSyntaxError, "Expecting = after :"
|
119
|
+
end
|
120
|
+
str = InterpreterString.parse_until_unquoted(io,"\n", false)
|
121
|
+
interpreter.variables.define_variable(symbol, str,
|
122
|
+
interpreter)
|
123
|
+
break
|
124
|
+
elsif ch == '='
|
125
|
+
str = InterpreterString.parse_until_unquoted(io,"\n", false)
|
126
|
+
interpreter.variables.define_variable(symbol, str, nil)
|
127
|
+
break
|
128
|
+
else
|
129
|
+
raise UnexpectedCharacter, "Did not expect #{ch} after #{symbol}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
136
|
+
|
137
|
+
SYMBOL_CHAR_REGEX = /[a-zA-Z0-9_-]/
|
138
|
+
|
139
|
+
# Parses the _io_ stream up to and including the next
|
140
|
+
# symbol. Only white space or comments may be found on the
|
141
|
+
# way. This function returns the symbol.
|
142
|
+
#
|
143
|
+
# Symbols are composed of the alphabet SYMBOL_CHAR_REGEX.
|
144
|
+
def up_to_next_symbol(io)
|
145
|
+
|
146
|
+
symbol = nil # As long as no symbol as been started
|
147
|
+
# it will stay nil.
|
148
|
+
while(1)
|
149
|
+
c = io.getc
|
150
|
+
if ! c # EOF
|
151
|
+
if symbol
|
152
|
+
raise UnterminatedSymbol, "EOF reached during symbol parsing"
|
153
|
+
else
|
154
|
+
# File is finished and we didn't meet any symbol.
|
155
|
+
# Nothing to do !
|
156
|
+
return nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
ch = c.chr
|
160
|
+
if symbol # We have started
|
161
|
+
if ch =~ SYMBOL_CHAR_REGEX
|
162
|
+
symbol += ch
|
163
|
+
else
|
164
|
+
io.ungetc(c)
|
165
|
+
return symbol
|
166
|
+
end
|
167
|
+
else
|
168
|
+
if ch =~ SYMBOL_CHAR_REGEX
|
169
|
+
symbol = ch
|
170
|
+
elsif ch =~ /\s/
|
171
|
+
# Nothing
|
172
|
+
elsif ch == '#'
|
173
|
+
io.gets
|
174
|
+
else
|
175
|
+
raise UnexpectedCharacter, "Unexpected character: #{ch}, when looking for a symbol"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
@@ -0,0 +1,303 @@
|
|
1
|
+
# strings.rb: the core of the file-based interpretation: strings !
|
2
|
+
# copyright (c) 2009 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 'stringio' # For debugging purposes
|
16
|
+
|
17
|
+
module CTioga2
|
18
|
+
|
19
|
+
Version::register_svn_info('$Revision: 2 $', '$Date: 2009-04-25 14:03:30 +0200 (Sat, 25 Apr 2009) $')
|
20
|
+
|
21
|
+
module Commands
|
22
|
+
|
23
|
+
class UnterminatedString < Exception
|
24
|
+
end
|
25
|
+
|
26
|
+
# All variables and arguments are only strings. There is no type
|
27
|
+
# in ctioga, at least not on the variable/strings level. Ruby
|
28
|
+
# functions of course work with typed objects, but all user input
|
29
|
+
# is taken to be a string and is converted using the
|
30
|
+
# CTioga2::Metabuilder::Type system.
|
31
|
+
#
|
32
|
+
# A string is however not a simple Ruby String, as it can contain
|
33
|
+
# elements that are expanded, and it needs to provide a way to be
|
34
|
+
# split into substrings for arguments/options processing.
|
35
|
+
#
|
36
|
+
# A string is composed of an array of [type, value, ... ? ]
|
37
|
+
# arrays, where _type_ can have the following meanings:
|
38
|
+
# * :unquoted : an unquoted string (can be split)
|
39
|
+
# * :unquoted_variable : an unquoted variable (replacement text
|
40
|
+
# can be split)
|
41
|
+
# * :quoted : a quoted string (cannot be split)
|
42
|
+
# * :quoted_variable : a quoted variable (cannot be split)
|
43
|
+
class InterpreterString
|
44
|
+
|
45
|
+
# A small lexical parser. It's job is to carry out the function
|
46
|
+
# of InterpreterString.parse_until_unquoted.
|
47
|
+
class LexicalAnalyzer
|
48
|
+
# The state of the parser:
|
49
|
+
# * :start -> first starting elements
|
50
|
+
# * :top -> toplevel
|
51
|
+
# * :single -> in a single quoted string
|
52
|
+
# * :double -> in a double quoted string
|
53
|
+
# * :dollar -> last element was a dollar at top-level
|
54
|
+
# * :dq_dollar -> last element was an unescaped dollar within
|
55
|
+
# a double quoted string
|
56
|
+
# * :escape -> last element was an unescaped escape char
|
57
|
+
# within a double-quoted string.
|
58
|
+
# * :var -> in a $(variable)
|
59
|
+
# * :dq_var -> in a $(variable) within a double-quoted string
|
60
|
+
attr_accessor :state
|
61
|
+
|
62
|
+
# The current string result, as described in InterpreterString
|
63
|
+
attr_accessor :parsed
|
64
|
+
|
65
|
+
# The current object on the way of being parsed
|
66
|
+
attr_accessor :current_string
|
67
|
+
|
68
|
+
# The io device with which the parser is interacting.
|
69
|
+
attr_accessor :io
|
70
|
+
|
71
|
+
# The terminating element
|
72
|
+
attr_accessor :term
|
73
|
+
|
74
|
+
# Initializes the parser.
|
75
|
+
def initialize(io, term)
|
76
|
+
@io = io
|
77
|
+
@term = term
|
78
|
+
end
|
79
|
+
|
80
|
+
# Parse the string from the _io_ object
|
81
|
+
def parse(eoerror = true)
|
82
|
+
@state = :start
|
83
|
+
@parsed = []
|
84
|
+
@current_string = ''
|
85
|
+
|
86
|
+
i = -1
|
87
|
+
while(1)
|
88
|
+
c = @io.getc
|
89
|
+
if ! c # EOF
|
90
|
+
if eoerror
|
91
|
+
raise UnterminatedString, "EOF reached before the end of this string"
|
92
|
+
else
|
93
|
+
push_current_element
|
94
|
+
return @parsed
|
95
|
+
end
|
96
|
+
end
|
97
|
+
# Convert the integer to a string.
|
98
|
+
ch = c.chr
|
99
|
+
i += 1
|
100
|
+
if (@state == :start || @state == :top) and
|
101
|
+
(term.include?(ch)) # Finished
|
102
|
+
push_current_element
|
103
|
+
@io.ungetc(c) # We push back the last char.
|
104
|
+
return @parsed
|
105
|
+
end
|
106
|
+
|
107
|
+
# puts "#{@state.inspect} -- #{ch}"
|
108
|
+
|
109
|
+
# We skip white space at the beginning of the string.
|
110
|
+
if @state == :start
|
111
|
+
# Skip white space
|
112
|
+
if ! (ch =~ /\s/)
|
113
|
+
@state = :top
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
case @state
|
118
|
+
when :escape
|
119
|
+
# Evaluating escape chars
|
120
|
+
@current_string += eval("\"\\#{ch}\"")
|
121
|
+
@state = :double
|
122
|
+
when :dollar, :dq_dollar
|
123
|
+
@state = (@state == :dollar ? :top : :double)
|
124
|
+
if ch == '(' # Beginning of a variable within a
|
125
|
+
# quoted string
|
126
|
+
push_current_element
|
127
|
+
@state = (@state == :top ? :var : :dq_var)
|
128
|
+
else
|
129
|
+
@current_string += "$#{ch}"
|
130
|
+
end
|
131
|
+
when :single # The simplest string
|
132
|
+
if ch == "'" # End of string
|
133
|
+
push_current_element
|
134
|
+
@state = :top
|
135
|
+
else
|
136
|
+
@current_string += ch
|
137
|
+
end
|
138
|
+
when :var, :dq_var
|
139
|
+
if ch == ")"
|
140
|
+
push_current_element
|
141
|
+
@state = (@state == :var ? :top : :double)
|
142
|
+
else
|
143
|
+
@current_string += ch
|
144
|
+
end
|
145
|
+
when :top
|
146
|
+
if ch == "'" # We start a single-quoted string
|
147
|
+
push_current_element
|
148
|
+
@state = :single
|
149
|
+
elsif ch == '$' # Dollar state
|
150
|
+
@state = :dollar
|
151
|
+
elsif ch == '"'
|
152
|
+
push_current_element
|
153
|
+
@state = :double
|
154
|
+
elsif ch == '#' # A comment: we read until end-of-line
|
155
|
+
@io.gets # and ignore the results
|
156
|
+
else
|
157
|
+
@current_string += ch
|
158
|
+
end
|
159
|
+
when :double
|
160
|
+
if ch == '"' # (necessarily unquoted)
|
161
|
+
push_current_element
|
162
|
+
@state = :top
|
163
|
+
elsif ch == '$'
|
164
|
+
@state = :dq_dollar
|
165
|
+
elsif ch == "\\"
|
166
|
+
@state = :escape
|
167
|
+
else
|
168
|
+
@current_string += ch
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
# Pushes the element currently being parsed unto the
|
176
|
+
# result
|
177
|
+
def push_current_element
|
178
|
+
if @current_string.size == 0
|
179
|
+
return
|
180
|
+
end
|
181
|
+
case @state
|
182
|
+
when :top
|
183
|
+
# We push an unquoted string
|
184
|
+
@parsed << [:unquoted, @current_string]
|
185
|
+
when :single, :double
|
186
|
+
@parsed << [:quoted, @current_string]
|
187
|
+
when :var
|
188
|
+
@parsed << [:unquoted_variable, @current_string]
|
189
|
+
when :dq_var
|
190
|
+
@parsed << [:quoted_variable, @current_string]
|
191
|
+
when :dollar
|
192
|
+
@parsed << [:unquoted, @current_string + '$']
|
193
|
+
when :dq_dollar
|
194
|
+
@parsed << [:quoted, @current_string + '$']
|
195
|
+
when :escape
|
196
|
+
@parsed << [:quoted, @current_string + "\\" ]
|
197
|
+
when :start
|
198
|
+
# Empty string at the beginning. Nothing interesting, move
|
199
|
+
# along !
|
200
|
+
else
|
201
|
+
raise "Fatal bug of the lexical analyzer here : unkown state"
|
202
|
+
end
|
203
|
+
# Flush current string
|
204
|
+
@current_string = ""
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# The array of the aforementioned [_type_, _value_, ...] arrays
|
211
|
+
attr_accessor :contents
|
212
|
+
|
213
|
+
# Read the given _io_ stream until an unquoted element of
|
214
|
+
# _term_ is found. Returns the parsed InterpreterString.
|
215
|
+
# The terminating element is pushed back onto the stream.
|
216
|
+
#
|
217
|
+
# If _io_ encounters EOF before the parsing is finished, an
|
218
|
+
# UnterminatedString exception is raised, unless the _eoerror_
|
219
|
+
# is false.
|
220
|
+
#
|
221
|
+
# This is the *central* function of the parsing of files.
|
222
|
+
def self.parse_until_unquoted(io, term, eoerror = true)
|
223
|
+
string = InterpreterString.new
|
224
|
+
string.contents = LexicalAnalyzer.new(io, term).parse(eoerror)
|
225
|
+
return string
|
226
|
+
end
|
227
|
+
|
228
|
+
def initialize(contents = [])
|
229
|
+
@contents = contents
|
230
|
+
end
|
231
|
+
|
232
|
+
# Fully expand the InterpreterString to obtain a String object.
|
233
|
+
# _interpreter_ is the Interpreter object in which the expansion
|
234
|
+
# takes place.
|
235
|
+
def expand_to_string(interpreter)
|
236
|
+
pre_expanded = expand_all_variables(interpreter)
|
237
|
+
retval = ""
|
238
|
+
for type, value in pre_expanded.contents
|
239
|
+
retval += value
|
240
|
+
end
|
241
|
+
return retval
|
242
|
+
end
|
243
|
+
|
244
|
+
# Splits the string, after expansion, in the *unquoted* parts,
|
245
|
+
# where _re_ matches, and returns the corresponding array of
|
246
|
+
# strings.
|
247
|
+
#
|
248
|
+
# An empty expanded string expands to a null array.
|
249
|
+
def expand_and_split(re, interpreter)
|
250
|
+
pre_expanded = expand_all_variables(interpreter)
|
251
|
+
retval = []
|
252
|
+
cur_str = ""
|
253
|
+
for type, value in pre_expanded.contents
|
254
|
+
case type
|
255
|
+
when :quoted
|
256
|
+
cur_str += value
|
257
|
+
when :unquoted
|
258
|
+
tmp = value.split(re, -1)
|
259
|
+
cur_str += tmp[0]
|
260
|
+
# Push splitted stuff here:
|
261
|
+
while tmp.size > 1
|
262
|
+
retval << cur_str
|
263
|
+
tmp.shift
|
264
|
+
cur_str = tmp[0]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
retval << cur_str
|
269
|
+
if (retval.size == 1) && (retval.first == "")
|
270
|
+
return []
|
271
|
+
end
|
272
|
+
return retval
|
273
|
+
end
|
274
|
+
|
275
|
+
protected
|
276
|
+
|
277
|
+
# Returns a new InterpreterString object with all variables
|
278
|
+
# expanded. _interpreter_ is the Interpreter in which the
|
279
|
+
# expansion takes place.
|
280
|
+
def expand_all_variables(interpreter)
|
281
|
+
c = []
|
282
|
+
for type, value in @contents
|
283
|
+
case type
|
284
|
+
when :quoted_variable
|
285
|
+
c << [:quoted, interpreter.variables.
|
286
|
+
expand_variable(value, interpreter)]
|
287
|
+
when :unquoted_variable
|
288
|
+
c << [:unquoted, interpreter.variables.
|
289
|
+
expand_variable(value, interpreter)]
|
290
|
+
else
|
291
|
+
c << [type, value]
|
292
|
+
end
|
293
|
+
end
|
294
|
+
return InterpreterString.new(c)
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|