VocalEasel/mma/MMA/file.py

318 lines
8.2 KiB
Python
Raw Normal View History

2006-11-10 08:07:56 +00:00
# file.py
"""
This module is an integeral part of the program
MMA - Musical Midi Accompaniment.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2007-04-29 06:47:40 +00:00
Bob van der Poel <bob@mellowood.ca>
2006-11-10 08:07:56 +00:00
"""
import sys
import os
import gbl
from MMA.common import *
def locFile(name, lib):
2007-04-29 06:47:40 +00:00
""" Locate a filename.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
This checks, in order:
lib/name + .mma
lib/name
name + .mma
name
2007-04-29 06:47:40 +00:00
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
ext=gbl.ext
exists = os.path.exists
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
name=os.path.expanduser(name) # for ~ expansion only
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if lib:
if not name.endswith(ext):
t=os.path.join(lib, name + ext)
if exists(t):
return t
t=os.path.join(lib, name)
if exists(t):
return t
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
if not name.endswith(ext):
t = name + ext
if exists(t):
return t
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if exists(name):
return name
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return None
2006-11-10 08:07:56 +00:00
###########################
# File read class
###########################
class ReadFile:
2007-04-29 06:47:40 +00:00
class FileData:
""" After reading the file in bulk it is parsed and stored in this
2009-05-17 22:34:44 +00:00
data structure. Blanks lines and comments are removed.
2007-04-29 06:47:40 +00:00
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def __init__(self, lnum, data, label):
self.lnum=lnum
self.data=data
self.label=label
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def __init__(self, fname):
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.fdata=fdata=[]
self.lastline = None
self.lineptr = None
self.fname = None
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.que = [] # que for pushed lines (mainly for REPEAT)
self.qnums = []
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
dataStore = self.FileData # shortcut to avoid '.'s
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
try:
inpath = file(fname, 'r')
except:
error("Unable to open '%s' for input" % fname)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.debug or gbl.showFilenames:
print "Opening file '%s'." % fname
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.fname = fname
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" Read entire file, line by line:
- strip off blanks, comments
- join continuation lines
- parse out LABELS
- create line numbers
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
lcount=0
label=''
labs=[] # track label defs, error if duplicate in same file
nlabs=[] # track linenumber label defs
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
while 1:
l = inpath.readline()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not l: # EOF
break
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
l= l.strip()
lcount += 1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not l:
continue
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
# join lines ending in '\' (the strip() makes this the last char
2007-04-29 06:47:40 +00:00
while l[-1] == '\\':
l = l[0:-1] + ' ' + inpath.readline().strip()
lcount +=1
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
""" input cleanup ... for now the only cleanup is to convert
0xa0 (non-breakable space) to 0x20 (regular space).
"""
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
l=l.replace('\xa0', '\x20')
2007-04-29 06:47:40 +00:00
""" This next line splits the line at the first found
comment '//', drops the comment, and splits the
remaining line into tokens using whitespace delimiters.
Note that split() will strip off trailing and leading
spaces, so a strip() is not needed here.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
l = l.split('//',1)[0].split()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not l:
continue
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" Parse out label lines. There are 2 different kinds of labels:
- LABEL XXX
and
- NNN
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
The first kind is treated as an exclusive. If a NNN label or previous
XXX duplicates, and error is generated.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
The LINE NUMBER type is not exclusive. If a duplicate NNN is found, the
last one is used.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
XXX NNN types can not duplicate each other.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
Also note that XXX lines are stripped from input as well as NNN lines
with only a NNN.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if l[0].upper()=='LABEL':
if len(l) !=2:
gbl.lineno = lcount
error("Usage: LABEL <string>")
label=l[1].upper()
if label[0]=='$':
gbl.lineno = lcount
error("Variables are not permitted as labels")
if label in labs:
gbl.lineno = lcount
error("Duplicate label specified in line %s" % lcount)
elif label in nlabs:
gbl.lineno = lcount
error("Label '%s' duplicates line number label" % label)
labs.append(label)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif l[0].isdigit():
label=l[0]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if label in labs:
gbl.lineno = lcount
error("Line number '%s' duplicates LABEL" % label)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not label in nlabs:
nlabs.append(label)
else:
for i, a in enumerate(fdata):
if a.label == label:
fdata[i].label=''
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else:
label = None
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
# Save the line, linenumber and (maybe) the label.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
fdata.append( dataStore(lcount, l, label))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
inpath.close()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.lineptr = 0
self.lastline = len(fdata)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def toEof(self):
""" Move pointer to End of File. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.lineptr=self.lastline+1
self.que = []
self.qnums = []
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def goto(self, l):
""" Do a goto jump.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
This isn't perfect, but is probably the way most GOTOs work. If
inside a repeat/if then nothing more is processed. The jump is
immediate. Of course, you'll run into problems with missing
repeat/repeatend if you try it. Since all repeats are stacked
back into the que, we just delete the que. Then we look for a
matching label in the file line array.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
Label search is linear. Not too efficient, but the lists
will probably never be that long either.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not l:
error("No label specified")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if self.que:
self.que=[]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for i,a in enumerate(self.fdata):
if a.label == l:
self.lineptr=i
return
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
error("Label '%s' has not be set" % l)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def push(self, q, nums):
""" Push a list of lines back into the input stream.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
Note: This is a list of semi-processed lines, no comments, etc.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
It's quicker to extend a list than to insert, so add to the end.
Note: we reverse the original, extend() then reverse again, just
in case the caller cares.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
nums is a list of linenumbers. Needed to report error lines.
2007-04-29 06:47:40 +00:00
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not self.que:
self.que = ['']
self.qnums=[gbl.lineno]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
q.reverse()
self.que.extend(q)
q.reverse()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
nums.reverse()
self.qnums.extend(nums)
nums.reverse()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def read(self):
""" Return a line.
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
This will return either a queued line or a line from the
file (which was stored/processed earlier).
2007-04-29 06:47:40 +00:00
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
while 1:
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Return a queued line if possible.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if self.que:
ln = self.que.pop(-1)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
gbl.lineno = self.qnums.pop()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not ln:
continue
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return ln
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Return the next line in the file.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if self.lineptr>=self.lastline:
return None #### EOF
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
ln=self.fdata[self.lineptr].data
gbl.lineno = self.fdata[self.lineptr].lnum
self.lineptr +=1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return ln
2006-11-10 08:07:56 +00:00