#!/usr/bin/env python """ plan3.py - Plan3 Implementation for CWM Author: Sean B. Palmer, inamidst.com Plan3 is a procedural RDF programming language """ import sys from swap import term, RDFSink, query class Plan3Error(Exception): pass def add(self, args, original=None): return sum(int(str(arg)) for arg in args) def delete(self, args, original=None): f = self.working g = args[0] p = self.program p.close() f.stayOpen = 0 f = f.close() # convert all existentials to universals # conv = {} # for existential in g.existentials(): # conv[existential] = f.newUniversal() # g.reopen() # g.substituteEqualsInPlace(conv) # g.close() assert f.canonical is f assert g.canonical is g f = f.renameVars() u = query.buildStrictPattern(f, g) # print 'UNMATCHED:', u log = self.working.store.newSymbol('http://www.w3.org/2000/10/swap/log') unmatched = [] for statement in u: if statement[1] != log['existentialVariableName']: unmatched.append(statement) # print 'UNMATCHED:', unmatched existentials = p.existentials() | g.existentials() # print 'EXISTENTIALS:', existentials variables = p.universals() | g.universals() | existentials # print 'VARIABLES:', variables query._substitute({g: f}, unmatched) dummy = self.working.store.newFormula(self.info.get("_baseURI") + "#_dummy") q = query.Query(f.store, unmatched=unmatched, template=g, conclusion=g, variables=variables, workingContext=f, targetContext=dummy, justReturn=1, interpretBuiltins=False, existentials=existentials, mode="") results = q.resolve() or [] self.working.reopen() self.program.reopen() for bindings in results: # print 'BINDINGS:', bindings bindings = dict((k, v[0]) for k, v in bindings.iteritems()) for statement in g.substitution(bindings): self.working.removeStatement(statement) # print '* * *' return True def dump(self, args, original=None): name = str(args[0]) context = getattr(self, name) context.stayOpen = 0 context = context.close() assert context.canonical != None self.info.get("_store").dumpNested(context, self.info.get("_outSink")) def cond(self, args, original=None): condition, block = args yes = self.perform(condition) if yes: self.perform(block) # def first(self, args): # arg = args[0] # return arg[0] def join(self, args, original=None): sep, args = args return str(sep).join(str(arg) for arg in args) def p3list(self, args, original=None): # print 'P3LIST:', args return list(args) def listhandler(self, args, original=None): input, args = args[0], args[1:] if len(args) == 1: return input[int(str(args[0]))] elif len(args) == 2: return input[int(str(args[0])):int(str(args[1]))] def loop(self, args, original=None): var = args[0] # friend input = args[2] # some list block = args[3] # thing to do if isinstance(input, term.NonEmptyList): input = self.perform(input) # print 'VAR, INPUT, BLOCK:', var, input, block # print 'RESULT:', result for item in input: self.bind(var, item) self.perform(block) def nelhandler(self, args, original=None): for arg in args: self.perform(arg) def out(self, args, original=None): outlist = [] for arg in args: if isinstance(arg, term.NonEmptyList): result = self.perform(arg) outlist.append(str(result)) else: outlist.append(str(arg)) sys.stdout.write(' '.join(outlist)) def output(self, args, original=None): if args: name = str(args[0]) else: name = 'working' context = getattr(self, name) context.stayOpen = 0 context = context.close() assert context.canonical != None self.info.get("_store").dumpNested(context, self.info.get("_outSink")) def push(self, args, original=None): obj, seq = args self.bind(original[1], seq + [obj]) return seq + [obj] def p3query(self, args, original=None): f = self.program f = f.close() g = args[0] assert f.canonical is f assert g.canonical is g unmatched = query.buildStrictPattern(f, g) # print unmatched existentials = f.existentials() | f.universals() variables = f.universals() # print existentials # print variables query._substitute({g: f}, unmatched) q = query.Query(f.store, unmatched=unmatched, template=g, variables=variables, workingContext=f, interpretBuiltins=False, existentials=existentials, justReturn=1, mode="") result = q.resolve() # print result def ret(self, args, original=None): # print 'RETURN:', args[0] arg = args[0] if isinstance(arg, term.NonEmptyList): arg = self.perform(arg) return args[0] def say(env, args, original=None): saylist = [] for arg in args: if isinstance(arg, term.NonEmptyList): # print 'PERFORM-IN-SAY' result = env.perform(arg) # print 'SAY RESULT:', result saylist.append(str(result)) else: saylist.append(str(arg)) print ' '.join(saylist) def select(self, args, original=None): # print 'SELECT ARGS:', args i = args.index(self.p3['where']) a, b = args[:i], args[i+1:] block = None if len(b) == 2: g, block = b else: g = b[0] if self.p3['from'] in a: f = self.program # @@ no configurability yet a = a[:-2] else: f = self.working # @@ need to clone it? varis = a # print 'VARS, F, G, BLOCK:', varis, f, g, block f.stayOpen = 0 f = f.close() g = g.close() p = self.program p.close() assert f.canonical is f assert g.canonical is g f = f.renameVars() u = query.buildStrictPattern(f, g) # print 'UNMATCHED:', u log = self.working.store.newSymbol('http://www.w3.org/2000/10/swap/log') unmatched = [] for statement in u: if statement[1] != log['existentialVariableName']: unmatched.append(statement) # print 'UNMATCHED:', unmatched existentials = p.existentials() | g.existentials() # print 'EXISTENTIALS:', existentials variables = p.universals() | g.universals() # print 'VARIABLES:', variables query._substitute({g: f}, unmatched) q = query.Query(f.store, unmatched=unmatched, template=g, variables=variables, workingContext=f, interpretBuiltins=False, existentials=existentials, justReturn=1, mode="") resolved = q.resolve() if not resolved: return [] # print 'RESOLVED:', resolved results = [] for bindings in resolved: if len(varis) > 1: results.append([bindings[var] for var in varis]) else: results.append(bindings[varis[0]]) if block: # self.stack.append(block) # for item in results: # self.bind(var, item, scope=block) # self.perform(block) # self.stack.pop() for result in results: if isinstance(result, list): for var, o in zip(varis, result): self.bind(var, o) self.perform(block) else: var = varis[0] self.bind(var, result) self.perform(block) return results def semantics(self, args, original=None): arg = args[0] # print 'ARG:', arg return arg.dereference() def sort(self, args, original=None): arg = args[0] if isinstance(arg, term.NonEmptyList): arg = self.perform(arg) return list(sorted(arg)) def startswith(self, args, original=None): something, string = args # print something, type(something) something, string = str(something.uriref()), str(string) # print 'STARTSWITH:', something, string return something.startswith(string) def store(self, args, original=None): arg = args[0] # print 'STORE ARG:', arg if isinstance(arg, term.NonEmptyList): arg = self.perform(arg) elif arg == self.p3['stdin']: ContentType={"rdf": "application/xml+rdf", "n3": "text/rdf+n3", "sparql": "x-application/sparql" }[self.info.get("option_first_format") or "n3"] arg = self.working.store.load( asIfFrom = self.info.get("_baseURI"), contentType = ContentType, flags = self.info.get("option_flags")[ self.info.get("option_first_format") or "n3"], remember = 0, referer = "", why = self.info.get("myReason"), topLevel=True) # return True # arg = self.working.store.load(asIfFrom = self.info.get('_baseURI elif isinstance(arg, term.Symbol): arg = semantics(self, [arg]) # print 'STORE:', arg self.working.reopen() # hmm... shouldn't close in the first place! self.working.loadFormulaWithSubstitution(arg) return True def stringhandler(self, args, original=None): args = [self.resolve(arg) for arg in args] return ''.join(str(arg) for arg in args) def think(self, args, original=None): # @@ conclude? # (think) - think in the working context # (think program) - think in the program context # (think f) - think in formula f and return it # print 'Gonna think now' if not args: self.working.isWorkingContext = True # print 'query.think:' query.think(self.working, mode=self.info.get('option_flags')['think']) # print 'done!' def p3type(self, args, original=None): print type(args[0]) def var(env, args, original=None): name, value = args if isinstance(value, term.NonEmptyList): value = env.perform(value) env.bind(name, value) class Interpreter(object): def __init__(self, program, working): self.program = program self.working = working self.p3 = program.store.newSymbol('http://example.org/ns/plan3') self.p3f = program.store.newSymbol('http://example.org/ns/plan3-funcs') self.variables = {} self.argspecs = {} self.stack = [] # @@ could be an RDF environment self.basics = { self.p3['add']: add, self.p3f['add']: add, self.p3['delete']: delete, self.p3f['delete']: delete, self.p3['dump']: dump, self.p3f['dump']: dump, # self.p3['first']: first, # self.p3f['first']: first, self.p3['for']: loop, self.p3f['for']: loop, self.p3['if']: cond, self.p3f['if']: cond, self.p3['join']: join, self.p3f['join']: join, self.p3['list']: p3list, self.p3f['list']: p3list, self.p3['out']: out, self.p3f['out']: out, self.p3['output']: output, self.p3f['output']: output, self.p3['push']: push, self.p3f['push']: push, # self.p3['query']: p3query, # self.p3f['query']: p3query, self.p3['return']: ret, self.p3f['return']: ret, self.p3['say']: say, self.p3f['say']: say, self.p3['select']: select, self.p3f['select']: select, self.p3['semantics']: semantics, self.p3f['semantics']: semantics, self.p3['sort']: sort, self.p3f['sort']: sort, self.p3['startswith']: startswith, self.p3f['startswith']: startswith, self.p3['store']: store, self.p3f['store']: store, self.p3['think']: think, self.p3f['think']: think, self.p3['type']: p3type, self.p3f['type']: p3type, self.p3['var']: var, self.p3f['var']: var } def get(self, **kargs): return self.program.statementsMatching(**kargs) def varbind(self, function, var, value): if not self.variables.has_key(function): self.variables[function] = {} self.variables[function][var] = value def root(self): """Return the outermost scope, the root program block.""" if self.stack: return self.stack[0] return None def stem(self): """Return the second outermost scope, the first level of nesting.""" if len(self.stack) > 1: return self.stack[1] return None def current(self): """Return the current scope.""" if self.stack: return self.stack[-1] return None def bind(self, name, value, scope=None): higher = False for function in self.stack: if self.variables.has_key(function): if self.variables[function].has_key(name): if not higher: self.varbind(function, name, value) higher = True else: del self.variables[function][name] if not higher: if scope is None: if self.current() == self.root(): scope = self.root() else: scope = self.stem() setvalue = False if scope is not None: self.varbind(scope, name, value) setvalue = True if (setvalue is False) and self.stack: root = self.root() self.varbind(root, name, value) def lookup(self, arg): for block in reversed(self.stack): if self.variables.has_key(block): if self.variables[block].has_key(arg): return self.variables[block][arg] # or is there a root def for it? # @@! this won't work if def is overridden... statements = self.get(subj=arg, pred=self.p3['def']) if statements: assert len(statements) == 1 return arg if self.basics.has_key(arg): return self.basics[arg] return arg def resolve(self, arg): value = self.lookup(arg) from swap import llyn if isinstance(value, llyn.IndexedFormula): bindings = {} for block in reversed(self.stack): if self.variables.has_key(block): for key in self.variables[block].iterkeys(): # if isinstance(key, term.AnonymousUniversal): if not bindings.has_key(key): bindings[key] = self.variables[block][key] if bindings: value = value.substitution(bindings) return value def perform(self, command): seq = command.asSequence() # print 'PERFORM SEQ:', seq function = self.resolve(seq[0]) args = [self.resolve(arg) for arg in seq[1:]] if isinstance(function, term.NonEmptyList): if not False in map(lambda arg: isinstance(arg, term.NonEmptyList), args): args = [function] + args result = nelhandler(self, args) else: function = self.perform(function) # If we know about this, we handle it # Otherwise, we just evaluate it as normal # print function, type(function) if hasattr(function, '__call__'): result = function(self, args, original=seq[1:]) elif isinstance(function, list): args = [function] + args result = listhandler(self, args) elif isinstance(function, term.Literal): args = [function] + args result = stringhandler(self, args) elif isinstance(function, term.AnonymousExistential): # so it's SOMETHING PREDICATE BNODE # and we've got the BNODE... statements = self.get(obj=function) raise Plan3Error("Not yet implemented") elif isinstance(function, term.NonEmptyList): pass else: rargs = [] argspec = self.argspecs[function] for i, arg in enumerate(args): if isinstance(arg, term.NonEmptyList): if (argspec is None) or isinstance(argspec[i], term.Fragment): rargs.append(self.evaluate(arg)) else: rargs.append(arg) else: rargs.append(arg) result = self.evaluate(function, tuple(rargs)) # print 'PERFORM RESULT:', result return result def evaluate(self, function, args=None, script=False): # print 'EVALUATE:', function if not script: pred = self.p3['def'] else: pred = self.p3['script'] statements = self.get(subj=function, pred=pred) if len(statements) != 1: length = len(statements) msg = 'Expected 1 definition of %s, found %s' % (function, length) raise Plan3Error(msg) definition = statements[0] obj = definition[RDFSink.OBJ] if not script: objseq = obj.asSequence() argspec, block = objseq[0], objseq[1:] argspec = argspec.asSequence() # print 'ARGSPEC, ARGS', argspec, args if len(argspec) != len(args): raise Plan3Error("%s doesn't satisfy %s" % (args, argspec)) for var, value in zip(argspec, args): # print 'VAR:', var if isinstance(var, term.NonEmptyList): seq = var.asSequence() if seq[0] == self.p3['list']: self.varbind(function, seq[1], value) else: raise Plan3Error("Argspec can't contain that list") self.varbind(function, var, value) else: argspec, block = [], obj.asSequence() result = None self.stack.append(function) if script: argv = [sys.argv[0]] + (self.working.store.argv or []) self.bind(self.p3['argv'], argv) for command in block: # print 'PERFORM-IN-EVALUATE' result = self.perform(command) self.stack.pop() # print 'EVALUATE RESULT:', result return result def script(self, statement): subj = statement[RDFSink.SUBJ] self.evaluate(subj, script=True) def run(self): defs = self.get(pred=self.p3['def']) for statement in defs: function = statement[RDFSink.SUBJ] argspec = statement[RDFSink.OBJ].asSequence()[0] self.argspecs[function] = argspec statements = self.get(pred=self.p3['script']) length = len(statements) if length == 1: statement = statements[0] self.script(statement) else: msg = 'Expected 1 script function, found %s' % length raise Plan3Error(msg) def run(info, working, program): i = Interpreter(program, working) i.info = info i.run() # EOF