class Pen(Struct('points count box start end')): # XXX maybe make this (except for `points`) all an optional field in Conn def build(self, env): # XXX Should we require len(points) to be 2? # Or work over each pair of successive points? # It looks like real IDEAL requires at least 2 but ignores any left over. assert len( self.points) == 2, "'conn using' must connect exactly two points" zero, one = self.points # XXX The true semantics is problematic for us in requiring # count to be evaluated to a number before we can instantiate # the boxes -- so far we've never interleaved creating # objects with solving constraints. I think we could do this # without a massive overhaul, but I'm not gonna try # tonight. Instead, require `count` to be a literal. assert isinstance(self.count, Literal), "XXX crude implementation restriction" assert self.count.value.imag == 0 n = self.count.value.real assert n == int(n) n = int(n) ps = [ Relatively(Div(Literal(i), self.count), zero, one) for i in range(n + 1) ] for a_exp, b_exp in zip(ps[:-1], ps[1:]): segment = Put( None, self.box.prepend((Equate( (self.start, a_exp)), Equate((self.end, b_exp))))) segment.build(env)
class Constraints(Struct('equations')): def build(self, env): for lhs, rhs in self.equations: LC.equate(lhs.evaluate(env), rhs.evaluate(env)) def draw(self, env): pass
class Text(Struct('justified string where')): def build(self, env): env.add_drawer(self) def draw(self, env): at = self.where.evaluate(env).get_value() renderer.text(self.string, self.justified, to_coords(at))
class Path(Struct('points')): def build(self, env): env.add_drawer(self) def draw(self, env): points = [p.evaluate(env).get_value() for p in self.points] self.render(map(to_coords, points))
class Box(Struct('name stmts')): def make(self, env): for stmt in self.stmts: stmt.build(env) def prepend(self, stmts): return Box(self.name, stmts + self.stmts)
class Tuple(Struct('exprs')): def evaluate(self, env): type_ = tuple_types[len(self.exprs)] inst = type_.instantiate(env) for v, expr in zip(type_.fields, self.exprs): LC.equate(inst.mapping[v], expr.evaluate(env)) return inst
class Draw(Struct('drawables')): def build(self, env): pass def draw(self, env): for drawable in self.drawables: drawable.draw(env)
class Div(Struct('arg1 arg2', supertype=(Expr, ))): def evaluate(self): combo1 = self.arg1.evaluate() combo2 = self.arg2.evaluate() terms2 = combo2.expand() if varies(terms2): raise Nonlinear() return combo1.scale(1 / constant(terms2))
class Relatively(Struct('coord zero one')): """Linear interpolate/extrapolate, i.e. `coord` in the coordinate system that places 0 at `zero` and 1 at `one`.""" def evaluate(self, env): coord = self.coord.evaluate(env) zero = self.zero.evaluate(env) one = self.one.evaluate(env) return zero + (one - zero) * coord
class Ref(Struct('name')): def evaluate(self, env): for frame in reversed(env.frames): try: return frame[self.name] except KeyError: pass raise KeyError("Unbound name", self.name)
class Put(Struct('opt_name box')): def build(self, env): name = self.opt_name or gensym( ) # (The default name's just for debugging.) subenv = env.spawn(name) env.types[self.box.name].make(subenv) define(env.frames[-1], name, subenv.frames[-1]) self.box.make(subenv)
class TupleType(Struct('fields')): def instantiate(self, env): inst = Instance(self) for f in self.fields: inst.mapping[f] = NumberInstance() return inst def draw(self, env): pass
class Compass(Struct('center boundary_point')): def build(self, env): env.add_drawer(self) def draw(self, env): points = [ p.evaluate(env).get_value() for p in (self.center, self.boundary_point) ] renderer.circle(*map(to_coords, points))
class NonlinearFn(Struct('fn arg', supertype=(Expr, ))): def evaluate(self): combo = self.arg.evaluate() terms = combo.expand() if varies(terms): raise Nonlinear() value = constant(terms) assert isinstance(value, (int, float, complex)), \ 'type: %s, value: %r' % (type(value), value) return make_constant(self.fn(value))
class VarDecl(Struct('type_id decls')): def build(self, env): type_ = env.types[self.type_id] for decl in self.decls: inst = type_.instantiate(env) env.init(decl.id, inst) for id, rhs in decl.params: LC.equate(inst.mapping[id], rhs.evaluate(env)) def draw(self, env): pass
class Equate(Struct('parts')): defaulty = False def build(self, env): env.add_constrainer(self) def constrain(self, env): def eq(lhs, rhs): env.constraints.append(solver.Equate(self.defaulty, lhs, rhs)) return rhs reduce(eq, (expr.evaluate(env) for expr in self.parts))
class Environment( Struct('types constrainers constraints drawers prefix frames')): def spawn(self, name): return Environment(self.types, self.constrainers, self.constraints, self.drawers, self.prefix + name + '.', self.frames + ({}, )) def add_constrainer(self, constrainer): self.constrainers.append((constrainer, self)) def add_drawer(self, drawer): self.drawers.append((drawer, self))
class Environment(Struct('types inst')): def spawn(self, inst): return Environment(self.types, inst) def init(self, id, value): assert id not in self.inst.mapping, "Multiple def: %s" % id self.inst.mapping[id] = value def fetch(self, id): assert id in self.inst.mapping, \ "Not found: %r in %r" % (id, self.inst) return self.inst.mapping[id]
class Definition(Struct('id extends decls')): def build(self, env): env.types[self.id] = self def instantiate(self, env): inst = Instance(self) subenv = env.spawn(inst) # XXX won't see global vars; should it? self.populate(subenv) return inst def populate(self, env): supe = self.super_definition(env) if supe: supe.populate(env) for decl in self.decls: decl.build(env) def draw(self, env): for drawer in self.pick_drawers(env) or env.inst.get_parts(): drawer.draw(env) def pick_drawers(self, env): defn = self while defn: decls = [decl for decl in defn.decls if isinstance(decl, Draw)] if decls: return decls defn = defn.super_definition(env) return [] def super_definition(self, env): if self.extends: supertype = env.types[self.extends] assert isinstance(supertype, Definition), \ "%r is not a definition" % supertype return supertype else: return None
class Combine(Struct('arg1 coeff arg2', supertype=(Expr, ))): def evaluate(self): return self.arg1.evaluate().combine(self.coeff, self.arg2.evaluate())
class CallPrim(Struct('fn arg')): def evaluate(self, env): return self.fn(self.arg.evaluate(env))
class Decl(Struct('names')): def build(self, env): for name in self.names: define(env.frames[-1], name, solver.make_variable(env.prefix + name))
class Structures: MSG_4_STRUCT = Struct('I') #4 MSG_2_STRUCT = Struct('H') #2
class VarDecl(Struct('type_id decls')): def build(self, env): type_ = env.types[self.type_id] for decl in self.decls: inst = type_.instantiate(env) env.init(decl.id, inst) for id, rhs in decl.params: LC.equate(inst.mapping[id], rhs.evaluate(env)) def draw(self, env): pass Declarator = Struct('id params', name='Declarator') class Constraints(Struct('equations')): def build(self, env): for lhs, rhs in self.equations: LC.equate(lhs.evaluate(env), rhs.evaluate(env)) def draw(self, env): pass class Draw(Struct('drawables')): def build(self, env): pass
class Number(Struct('value')): def evaluate(self, env): return self.value
class Dot(Struct('base field')): def evaluate(self, env): return self.base.evaluate(env).mapping[self.field]
class Name(Struct('id')): def evaluate(self, env): return env.fetch(self.id)
class DrawFunction(Struct('fn_name')): def draw(self, env): draw_functions[self.fn_name](**env.inst.mapping)
class BinaryOp(Struct('arg1 arg2')): def evaluate(self, env): return self.operate(self.arg1.evaluate(env), self.arg2.evaluate(env))
class DrawName(Struct('name')): def draw(self, env): self.name.evaluate(env).draw(env)