source: SHX/trunk/src/SeismicHandler/core/parser.py @ 232

Revision 232, 13.7 KB checked in by marcus, 13 years ago (diff)
  • command sdel done (solved #23)
  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Rev Id Date
Line 
1# -*- coding: utf-8 -*-
2
3#    This file is part of Seismic Handler eXtended (SHX).
4#
5#    SHX is free software: you can redistribute it and/or modify
6#    it under the terms of the GNU Lesser General Public License as published
7#    by the Free Software Foundation, either version 3 of the License, or
8#    (at your option) any later version.
9#
10#    SHX is distributed in the hope that it will be useful,
11#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#    GNU Lesser General Public License for more details.
14#
15#    You should have received a copy of the GNU Lesser General Public License
16#    along with SHX.  If not, see <http://www.gnu.org/licenses/>.
17
18"""
19These classes cover the processing of SH scripting language.
20
21- "script" processes a stream (open file, stringIO, ...) of commands
22- "parse" parses one command
23- "translate" manages variable substitution
24- "symbol" cares for local and global symbols
25
26It's intended to be compatible with the original C code SH. Please file a ticket
27at http://www.seismic-handler.org/ if there's something wrong or missing.
28"""
29
30import re
31import inspect
32from SeismicHandler.core import Settings, Logging
33import SeismicHandler.commands as commands
34
35class parse(object):
36    """
37    Parse SH scripting language.
38
39    After successful parsing these attributes are set:
40    - input (original input),
41    - input_conv (maybe changed to upper case, depends on global setting),
42    - parameter
43    - qualifier
44    - suspectedFilename (unix filenames contain slashes, it just a guess)
45
46    These values can be also accessed via:
47    - class attribute "parsed" (dictionary)
48    - class attribute with "shx_" prefix (e.g. shx_input)
49
50    The parser analyses the command string:
51    >>> cmd = 'COMMAND /SWITCH1 PARAMETER1 /SWITCH2=4 PARAMETER2'
52    >>> x = parse(cmd).parsed
53
54    The parsed command name is always lower case. It's used for comparision with
55    the "provides" attribute of the command classes.
56    >>> x["shx_command"]
57    'command'
58
59    Switches may occur at any position, but are commonly placed following the
60    command name:
61    >>> sorted(x["shx_qualifiers"].keys())
62    ['SWITCH1', 'SWITCH2']
63    >>> x["shx_qualifiers"]["SWITCH1"]
64    True
65    >>> x["shx_qualifiers"]["SWITCH2"]
66    '4'
67
68    Parameter are returned as list in the order of occurrence:
69    >>> x["shx_parameter"]
70    ['PARAMETER1', 'PARAMETER2']
71
72    One new feature is that the algorithm guesses a file name (slashes normally
73    indicate qualifiers). This only works, if the switches follow directly after
74    the command name!
75    >>> cmd = 'DELAY+SUM/BLA/FOO=1 BAR /tmp/test /SW FOO'
76    >>> x = parse(cmd).parsed
77    >>> x["shx_parameter"]
78    ['BAR', 'FOO']
79    >>> x["shx_qualifiers"]["FOO"]
80    '1'
81
82    Please note that the "suspected" file name is also present as switch:
83    >>> sorted(x["shx_qualifiers"].keys())
84    ['BLA', 'FOO', 'SW', 'TEST', 'TMP']
85    >>> x["shx_suspectedFilename"]
86    '/TMP/TEST'
87
88    If more than one "possible filename is given, only the first one is
89    recognized. All following are only processed as qualifiers.
90    >>> x = parse('COMMAND /tmp/x /tmp/y').parsed
91    >>> x["shx_suspectedFilename"]
92    '/TMP/X'
93
94    The semicolon is an alternative separator that is mostly used to skip
95    parameters:
96    >>> x = parse("ECHO;;2 3 4 ;; 6 7").parsed
97    >>> x["shx_parameter"]
98    ['', '2', '3', '4', '', '6', '7']
99
100    >>> x = parse('ECHO;;;;foo;bar;;').parsed
101    >>> x["shx_parameter"]
102    ['', '', '', 'FOO', 'BAR', '']
103    """
104
105    # regular expressions for parsing
106    re_cmd = "[+\w]+"
107    re_qual = "/[\w=]+"
108    re_file = " (/\w+)+ ?"
109
110    def __init__(self, input):
111        self.input = input
112
113        if Settings.swCapconv:
114            input = input.upper()
115
116        c = re.compile(self.re_cmd)
117        q = re.compile(self.re_qual)
118        f = re.compile(self.re_file)
119
120        cmd = c.search(input).group(0)
121        qual = q.findall(input)
122
123        # guess file
124        try:
125            sfile = f.search(input).group(0).strip()
126        except:
127            sfile = None
128
129        qualifiers = {}
130        # remove cmd and qualifiers from string
131        cmd_par = input.replace(cmd, "")
132        for qq in qual:
133            cmd_par = cmd_par.replace(qq, "")
134
135            # build dict of qualifiers
136            i = qq.split("=")
137            if len(i) > 1:
138                qualifiers[i[0][1:]] = i[1]
139            else:
140                qualifiers[i[0][1:]] = True
141
142        par = cmd_par.split()
143
144        # check for semicolon placeholders
145        parameter = []
146        for j in par:
147            if ";" in j:
148                j = j.split(";")
149
150                # correct leading and trailing
151                if j[:2] == ['','']:
152                    j = j[1:]
153                if j[-2:] == ['','']:
154                    j = j[:-1]
155
156                parameter.extend(j)
157            else:
158                parameter.append(j)
159
160        self.__parsed = {
161                "shx_input": self.input,
162                "shx_input_conv": input,
163                "shx_command": cmd.lower(), # command always in lower case
164                "shx_parameter": parameter,
165                "shx_qualifiers": qualifiers,
166                "shx_suspectedFilename": sfile,
167        }
168
169    def __getattr__(self, name):
170        if name.startswith("shx_"):
171            return self.__parsed[name]
172
173        if "shx_%s" % name in self.__parsed.keys():
174            return self.__parsed["shx_%s" % name]
175
176        if name == "parsed":
177            return self.__parsed
178
179        try:
180            return self.__dict__[name]
181        except KeyError:
182            raise AttributeError(name)
183
184class script(object):
185    """
186    Read commands from stream.
187
188    GOTO targets are cached and removed from stream so that any GOTO command
189    must be handled inside this class.
190
191    Also the IF condition is evaluated here.
192
193    All other commands are promoted to their handler or treated as new script.
194
195    For testing purposes we use a stream and fill it with commands. Also a
196    symbol set is needed. To echo the commands, the global "Echo" switch must
197    be turned on.
198    >>> from StringIO import StringIO
199    >>> strm = StringIO("echo line1\\necho line2\\n")
200    >>> symb = symbol()
201    >>> Settings.swEcho = True
202    >>> x = script(strm, symb)
203    >>> x.run()
204    echo line1
205    echo line2
206
207    It's possible to add more commands at runtime. The script will continue
208    there.
209    >>> x.feed(StringIO("echo line3\\n"))
210    >>> x.run()
211    echo line3
212    """
213
214    def __init__(self, input, symbols, parameters=None):
215        """
216        Read in stream, skip empty lines and comments. Remember GOTO targets.
217
218        Input parameters are:
219        - stream object having readline function
220        - symbol class instance (containing global and local variables
221        """
222
223        self.content = []
224        self.targets = {}
225
226        self.pointer = 0
227        self.symbols = symbols
228
229        try:
230            if hasattr(input, "read"):
231                stream = input
232            elif type(input) == str:
233                stream = open("input", "r")
234            _ = stream
235        except:
236            # cannot open
237            raise Exception("Input not readable!")
238
239        # Cache self-handled commands. These methods begin with "command".
240        commands = []
241        for i in inspect.getmembers(self, lambda x: inspect.ismethod(x)):
242            if i[0].startswith("command"):
243                commands.append(i[0][7:].lower())
244
245        self.commands = commands
246
247        self.feed(stream)
248
249    def feed(self, stream):
250        """
251        Method for populating command list.
252
253        This method can be run several times in order to run commands from
254        interactive input. The local symbol set is hereby not changed.
255
256        Execution of commands will automatically continue after the last
257        command.
258
259        So it's no possible to use loops also in interactive scripts. But be
260        warned: It's not easy to get a proper exit GOTO target!
261        """
262
263        content = self.content
264        targets = self.targets
265        skipped = 0
266
267        for line, cmdstr in enumerate(stream):
268            cmdstr = cmdstr.strip()
269
270            # skip empty lines or comments
271            if len(cmdstr) and cmdstr[0] not in "!#-":
272                # remember goto target line?
273                if cmdstr.split()[0][-1] == ":":
274                    targets[cmdstr.upper()] = line - skipped
275
276                content.append(cmdstr)
277            else:
278                skipped += 1
279
280        self.content = content
281        self.targets = targets
282
283    def run(self):
284        while True:
285            try:
286                cmd = self.next()
287
288                if Settings.swEcho:
289                    print cmd
290
291                cmd = parse(cmd).parsed
292
293                # Execute command...
294                if cmd["shx_command"] in commands.list:
295                    # also supply recent symbolset
296                    commands.list[cmd["shx_command"]](shx_symbols=self.symbols, \
297                                 *cmd["shx_parameter"], **cmd["shx_qualifiers"])
298
299                # .. or start script.
300                else:
301                    symb = symbol()
302                    try:
303                        ns = script(cmd["shx_command"], symb)
304                        ns.run()
305                    except Exception, e:
306                        msg = "Cannot run script '%s'!" % cmd["shx_command"]
307
308                        # Respect global settings:
309                        if Settings.swSherrstop:
310                            import sys
311                            print >> sys.stderr, msg
312                            quit()
313
314                        if Settings.swCmderrstop:
315                            raise Exception(msg)
316
317                        if not Settings.swNoerrmsg:
318                            import warnings
319                            warnings.warn(msg)
320
321            except StopIteration:
322                break
323
324    def next(self):
325        """
326        Iterate over commands.
327        """
328
329        try:
330            # skip goto targets
331            while self.pointer in self.targets.values():
332                self.pointer += 1
333
334            pnt = self.pointer
335            self.pointer += 1
336
337            return self.content[pnt]
338        except IndexError:
339            # lower pointer
340            self.pointer -= 1
341            raise StopIteration
342
343    def commandGoto(self, target):
344        try:
345            self.pointer = self.targets[target.upper()]
346        except KeyError:
347            raise NameError(target.upper())
348
349    def commandIf(self, cmd):
350        pass
351
352    def IfGoto(self, cmd):
353        # cast format
354        cast = {
355                "S": str,
356                "I": int,
357                "R": float
358                }
359
360        cmp, check = cmd.p[2].value, cmd.p[2].value[:2]
361
362        var1 = cast[cmp[-1]](cmd.p[1].value)
363        var2 = cast[cmp[-1]](cmd.p[3].value)
364
365        # comparison
366        try:
367            comp = {
368                    "EQ": var1.__eq__,
369                    "NE": var1.__ne__,
370                    "GT": var1.__gt__,
371                    "GE": var1.__ge__,
372                    "LT": var1.__lt__,
373                    "LE": var1.__le__,
374                    }
375        except AttributeError:
376            # in python 2.5 integers have no __eq__, __gt__, ... methods :(
377            comp = {
378                    "EQ": lambda x: var1 == x,
379                    "NE": lambda x: var1 != x,
380                    "GT": lambda x: var1 > x,
381                    "GE": lambda x: var1 >= x,
382                    "LT": lambda x: var1 < x,
383                    "LE": lambda x: var1 <= x,
384                    }
385
386        # check condition
387        if comp[check](var2):
388            self.Goto(cmd.p[5].value)
389
390class translate(object):
391    """
392    Translate variables in command.
393    """
394    def __init__(self):
395        pass
396
397class symbol(object):
398    """
399    This class holds locals symbols. Access to global symbols is granted.
400
401    Important note: All symbol names are converted into upper case!
402
403    >>> s = symbol()
404    >>> s.foo = 1
405    >>> s.foo
406    1
407    >>> s.setGlobal("bar", "qux")
408    >>> s.bar
409    'qux'
410
411    Local symbols mask global ones! This can be undone by deleting the symbol.
412    >>> s.bar = 5
413    >>> s.bar
414    5
415    >>> del s.bar
416    >>> s.bar
417    'qux'
418
419    If no local symbol exists, the global one will be removed.
420    >>> del s.bar
421    >>> s.bar #doctest: +ELLIPSIS
422    Traceback (most recent call last):
423    ...
424    Exception: Symbol BAR not found!
425
426    If one wants to delete a global symbol but keep the local symbol:
427    >>> s.setGlobal("bar", "global")
428    >>> s.bar
429    'global'
430    >>> s.bar = "local"
431    >>> s.deleteGlobal("bar")
432    >>> s.bar
433    'local'
434    """
435
436    def __init__(self):
437        self.__dict__["__globals"] = Settings.Globals
438
439    def __getattr__(self, name):
440        name = name.upper()
441        try:
442            return self.__dict__[name]
443        except KeyError:
444            pass
445
446        try:
447            return self.__dict__["__globals"][name]
448        except KeyError:
449            pass
450
451        raise Exception("Symbol %s not found!" % name)
452
453    def __setattr__(self, name, value=None):
454        name = name.upper()
455
456        self.__dict__[name] = value
457
458    def __delattr__(self, name):
459        name = name.upper()
460
461        # delete local symbol
462        try:
463            del self.__dict__[name]
464        except KeyError:
465            pass
466        else:
467            return
468
469        try:
470            del self.__dict__["__globals"][name]
471        except KeyError:
472            pass
473        else:
474            return
475
476        raise Exception("Symbol %s not found!" % name)
477
478    def setGlobal(self, name, value=None):
479        name = name.upper()
480
481        self.__dict__["__globals"][name] = value
482
483    def deleteGlobal(self, name):
484        name = name.upper()
485
486        del self.__dict__["__globals"][name]
487
488if __name__ == "__main__":
489    import doctest
490    doctest.testmod(exclude_empty=True)
Note: See TracBrowser for help on using the repository browser.