#!/usr/pkg/bin/python """bootstrap for postforth version 1.0 get enough forth built so it can bootstrap itself up the rest of the way""" Copyright = """ pf.py - minimal postforth bootstrap compiler Copyright (C) 2004 John Comeau 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., 675 Mass Ave, Cambridge, MA 02139, USA. """ errormessage = "Not all needed libraries found, upgrade or check path" try: True # not defined in older Python releases except: True, False = 1, 0 try: import sys, os, types, re, pwd sys.path.append(os.sep.join([pwd.getpwuid(os.geteuid())[5], 'lib', 'python'])) from com.jcomeau import gpl, jclicense except: try: sys.stderr.write("%s\n" % errormessage) except: print errormessage raise # get name this program was called as self = sys.argv[0].split(os.sep)[-1] # now get name we gave it when we wrote it originalself = Copyright.split()[0] # globals and routines that should be in every program # (yes, you could import them, but there are problems in that approach too) debugging = True # set to False for production release def DebugPrint(*whatever): global debugging if debugging: sys.stderr.write("%s\n" % repr(whatever)) def test(*args): "check if lowercase functions actually work as programs" print "this is a test. it is only a test. passed arguments follow..." DebugPrint(args) # other globals, specific to this program cellsize = 4 # for 32-bit forth linebuffer = '' linepointer = 0 infile = None current = None # alias to object of currently-being-defined word stack = [] # simulated FORTH stack words = [] # array of ForthWord objects text = '' # string of compiled code (.text segment) textoffset = 0x1000 # relative to image base, which is typically 0x400000 compilerpass = None # we will have to set the entrypoint, not to 0x1000 as is typical, # but to the offset to 'COLD', the coldstart routine # alternatively, we can place the coldstart at the beginning of .text segment class ForthWord: def __init__(self, *args): global text self.name = args[0] self.compiled = [] self.emulation = None self.codeaddress = len(text) try: self.emulation = args[1] except: pass def compilefile(*args): """two-pass compiler, with first pass just determining offsets after first pass, if you see a lot of words with codeaddress 0, it means they are defined in this program but not in the sources. if you see words with same nonzero codeaddress, they are being used in the sources but not defined anywhere.""" args = GetStrings(args) global infile, text, compilerpass for compilerpass in 'first', 'final': text = '' # reset text segment each pass DebugPrint('beginning %s compiler pass' % compilerpass) for index in range(0, len(args)): infilename = args[index] infile = open(infilename, "r") while not eof(): DebugPrint('text', text) try: nextword = word() except: # assume normal EOF DebugPrint("EOF detected, on to next file if any") continue pfword = newword(nextword) DebugPrint('processing: ', pfword.__dict__) execute(pfword) if current is not None: DebugPrint('current: ', current.__dict__) pause() DebugPrint("'while' loop ended") DebugPrint("end of %s pass" % compilerpass) DebugPrint(map(lambda word: word.__dict__, words)) if debugging: sys.exit(0) def pause(): DebugPrint(" to continue") input = sys.stdin.readline() if input != "\n": sys.exit(0) def execute(pfword): "second-pass actions, either emulated or simulated" DebugPrint('executing', pfword.__dict__) if pfword.emulation is not None: pfword.emulation() else: # simulate it by executing the words that make this word up # note that this can lead to endless loops! for word in pfword.compiled: execute(word) def eof(): "test input file for end-of-file condition" global infile, linebuffer, linepointer if linepointer == len(linebuffer): linepointer = 0 linebuffer = infile.readline() if len(linebuffer) == 0: infile.close() return True else: return False else: return False def findword(forthword): global words try: return filter(lambda word: word.name == forthword, words)[0] except: return None def newword(forthword): global words if findword(forthword) is None: words.append(ForthWord(forthword)) return findword(forthword) def word(sep = ' '): "return the next Forth word from source" global linebuffer, linepointer token = '' # token, once built, will be returned as word if sep == ' ': """use any character less than or equal to a space as the separator: newline, tab, null, whatever""" while linebuffer[linepointer] <= sep: nextbyte() while linebuffer[linepointer] > sep: token = token + linebuffer[linepointer] nextbyte() else: while linebuffer[linepointer] != sep: token = token + linebuffer[linepointer] nextbyte() nextbyte() # skip past this token delimiter return token def nextbyte(): "point globals to next byte from input file" global infile, linebuffer, linepointer linepointer = linepointer + 1 if linepointer == len(linebuffer): linepointer = 0 linebuffer = infile.readline() if len(linebuffer) == 0: raise Exception, 'Unexpected EOF' def paren(): "skip over comment" comment = word(')') DebugPrint('skipping comment: %s' % comment) def binary(): "read binary number and push onto stack" global stack binarynumber = word() number = 0L for index in range(0, len(binarynumber)): number = (number << 1) | int(binarynumber[index]) DebugPrint("pushing number %s (%d)" % (binarynumber, number)) stack.append(number) def base10(): "read decimal number and push onto stack" global stack number = long(word()) DebugPrint("pushing number %d" % number) stack.append(number) def packlong(number): """pack long for little-endian machines""" try: number = long(number) except: number = 0 return chr(number & 0xffL) + chr((number & 0xff00L) >> 8) + \ chr((number & 0xff0000L) >> 16) + \ chr((number & 0xff000000L) >> 24) def compile(whatever): "compile for x86" global compilerpass if type(whatever) is types.InstanceType: if compilerpass == 'first': return pad(whatever.name, cellsize) else: return packlong(whatever.codeaddress) else: return packlong(whatever) def semi(): "end colon definition" global text text += compile(findword(';S')) def litcomma(): "compile LIT and number" global text text += compile(findword('LIT')) comma() def comma(): "FORTH comma compiles whatever is at TOS (top of stack)" global text, stack text += compile(stack.pop()) def ccomma(): "FORTH C, compiles as byte whatever is at TOS" global text, stack text += chr(stack.pop() & 0xff) def nop(): "no operation - just a placeholder" pass def here(): # like FORTH word HERE "return offset in .text section of next location for compiled code" global text return len(text) def create(): "initialize creation of new FORTH word" global words, current token = word() pfword = newword(token) # now reorder words list so this is the next defined word if current is None: DebugPrint("inserting new word '%s' at beginning of list" % pfword.name) words.insert(0, words.pop(words.index(pfword))) else: DebugPrint("inserting new word '%s' at position %d in list" % \ (pfword.name, words.index(current) + 1)) words.insert(words.index(current) + 1, words.pop(words.index(pfword))) # now this is the new current (currently being defined) word current = pfword def colon(): "colon definition (high level FORTH word)" global text, compilerpass create() if compilerpass == 'first': text += 'colon' def pad(string, length, padding = '.'): padded = string + (padding * length) return padded[0:length] def tick(): global stack token = word() pfword = newword(token) DebugPrint("pushing codeaddress %d" % pfword.codeaddress) stack.append(pfword.codeaddress) def GetStrings(*args): while len(args) > 0 and (type(args[0]) is types.ListType or \ type(args[0]) is types.TupleType): args = args[0] return list(args) def GetInt(*args): """helper routine for template program, extracts integer argument depending on whether a routine was called directly from the command line (testing mode) or from elsewhere in the program, the argument may be a list of strings rather than the expected integer. This will extract one integer from whatever is passed in.""" while len(args) > 0 and (type(args) is types.ListType or \ type(args) is types.TupleType): args = args[0] return int(args) # initialize some of the Forth words here: words.append(ForthWord('(', paren)) words.append(ForthWord(':', colon)) # ' DOCOL CALL, words.append(ForthWord('2#', binary)) words.append(ForthWord('LIT')) words.append(ForthWord('LIT,', litcomma)) words.append(ForthWord(';', semi)) # compiles ;S words.append(ForthWord(';S')) words.append(ForthWord('(P', paren)) words.append(ForthWord("'", tick)) words.append(ForthWord(',', comma)) # compiles word on stack words.append(ForthWord('C,', ccomma)) # compiles byte on stack words.append(ForthWord('10#', base10)) words.append(ForthWord('CREATE', create)) words.append(ForthWord('LODSL,', nop)) # comment out one of the two lines below #def pf(*args): pf = \ """ If you want this script to be a library of routines, that you call as './thisscript myroutine arg1 arg2 arg3', then name a string the same as the file, as this is, and the main() routine will automagically make that happen for you. This also makes possible the easy debugging of subroutines. However, if this is a single-purpose script, just name the main routine the same as the file, and main() will just call that routine with sys.argv[1:] as args. """ def main(): """main routine, only used for command-line testing let's say this program is called 'template.py' don't, by the way, name the program with the name of an included function if you're using this system... now if the program is invoked as './template.py', it will simply output a usage message. if invoked as './template.py MyFunction 1 2 3' we will attempt to call MyFunction(1, 2, 3) and show whatever is found if invoked as './template.py myfunction 1 2 3' we attempt to call myfunction(1, 2, 3) as a program and let it output whatever it decides likewise if you symlink like so: 'ln -s template.py myfunction' then invoke as './myfunction 1 2 3'""" program = sys.argv[0].split(os.sep)[-1] function = program.split('.')[0] # in case of .py extension try: if eval("type(%s)" % function) == types.FunctionType: if re.compile('^[a-z0-9]+$').match(function) != None: eval("%s(sys.argv[1:])" % function) else: try: print repr(eval("%s(sys.argv[1:])" % function)) except: sys.stderr.write("Error occurred testing routine %s(%s)\n" % ( function, repr(sys.argv[1:]))) raise else: raise NameError, '%s not a function' % function except NameError, instance: # first make sure this wasn't some other type of name error; if it was, barf #DebugPrint(instance.args) try: message = instance.args[0] except: message = 'unknown NameError' if message != '%s not a function' % function: raise NameError, message # called name didn't match anything defined if len(sys.argv) > 1: # hopefully the next arg is the function name we want sys.argv.pop(0) main() else: # called name didn't match any routine, so output usage message sys.stderr.write("""Usage: %s [OPTIONS] INPUT[...] or for more detailed help, read source or: pydoc %s """ % (self, self.split('.')[0])) if __name__ == '__main__': # if this program was imported by another, the above test will fail, # and this following code won't be used... main() else: # if you want something to be done on import, do it here; otherwise pass pass