source: trunk/coopr/sucasa/ampl_parser.py @ 1768

Last change on this file since 1768 was 1768, checked in by wehart, 11 years ago

Rework of Coopr to use the new PyUtilib? package decomposition.

NOTE: to use Coopr with this update, we need to work with a new version of coopr_install.

File size: 11.7 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11#
12# TODOs
13#
14# Handle comment lines
15# Print constraint names "subject to" instead of "subject" "to"
16# Parse dimensions of sets, params, vars, etc...
17#
18
19import re
20import os
21import ply.lex as lex
22import ply.yacc as yacc
23from ampl_info import AmplInfo
24from pyutilib.misc import flatten
25from pyutilib.ply import t_newline, t_ignore, _find_column, p_error, ply_init
26
27_parse_info = None
28_comment_list = []
29_parsedata = None
30debugging = False
31
32reserved = {
33    'check' : 'CHECK',
34    'data' : 'DATA',
35    'set' : 'SET',
36    'param' : 'PARAM',
37    'var' : 'VAR',
38    'minimize' : 'MIN',
39    'maximize' : 'MAX',
40    'subject' : 'SUBJECT',
41    's.t.' : 'ST',
42    'to' : 'TO',
43    'in' : 'IN',
44    'within' : 'WITHIN',
45    'sum' : 'SUM',
46    'node' : 'NODE',
47    'arc' : 'ARC',
48    'dimen' : 'DIMEN',
49    'integer' : 'INTEGER',
50    'binary' : 'BINARY',
51    'symbolic' : 'SYMBOLIC',
52    'Integers' : 'INTEGERS',
53    'Reals' : 'REALS'
54}
55
56# Token names
57tokens = [
58    "COMMA",
59    "LBRACE",
60    "RBRACE",
61    "NUMBER",
62    "SEMICOLON",
63    "COLON",
64    "COLONEQ",
65    "LBRACKET",
66    "RBRACKET",
67    "LPAREN",
68    "RPAREN",
69    "LT",
70    "GT",
71    "LTEQ",
72    "GTEQ",
73    "LTLT",
74    "GTGT",
75    "LTGT",
76    "RANGE",
77    "WORD",
78    "NONWORD",
79] + reserved.values()
80
81# Regular expression rules
82t_COMMA     = r","
83t_LBRACKET  = r"\["
84t_RBRACKET  = r"\]"
85t_NUMBER    = r"[0-9]+(\.[0-9]+){0,1}"
86t_SEMICOLON = r";"
87t_COLON     = r":"
88t_COLONEQ   = r":="
89t_LT        = r"<"
90t_GT        = r">"
91t_LTEQ      = r"<="
92t_GTEQ      = r">="
93t_LTLT      = r"<<"
94t_GTGT      = r">>"
95t_LTGT      = r"<>"
96t_LBRACE    = r"{"
97t_RBRACE    = r"}"
98t_LPAREN    = r"\("
99t_RPAREN    = r"\)"
100t_RANGE     = r"\.\."
101
102# Discard comments
103def t_COMMENT(t):
104    r'\#[^\n]*'
105    global _comment_list
106    _comment_list.append(t.value)
107
108def process_comment_list():
109    global _comment_list
110    global _parse_info
111
112    for comment in _comment_list:
113        tmp = re.split('[ \t]+',comment.strip())
114        if len(tmp) > 3 and tmp[1] == "SUCASA":
115            if tmp[2] == "SYMBOLS:":
116                _parse_info.exported_symbols = set(tmp[3:])
117            else:
118                _parse_info.add_mapfile_declaration(tmp[3]," ".join(tmp[2:]))
119        if len(tmp) > 2 and tmp[0] == "#SUCASA":
120            if tmp[1] == "SYMBOLS:":
121                _parse_info.exported_symbols = set(tmp[2:])
122            else:
123                _parse_info.add_mapfile_declaration(tmp[2]," ".join(tmp[1:]))
124    _comment_list = []
125
126def t_WORD(t):
127    r'[a-zA-Z_][a-zA-Z_0-9\.]*'
128    t.type = reserved.get(t.value,'WORD')    # Check for reserved words
129    return t
130t_NONWORD   = r"[^\.A-Za-z0-9,;:<>\#{}\(\)\[\] \n\t\r]+"
131
132# Error handling rule
133def t_error(t):             #pragma:nocover
134    print "Illegal character '%s'" % t.value[0]
135    t.lexer.skip(1)
136
137##
138## Yacc grammar
139##
140## NOTE: This grammar is not intended to reflect the semantics of AMPL.
141## Instead, our goal is to extract info about how the sets, variables, etc are
142## declared.
143##
144
145def p_expr(p):
146    '''expr : statements
147            | '''
148    process_comment_list()
149
150def p_statements(p):
151    '''statements : statement statements
152                  | statement '''
153
154def p_statement(p):
155    '''statement : SET WORD SEMICOLON
156                 | SET WORD csdecl SEMICOLON
157                 | SET WORD WITHIN csdecl SEMICOLON
158                 | SET WORD LBRACE index RBRACE SEMICOLON
159                 | SET WORD LBRACE index RBRACE csdecl SEMICOLON
160                 | SET WORD LBRACE index RBRACE WITHIN csdecl SEMICOLON
161                 | PARAM  WORD SEMICOLON
162                 | PARAM  WORD csdecl SEMICOLON
163                 | PARAM  WORD LBRACE index RBRACE SEMICOLON
164                 | PARAM  WORD LBRACE index RBRACE csdecl SEMICOLON
165                 | VAR  WORD SEMICOLON
166                 | VAR  WORD csdecl SEMICOLON
167                 | VAR  WORD LBRACE index RBRACE SEMICOLON
168                 | VAR  WORD LBRACE index RBRACE csdecl SEMICOLON
169                 | CHECK COLON misc SEMICOLON
170                 | CHECK LBRACE index RBRACE COLON misc SEMICOLON
171                 | row WORD SEMICOLON
172                 | row WORD csdecl SEMICOLON
173                 | row WORD LBRACE index RBRACE SEMICOLON
174                 | row WORD LBRACE index RBRACE csdecl SEMICOLON
175                 | row WORD COLON WORD SEMICOLON
176                 | row WORD COLON csdecl SEMICOLON
177                 | row WORD LBRACE index RBRACE COLON WORD SEMICOLON
178                 | row WORD LBRACE index RBRACE COLON csdecl SEMICOLON
179                 | data
180    '''
181    global _parse_info
182    index = []
183    if len(p) > 5 and p[3] is '{':
184       index = flatten(p[4])
185       while "," in index:
186         index.remove(",")
187
188    if p[1] in ("set", "param", "var", "minimize", "maximize", "subject", "s.t."):
189       dimen=1
190       superset=None
191       setlist = []
192       if p[1] == "set":
193            if len(p) == 5:
194                setlist = p[3]
195            elif len(p) == 6:
196                setlist = p[4]
197            elif len(p) == 8:
198                setlist = p[6]
199            elif len(p) == 9:
200                setlist = p[7]
201       elif p[1] == "param":
202            if len(p) == 5:
203                setlist = p[3]
204            if len(p) == 8:
205                setlist = p[6]
206
207       tmp = flatten(setlist)
208       setlist=[]
209       for item in tmp:
210            setlist = setlist + re.split('[\t ]+',item)
211
212       if "integer" in setlist or "binary" in setlist or "Integers" in setlist:
213            superset = "integers"
214       elif "symbolic" in setlist:
215            superset = "literals"
216       elif "Reals" in setlist:
217            superset = "reals"
218
219       if p[1] == "param" and superset is None:
220            superset="reals"
221
222       #print "HERE X",p[1:],superset,len(p),setlist
223       _parse_info.add(p[1],p[2], index, dimen=1, superset=superset)
224    #
225    # checks are not named, so we ignore them
226    #
227    elif p[1] in ("check", "arc", "node", "data"):
228        process_comment_list()
229        return
230    else:                           #pragma:nocover
231        #
232        # If the AMPL model is syntactically correct, we should never see this
233        #
234        print "ERROR statement", p[1:]
235    if debugging:                   #pragma:nocover
236        if p[1] != "data":
237           print "* DECL",p[1],p[2],index
238    process_comment_list()
239
240def p_row(p):
241    '''row : MIN
242           | MAX
243           | NODE
244           | ARC
245           | SUBJECT TO
246           | ST'''
247    p[0] = p[1]
248
249def p_index(p):
250    '''index : setname
251             | token
252             | misc
253             | setname COMMA index
254             | token COMMA index
255             | misc COMMA index'''
256    if len(p) == 2:
257       tmp=p[1:]
258    else:
259       tmp = [p[1],p[2],p[3]]
260    if debugging:                   #pragma:nocover
261        print "* INDEX",tmp,p[1:]
262    p[0] = tmp
263
264def p_csdecl(p):
265    '''csdecl : token
266             | misc_bool
267             | token COMMA csdecl
268             | misc_bool COMMA csdecl'''
269    if len(p) == 2:
270       p[0]=p[1:]
271    else:
272       p[0] = flatten(p[1:])
273       #if type(p[1]) is list:
274            #p[0] = p[1] + [p[2],p[3]]
275       #else:
276            #p[0] = [p[1],p[2],p[3]]
277    if debugging:                   #pragma:nocover
278        print "* INDEX",p[0],p[1:]
279
280def p_setname(p):
281    '''setname : token IN token Xmisc_bool
282               | token IN misc Xmisc_bool'''
283    p[0] = p[3]
284    if debugging:                   #pragma:nocover
285        print "* SETNAME",p[0]
286
287def p_misc(p):
288    '''misc : token token_list'''
289    p[0] = p[1]+" "+p[2]
290    if debugging:                   #pragma:nocover
291        print "* MISC",p[0]
292
293def p_token_list(p):
294    '''token_list : token_list token
295                  | token'''
296    if len(p) > 2:
297       p[0] = p[1]+" "+p[2]
298    else:
299       p[0] = p[1]
300    if debugging:                   #pragma:nocover
301        print "* TOKENS",p[0],p[1:]
302
303def p_token(p):
304    '''token : WORD
305             | NUMBER
306             | RANGE
307             | INTEGER
308             | BINARY
309             | SYMBOLIC
310             | REALS
311             | INTEGERS
312             | IN
313             | TO
314             | GT
315             | LT
316             | LTGT
317             | GTEQ
318             | LTEQ
319             | DIMEN
320             | COLONEQ
321             | SUM
322             | LBRACKET csdecl RBRACKET
323             | LBRACE csdecl RBRACE
324             | LPAREN csdecl RPAREN
325             | LTLT csdecl SEMICOLON csdecl GTGT
326             | NONWORD'''
327    if len(p) == 2:
328        p[0]=p[1]
329    else:
330        p[0] = " ".join(flatten(p[1:]))
331    if debugging:                   #pragma:nocover
332        print "* TOKEN ",p[0],p[1:]
333
334def p_Xmisc_bool(p):
335    '''Xmisc_bool : COLON misc_bool
336                 | '''
337    if len(p) > 1:
338        p[0] = p[2]
339
340def p_misc_bool(p):
341    '''misc_bool : token bool_token_list'''
342    p[0] = p[1]+" "+p[2]
343    if debugging:                   #pragma:nocover
344        print "* MISC_BOOL",p[0]
345
346def p_bool_token_list(p):
347    '''bool_token_list : bool_token_list token
348                  | bool_token_list COLON token
349                  | COLON token
350                  | bool_token_list IN token
351                  | IN token
352                  | token'''
353    if len(p) > 2:
354       p[0] = p[1]+" "+p[2]
355    else:
356       p[0] = p[1]
357    if debugging:                   #pragma:nocover
358        print "* BOOL_TOKENS",p[0],p[1:]
359
360def p_data(p):
361    '''data : DATA SEMICOLON datalines
362            | DATA SEMICOLON'''
363    p[0] = 'data'
364
365def p_datalines(p):
366    '''datalines : dataline datalines
367                 | dataline'''
368
369def p_dataline(p):
370    '''dataline : anytokens SEMICOLON'''
371
372def p_anytokens(p):
373    '''anytokens : anytoken anytokens
374                 | anytoken'''
375
376def p_anytoken(p):
377    '''anytoken : WORD
378             | SET
379             | PARAM
380             | NUMBER
381             | IN
382             | TO
383             | GT
384             | LT
385             | GTEQ
386             | LTEQ
387             | COLON
388             | COLONEQ
389             | COMMA
390             | LBRACE
391             | RBRACE
392             | LBRACKET
393             | RBRACKET
394             | LPAREN
395             | RPAREN
396             | RANGE
397             | NONWORD'''
398
399#
400# The function that performs the parsing
401#
402def parse_ampl(data=None, filename=None, debug=0):
403    global debugging
404    #
405    # Always remove the parser.out file, which is generated to create debugging
406    #
407    if os.path.exists("parser.out"):        #pragma:nocover
408       os.remove("parser.out")
409    if debug > 0:                           #pragma:nocover
410        #
411        # Remove the parsetab.py* files.  These apparently need to be removed
412        # to ensure the creation of a parser.out file.
413        #
414        if os.path.exists("parsetab.py"):
415           os.remove("parsetab.py")
416        if os.path.exists("parsetab.pyc"):
417           os.remove("parsetab.pyc")
418        debugging=True
419    #
420    # Build lexer
421    #
422    lex.lex()
423    #
424    # Initialize parse object
425    #
426    global _parse_info
427    _parse_info = AmplInfo()
428    #
429    # Build yaccer
430    #
431    yacc.yacc(debug=debug)
432    #
433    # Parse the file
434    #
435    global _parsedata
436    if not data is None:
437        _parsedata=data
438        ply_init(_parsedata)
439        yacc.parse(data,debug=debug)
440    elif not filename is None:
441        f = open(filename)
442        data = f.read()
443        f.close()
444        _parsedata=data
445        ply_init(_parsedata)
446        yacc.parse(data, debug=debug)
447    else:
448        _parse_info = None
449    #
450    # Disable parsing I/O
451    #
452    debugging=False
453    return _parse_info
454
455
Note: See TracBrowser for help on using the repository browser.