#!/usr/bin/env python """ compiler.py - Pluvo Language Compiler Author: Sean B. Palmer, inamidst.com """ import sys, re import parser from datatypes import Table, Variable, String, Number from basics import basics class Compiler(parser.Parser): """Compile utf-8 encoded Pluvo source to Python objects. >>> c = Compiler() >>> c.compileString('say "Hello World!"') >>> print c.program """ def __init__(self, verbose=False): """Make a new Compiler instance.""" parser.Parser.__init__(self, verbose=verbose) self.program = None self.level = 0 self.indent = '' def compile(self, f): """Parse the file, then compile into Compiler.program.""" self.parse(f) self.document() def compileString(self, string): """Basically wrap string and pass it to Compile.parse.""" import cStringIO as StringIO f = StringIO.StringIO(string) f.seek(0) self.compile(f) def document(self): """document = ( command | newline )*""" if self.verbose: print >> sys.stderr, "Start document" # Set up the default program environment self.program = Table({ String("kind"): String("Function"), String("basics"): basics, # Or "basics"? String("variables"): Table() }) while self.position < self.length: if self.peek().kind != 'Newline': command = self.command() self.program.append(command) else: self.newline() if self.verbose: print >> sys.stderr, "End document" def command(self): """command = ( ( atom | table | block )+ ";"? )""" if self.verbose: print >> sys.stderr, "Start command" result = Table({ "kind": "Command" }) commands = 0 # May be indent, newline, or even just a smaller indent while not self.test(parser.CommandClosers): commands += 1 # @@ um... this should surely be "args" token = self.peek() if token.kind == 'Indent': indent = token.value[1:] if self.indent is False: break elif len(indent) <= len(self.indent): break indentation = self.indentation() result.append(indentation) break elif self.test(parser.Atoms): atom = self.atom() result.append(atom) elif self.test('OpenParen'): table = self.table() result.append(table) elif self.test('OpenBrace'): block = self.block() result.append(block) else: raise ValueError("Expected atom, table, or block") if commands < 1: msg = "Must have at least one command here" raise ValueError(msg) if self.test('SemiColon'): self.eat() if self.verbose: print >> sys.stderr, "End command" return result def atom(self): """atom = (operator | comment | doc | number | string | uri| variable)""" if self.verbose: print >> sys.stderr, "Start atom" if self.test(parser.Atoms): token = self.eat() if token.kind in parser.Operators: result = Variable(token.value) elif token.kind == 'Documentation': from datatypes import String doc = String(token.value[2:]) def result(env, *args): env.program[String("basics")][String('documentation')] = doc return env.stack[-1].get(String("result")) elif token.kind == 'Comment': from datatypes import String comment = String(token.value[2:]) def result(env, *args): env.stack[-1][String('comment')] = comment return env.stack[-1].get(String("result")) elif token.kind == 'Number': result = Number(token.value) elif token.kind == 'String': from datatypes import String result = String(eval(token.value)) elif token.kind == 'Regexp': from datatypes import Regexp result = Regexp(token.value[1:-1]) elif token.kind == 'URI': from datatypes import URI result = URI(eval('"' + token.value[1:-1] + '"')) # @@ For now elif token.kind == 'Flag': from datatypes import Flag result = Flag(token.value) elif token.kind == 'Variable': result = Variable(token.value) else: msg = "Unknown kind of atom: %s" % token.kind raise ValueError(msg) else: raise ValueError("Expected atom") if self.verbose: print >> sys.stderr, "End atom" return result def table(self): """table = ( "(" (block | atom)* ")" )""" if self.verbose: print >> sys.stderr, "Start table" oparen = self.eat() if not oparen.kind == 'OpenParen': raise ValueError("Expected OpenParen") result = Table() while not self.test('CloseParen'): if self.test('OpenBrace'): block = self.block() result.append(block) else: atom = self.atom() result.append(atom) cparen = self.eat() if not cparen.kind == 'CloseParen': raise ValueError("Expected CloseParen") if self.verbose: print >> sys.stderr, "End table" return result def block(self): """block = ( "{" newline* ( command newline* ) )+ "}" )""" if self.verbose: print >> sys.stderr, "Start block" indent = self.indent self.indent = False obrace = self.eat() if obrace.kind != 'OpenBrace': raise ValueError("Expected OpenBrace") while self.test('Newline') or self.test('Indent'): self.whitespace() result = Table({ 'kind': 'Function', 'variables': Table() }) commands = 0 while not self.test('CloseBrace'): command = self.command() result.append(command) commands += 1 while self.test('Newline') or self.test('Indent'): self.whitespace() if commands < 1: msg = "Expected at least one command" raise ValueError(msg) cbrace = self.eat() if cbrace.kind != 'CloseBrace': raise ValueError("Expected CloseBrace") if self.verbose: print >> sys.stderr, "End block" self.indent = indent return result def indentation(self): """block = ( "{" newline* ( command newline* ) )+ "}" )""" if self.verbose: print >> sys.stderr, "Start indentation" odelim = self.eat() if odelim.kind != 'Indent': raise ValueError("Expected Indent") self.level += 1 self.indent = odelim.value[1:] result = Table({ 'kind': 'Function', 'variables': Table() }) commands = 0 while True: command = self.command() result.append(command) commands += 1 while self.test('Newline'): self.newline() if self.test('Indent'): indent = self.peek() ind = indent.value[1:] if len(ind) < len(self.indent): self.indent = ind self.level -= 1 break self.eat() else: self.level -= 1 if not self.level: self.indent = '' break if commands < 1: msg = "Expected at least one command" raise ValueError(msg) if self.verbose: print >> sys.stderr, "End indentation" return result def whitespace(self): if self.test('Newline'): self.newline() elif self.test('Indent'): self.eat() else: raise ValueError("Expected Newline or Indent") def newline(self): """newline = ( "\n" )""" if self.verbose: print >> sys.stderr, "Start newline" newline = self.eat() if newline.kind != 'Newline': raise ValueError("Expected Newline") if self.verbose: print >> sys.stderr, "End newline" def compile(f, verbose=False): """Parse the Pluvo file f and return a compiled program.""" c = Compiler(verbose=verbose) c.compile(f) return c.program def trim(string): return string.strip(' \t\r\n') if __name__ == '__main__': print trim(__doc__)