def RelativeTo(X, Y): xf, yf = isA(X, VectorField), isA(Y, VectorField) if xf or yf: if xf and yf and X.valueType != Y.valueType: raise RuntimeParseError('"X relative to Y" with X, Y fields of different types') fieldType = X.valueType if xf else Y.valueType error = '"X relative to Y" with field and value of different types' def helper(context): pos = context.position.toVector() xp = X[pos] if xf else toType(X, fieldType, error) yp = Y[pos] if yf else toType(Y, fieldType, error) return xp + yp return DelayedArgument({'position'}, helper) else: if isinstance(X, OrientedPoint): # TODO too strict? if isinstance(Y, OrientedPoint): raise RuntimeParseError('"X relative to Y" with X, Y both oriented points') Y = toVector(Y, '"X relative to Y" with X an oriented point but Y not a vector') return X.relativize(Y) elif isinstance(Y, OrientedPoint): X = toVector(X, '"X relative to Y" with Y an oriented point but X not a vector') return Y.relativize(X) else: X = toTypes(X, (Vector, float), '"X relative to Y" with X neither a vector nor scalar') Y = toTypes(Y, (Vector, float), '"X relative to Y" with Y neither a vector nor scalar') return valueRequiringEqualTypes(X + Y, X, Y, '"X relative to Y" with vector and scalar')
def __init__(self, opts): if isinstance(opts, dict): self.options = [] self.weights = dict() ordered = [] for opt, prob in opts.items(): if not isinstance(prob, (float, int)): raise RuntimeParseError( f'discrete distribution weight {prob}' ' is not a constant number') if prob < 0: raise RuntimeParseError( f'discrete distribution weight {prob} is negative') if prob == 0: continue opt = toDistribution(opt) self.options.append(opt) self.weights[opt] = prob ordered.append(prob) self.cumulativeWeights = tuple(itertools.accumulate(ordered)) else: self.options = tuple(toDistribution(opt) for opt in opts) self.cumulativeWeights = None if len(self.options) == 0: raise RuntimeParseError( 'tried to make discrete distribution over empty domain!') valueType = type_support.unifyingType(self.options) super().__init__(*self.options, valueType=valueType)
def __init__(self, opts): if isinstance(opts, dict): options, weights = [], [] for opt, prob in opts.items(): if not isinstance(prob, (float, int)): raise RuntimeParseError( f'discrete distribution weight {prob}' ' is not a constant number') if prob < 0: raise RuntimeParseError( f'discrete distribution weight {prob} is negative') if prob == 0: continue options.append(opt) weights.append(prob) self.optWeights = dict(zip(options, weights)) else: weights = None options = tuple(opts) self.optWeights = None if len(options) == 0: raise RuntimeParseError( 'tried to make discrete distribution over empty domain!') index = self.makeSelector(len(options) - 1, weights) super().__init__(index, options)
def ego(obj=None): global egoObject if obj is None: if egoObject is None: raise RuntimeParseError('referred to ego object not yet assigned') elif not isinstance(obj, Object): raise RuntimeParseError('tried to make non-object the ego object') else: egoObject = obj return egoObject
def mutate(*objects): # TODO update syntax """Function implementing the mutate statement.""" if evaluatingRequirement: raise RuntimeParseError('used mutate statement inside a requirement') if len(objects) == 0: objects = allObjects for obj in objects: if not isinstance(obj, Object): raise RuntimeParseError('"mutate X" with X not an object') obj.mutationEnabled = True
def RelativeTo(X, Y): """The 'X relative to Y' polymorphic operator. Allowed forms: F relative to G (with at least one a field, the other a field or heading) <vector> relative to <oriented point> (and vice versa) <vector> relative to <vector> <heading> relative to <heading> """ xf, yf = isA(X, VectorField), isA(Y, VectorField) if xf or yf: if xf and yf and X.valueType != Y.valueType: raise RuntimeParseError( '"X relative to Y" with X, Y fields of different types') fieldType = X.valueType if xf else Y.valueType error = '"X relative to Y" with field and value of different types' def helper(context): pos = context.position.toVector() xp = X[pos] if xf else toType(X, fieldType, error) yp = Y[pos] if yf else toType(Y, fieldType, error) return xp + yp return DelayedArgument({'position'}, helper) else: if isinstance(X, OrientedPoint): # TODO too strict? if isinstance(Y, OrientedPoint): raise RuntimeParseError( '"X relative to Y" with X, Y both oriented points') Y = toVector( Y, '"X relative to Y" with X an oriented point but Y not a vector' ) return X.relativize(Y) elif isinstance(Y, OrientedPoint): X = toVector( X, '"X relative to Y" with Y an oriented point but X not a vector' ) return Y.relativize(X) else: X = toTypes( X, (Vector, float), '"X relative to Y" with X neither a vector nor scalar') Y = toTypes( Y, (Vector, float), '"X relative to Y" with Y neither a vector nor scalar') return evaluateRequiringEqualTypes( lambda: X + Y, X, Y, '"X relative to Y" with vector and scalar')
def ego(obj=None): """Function implementing loads and stores to the 'ego' pseudo-variable. The translator calls this with no arguments for loads, and with the source value for stores. """ global egoObject if obj is None: if egoObject is None: raise RuntimeParseError('referred to ego object not yet assigned') elif not isinstance(obj, Object): raise RuntimeParseError('tried to make non-object the ego object') else: egoObject = obj return egoObject
def registerObject(obj): if activity > 0: assert not evaluatingRequirement assert isinstance(obj, Constructible) allObjects.append(obj) elif evaluatingRequirement: raise RuntimeParseError('tried to create an object inside a requirement')
def ApparentHeading(X, Y=None): if not isinstance(X, OrientedPoint): raise RuntimeParseError('"apparent heading of X from Y" with X not an OrientedPoint') if Y is None: Y = ego() Y = toVector(Y, '"relative heading of X from Y" with Y not a vector') return apparentHeadingAtPoint(X.position, X.heading, Y)
def param(**params): """Function implementing the param statement.""" if evaluatingRequirement: raise RuntimeParseError( 'tried to create a global parameter inside a requirement') for name, value in params.items(): globalParameters[name] = toDistribution(value)
def mutate(*objects): # TODO update syntax if len(objects) == 0: objects = allObjects for obj in objects: if not isinstance(obj, Object): raise RuntimeParseError('"mutate X" with X not an object') obj.mutationEnabled = True
def valueRequiringEqualTypes(val, thingA, thingB, typeError='type mismatch'): """Return the value, assuming thingA and thingB have the same type.""" if not needsLazyEvaluation(thingA) and not needsLazyEvaluation(thingB): if underlyingType(thingA) is not underlyingType(thingB): raise RuntimeParseError(typeError) return val else: return TypeEqualityChecker(val, thingA, thingB, typeError)
def CanSee(X, Y): if not isinstance(X, Point): raise RuntimeParseError('"X can see Y" with X not a Point') if isinstance(Y, Object): return X.canSee(Y) else: Y = toVector(Y, '"X can see Y" with Y not a vector') return X.visibleRegion.containsPoint(Y)
def Follow(F, X, D): if not isinstance(F, VectorField): raise RuntimeParseError('"follow F from X for D" with F not a vector field') X = toVector(X, '"follow F from X for D" with X not a vector') D = toScalar(D, '"follow F from X for D" with D not a number') pos = F.followFrom(X, D) heading = F[pos] return OrientedPoint(position=pos, heading=heading)
def dfs(spec): if spec in done: return elif spec in seen: raise RuntimeParseError( f'specifier for property {spec.property} ' 'depends on itself') seen.add(spec) for dep in spec.requiredProperties: child = properties.get(dep) if child is None: raise RuntimeParseError( f'property {dep} required by ' f'specifier {spec} is not specified') else: dfs(child) order.append(spec) done.add(spec)
def coerceToAny(thing, types, error): """Coerce something into any of the given types, printing an error if impossible.""" for ty in types: if canCoerce(thing, ty): return coerce(thing, ty) print( f'Failed to coerce {thing} of type {underlyingType(thing)} to {types}', file=sys.stderr) raise RuntimeParseError(error)
def __init__(self, prop, value, deps=None, optionals={}): self.property = prop self.value = toDelayedArgument(value) if deps is None: deps = set() deps |= requiredProperties(value) if prop in deps: raise RuntimeParseError(f'specifier for property {prop} depends on itself') self.requiredProperties = deps self.optionals = optionals
def param(*quotedParams, **params): """Function implementing the param statement.""" if evaluatingRequirement: raise RuntimeParseError( 'tried to create a global parameter inside a requirement') for name, value in params.items(): globalParameters[name] = toDistribution(value) assert len(quotedParams) % 2 == 0, quotedParams it = iter(quotedParams) for name, value in zip(it, it): globalParameters[name] = toDistribution(value)
def require(reqID, req, line, prob=1): """Function implementing the require statement.""" if evaluatingRequirement: raise RuntimeParseError( 'tried to create a requirement inside a requirement') # the translator wrapped the requirement in a lambda to prevent evaluation, # so we need to save the current values of all referenced names; throw in # the ego object too since it can be referred to implicitly assert reqID not in pendingRequirements pendingRequirements[reqID] = (req, getAllGlobals(req), egoObject, line, prob)
def registerObject(obj): """Add a Scenic object to the global list of created objects. This is called by the Object constructor.""" if activity > 0: assert not evaluatingRequirement assert isinstance(obj, Constructible) allObjects.append(obj) elif evaluatingRequirement: raise RuntimeParseError( 'tried to create an object inside a requirement')
def VisibleFrom(base): """The 'visible from <Point>' specifier. Specifies 'position', with no dependencies. This uses the given object's 'visibleRegion' property, and so correctly handles the view regions of Points, OrientedPoints, and Objects. """ if not isinstance(base, Point): raise RuntimeParseError( 'specifier "visible from O" with O not a Point') return Specifier('position', Region.uniformPointIn(base.visibleRegion))
def Beyond(pos, offset, fromPt=None): pos = toVector(pos, 'specifier "beyond X by Y" with X not a vector') dType = underlyingType(offset) if dType is float or dType is int: offset = Vector(0, offset) elif dType is not Vector: raise RuntimeParseError('specifier "beyond X by Y" with Y not a number or vector') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "beyond X by Y from Z" with Z not a vector') lineOfSight = fromPt.angleTo(pos) return Specifier('position', pos.offsetRotated(lineOfSight, offset))
def ApparentHeading(X, Y=None): """The 'apparent heading of <oriented point> [from <vector>]' operator. If the 'from <vector>' is omitted, the position of ego is used. """ if not isinstance(X, OrientedPoint): raise RuntimeParseError( '"apparent heading of X from Y" with X not an OrientedPoint') if Y is None: Y = ego() Y = toVector(Y, '"relative heading of X from Y" with Y not a vector') return apparentHeadingAtPoint(X.position, X.heading, Y)
def __init__(self, requiredProperties, attributes, value): self.requiredProperties = requiredProperties self.value = value def enabled(thing, default): if thing in attributes: attributes.remove(thing) return True else: return default self.isAdditive = enabled('additive', False) for attr in attributes: raise RuntimeParseError(f'unknown property attribute "{attr}"')
def CanSee(X, Y): """The 'X can see Y' polymorphic operator. Allowed forms: <point> can see <object> <point> can see <vector> """ if not isinstance(X, Point): raise RuntimeParseError('"X can see Y" with X not a Point') if isinstance(Y, Point): return X.canSee(Y) else: Y = toVector(Y, '"X can see Y" with Y not a vector') return X.visibleRegion.containsPoint(Y)
def evaluateRequiringEqualTypes(func, thingA, thingB, typeError='type mismatch'): """Evaluate the func, assuming thingA and thingB have the same type. If func produces a lazy value, it should not have any required properties beyond those of thingA and thingB.""" if not needsLazyEvaluation(thingA) and not needsLazyEvaluation(thingB): if underlyingType(thingA) is not underlyingType(thingB): raise RuntimeParseError(typeError) return func() else: # cannot check the types now; create proxy object to check types after evaluation return TypeEqualityChecker(func, thingA, thingB, typeError)
def leftSpecHelper(syntax, pos, dist, axis, toComponents, makeOffset): extras = set() dType = underlyingType(dist) if dType is float or dType is int: dx, dy = toComponents(dist) elif dType is Vector: dx, dy = dist else: raise RuntimeParseError(f'"{syntax} X by D" with D not a number or vector') if isinstance(pos, OrientedPoint): # TODO too strict? val = lambda self: pos.relativePosition(makeOffset(self, dx, dy)) new = DelayedArgument({axis}, val) extras.add('heading') else: pos = toVector(pos, f'specifier "{syntax} X" with X not a vector') val = lambda self: pos.offsetRotated(self.heading, makeOffset(self, dx, dy)) new = DelayedArgument({axis, 'heading'}, val) return Specifier('position', new, optionals=extras)
def Following(field, dist, fromPt=None): """The 'following F [from X] for D' specifier. Specifies 'position', and optionally 'heading', with no dependencies. Allowed forms: following <field> [from <vector>] for <number> If the 'from <vector>' is omitted, the position of ego is used. """ if fromPt is None: fromPt = ego() else: dist, fromPt = fromPt, dist if not isinstance(field, VectorField): raise RuntimeParseError( '"following F" specifier with F not a vector field') fromPt = toVector(fromPt, '"following F from X for D" with X not a vector') dist = toScalar(dist, '"following F for D" with D not a number') pos = field.followFrom(fromPt, dist) heading = field[pos] val = OrientedPoint(position=pos, heading=heading) return Specifier('position', val, optionals={'heading'})
def closure(values): # rebind any names referring to sampled objects for name, value in bindings.items(): if value in values: namespace[name] = values[value] # rebind ego object, which can be referred to implicitly if ego is not None: veneer.egoObject = values[ego] # evaluate requirement condition, reporting errors on the correct line try: veneer.evaluatingRequirement = True result = req() assert not needsSampling(result) if needsLazyEvaluation(result): raise RuntimeParseError( f'requirement on line {line} uses value' ' undefined outside of object definition') return result except RuntimeParseError as e: cause = e if showInternalBacktrace else None raise InterpreterParseError(e, line) from cause finally: veneer.evaluatingRequirement = False
def Beyond(pos, offset, fromPt=None): """The 'beyond X by Y [from Z]' polymorphic specifier. Specifies 'position', with no dependencies. Allowed forms: beyond <vector> by <number> [from <vector>] beyond <vector> by <vector> [from <vector>] If the 'from <vector>' is omitted, the position of ego is used. """ pos = toVector(pos, 'specifier "beyond X by Y" with X not a vector') dType = underlyingType(offset) if dType is float or dType is int: offset = Vector(0, offset) elif dType is not Vector: raise RuntimeParseError( 'specifier "beyond X by Y" with Y not a number or vector') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "beyond X by Y from Z" with Z not a vector') lineOfSight = fromPt.angleTo(pos) return Specifier('position', pos.offsetRotated(lineOfSight, offset))