# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Created on Jul 23, 2019
#
# @author: ballance
import inspect
from vsc.impl.randobj_int import RandObjInt
from vsc.constraints import constraint_t, dynamic_constraint_t
from vsc.impl.ctor import push_constraint_scope, pop_constraint_scope, \
clear_exprs, push_srcinfo_mode, pop_srcinfo_mode, in_srcinfo_mode
from vsc.impl.generator_int import GeneratorInt
from vsc.impl.expr_mode import _expr_mode, get_expr_mode, expr_mode, get_expr_mode_depth, \
enter_expr_mode, leave_expr_mode, is_raw_mode, is_expr_mode
from vsc.model.field_composite_model import FieldCompositeModel
from vsc.model.constraint_block_model import ConstraintBlockModel
from vsc.model.randomizer import Randomizer
from vsc.model.field_scalar_model import FieldScalarModel
from vsc.model.source_info import SourceInfo
from vsc.types import type_base, field_info, list_t
from vsc.model.solve_failure import SolveFailure
from vsc.impl.constraint_proxy import ConstraintProxy
class _randobj:
"""Mark a class as randomizable"""
def __init__(self, kwargs):
self.srcinfo = False
for kw in kwargs.keys():
if kw == "srcinfo":
self.srcinfo = kwargs[kw]
else:
raise Exception("Unknown randobj kwarg: %s" % kw)
def __call__(self, T):
srcinfo = self.srcinfo
class randobj_interposer(T):
def __init__(self, *args, **kwargs):
ro_i = self._get_ro_int()
ro_i.srcinfo = srcinfo
# Capture the instantiation location
frame = inspect.stack()[1]
ro_i.srcinfo_inst = SourceInfo(frame.filename, frame.lineno)
# Initialize the field_info member before going deeper
if ro_i.ctor_level == 0:
self.tname = T.__qualname__
self._int_field_info = field_info()
# Decide whether to record sourceinfo for this class
push_srcinfo_mode(srcinfo)
# Call the user's constructor
ro_i.ctor_level += 1
super().__init__(*args, **kwargs)
ro_i.ctor_level -= 1
if ro_i.ctor_level == 0:
self.build_field_model(None)
pop_srcinfo_mode()
# Add the interposer class
ret = type(T.__name__, (randobj_interposer,), dict())
if not hasattr(T, "_ro_init"):
def __getattribute__(self, a):
ret = object.__getattribute__(self, a)
if isinstance(ret, type_base) and not is_raw_mode():
# We're not in an expression, so the user
# wants the value of this field
ret = ret.get_val()
elif a == "rand_mode":
ret = self._int_rand_info.rand_mode
elif isinstance(ret, (constraint_t,dynamic_constraint_t)):
if not is_expr_mode():
# The constraint_t wrapper is per-type. In regular
# procedural code we need to return a reference
# to the instance object. The proxy provides a
# way to do so.
model = object.__getattribute__(self, "get_model")()
cm = model.get_constraint(a)
ret = ConstraintProxy(cm)
return ret
def __setattr__(self, field, val):
try:
# Retrieve the field object so we can check if it's
# a type_base object. This will throw an exception
# if the field doesn't exist
fo = object.__getattribute__(self, field)
except:
object.__setattr__(self, field, val)
else:
if isinstance(fo, type_base):
if not is_raw_mode():
# We're not in an expression context, so the
# user really wants us to set the actual value
# of the field
if isinstance(val, type_base):
# Looks like we're re-assigning it.
if self._get_ro_int().ctor_level > 0:
object.__setattr__(self, field, val)
else:
raise Exception("Cannot re-construct field")
else:
fo.set_val(val)
else:
raise Exception("Attempting to use '=' in a constraint")
elif isinstance(fo, list_t):
fo.clear()
for i in val:
fo.append(i)
elif field == "rand_mode":
self._int_rand_info.rand_mode = bool(val)
else:
object.__setattr__(self, field, val)
def get_randstate(self):
ro_int = self._get_ro_int()
# Return a copy to the user
return ro_int.get_randstate().clone()
def set_randstate(self, rs):
ro_int = self._get_ro_int()
ro_int.set_randstate(rs)
def randomize(self,
debug=0,
lint=0,
solve_fail_debug=0):
ro_int = self._get_ro_int()
frame = inspect.stack()[1]
model = self.get_model()
try:
Randomizer.do_randomize(
ro_int.get_randstate(),
SourceInfo(frame.filename, frame.lineno),
[model],
debug=debug,
lint=lint,
solve_fail_debug=solve_fail_debug)
except SolveFailure as e:
print(e.diagnostics)
raise e
def build_field_model(self, name):
if self._int_field_info.model is None:
model = FieldCompositeModel(name, self._int_field_info.is_rand, self)
model.typename = T.__qualname__
self._int_field_info.model = model
# Iterate through the fields and constraints
# First, assign IDs to each of the randomized fields
with expr_mode():
for f in dir(self):
if not f.startswith("__") and not f.startswith("_int"):
fo = getattr(self, f)
if hasattr(fo, "_int_field_info"):
if fo._int_field_info.model is None:
fo._int_field_info.model = fo.build_field_model(f)
else:
# Some fields may already be created, and will
# have been given a placeholder name. Back-annotate
# the proper name now
fo._int_field_info.model.name = f
fo._int_field_info.parent = self._int_field_info
model.add_field(fo._int_field_info.model)
# Now, elaborate the constraints
for f in dir(self):
if not f.startswith("__") and not f.startswith("_int"):
fo = getattr(self, f)
if isinstance(fo, constraint_t):
clear_exprs()
block = ConstraintBlockModel(f)
block.srcinfo = fo.srcinfo
push_constraint_scope(block)
try:
fo.c(self)
except Exception as e:
print("Exception while processing constraint: " + str(e))
raise e
fo.set_model(pop_constraint_scope())
model.add_constraint(fo.model)
clear_exprs()
elif isinstance(fo, dynamic_constraint_t):
clear_exprs()
block = ConstraintBlockModel(f)
block.srcinfo = fo.srcinfo
push_constraint_scope(block)
try:
fo.c(self)
except Exception as e:
print("Exception while processing constraint: " + str(e))
raise e
fo.set_model(pop_constraint_scope())
fo.model.is_dynamic = True
model.add_dynamic_constraint(fo.model)
clear_exprs()
self._int_field_info.model.name = name
return self._int_field_info.model
def get_model(self):
with expr_mode():
if self._int_field_info.model is None:
self._int_field_info.model = self.build_field_model(None)
return self._int_field_info.model
def _get_ro_int(self):
if not hasattr(self, "_ro_int"):
self._ro_int = RandObjInt()
return self._ro_int
def __enter__(self):
ro_i = self._get_ro_int()
enter_expr_mode()
self.get_model() # Ensure model is constructed
push_srcinfo_mode(ro_i.srcinfo)
push_constraint_scope(ConstraintBlockModel("inline"))
return self
def __exit__(self, t, v, tb):
ro_i = self._get_ro_int()
frame = inspect.stack()[1]
c = pop_constraint_scope()
leave_expr_mode()
pop_srcinfo_mode()
model = self.get_model() # Ensure model is constructed
try:
Randomizer.do_randomize(
ro_i.get_randstate(),
SourceInfo(frame.filename, frame.lineno),
[model],
[c],
debug=self.debug,
lint=self.lint,
solve_fail_debug=self.solve_fail_debug)
except SolveFailure as e:
print(e.diagnostics)
raise e
def randomize_with(self,
debug=0,
lint=0,
solve_fail_debug=0):
# Ensure the 'model' data structures have been built
self.get_model()
self.debug = debug
self.lint = lint
self.solve_fail_debug = solve_fail_debug
return self
def do_pre_randomize(self):
if hasattr(self, "pre_randomize"):
self.pre_randomize()
def do_post_randomize(self):
if hasattr(self, "post_randomize"):
self.post_randomize()
def _id_fields(self, it, parent):
"""Apply an ID to all fields so they can be
referenced using indexed expressions
"""
it._int_field_info.parent = parent
fid = 0
for fn in dir(it):
fo = getattr(it, fn)
if hasattr(fo, "_int_field_info"):
fi = fo._int_field_info
fi.id = fid
fi.parent = it._int_field_info
fid += 1
if fi.is_composite:
self._id_fields(fo, fi)
setattr(T, "__getattribute__", __getattribute__)
setattr(T, "__setattr__", __setattr__)
setattr(T, "randomize", randomize)
setattr(T, "randomize_with", randomize_with)
setattr(T, "build_field_model", build_field_model)
setattr(T, "get_model", get_model)
setattr(T, "set_randstate", set_randstate)
setattr(T, "get_randstate", get_randstate)
setattr(T, "_get_ro_int", _get_ro_int)
setattr(T, "__enter__", __enter__)
setattr(T, "__exit__", __exit__)
setattr(T, "do_pre_randomize", do_pre_randomize)
setattr(T, "do_post_randomize", do_post_randomize)
setattr(T, "_id_fields", _id_fields)
setattr(T, "_ro_init", True)
return ret
[docs]
def randobj(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# Called without arguments
obj = _randobj({})
# Capture the original module, since our inheritance
# trick causes the module to not match the user's module
args[0].original_module = args[0].__module__
return obj(args[0])
else:
obj = _randobj(kwargs)
return obj
[docs]
def generator(T):
"""Mark a class as a generator"""
class generator_interposer(T):
def __init__(self, *args, **kwargs):
gen_i = self._get_int()
# Capture the instantiation location
frame = inspect.stack()[1]
gen_i.srcinfo_inst = SourceInfo(frame.filename, frame.lineno)
# Call the user's constructor
with gen_i:
super().__init__(*args, **kwargs)
self._int_field_info = field_info()
if gen_i.ctor_level == 0:
self.build_model()
pass
# Add the interposer class
ret = type(T.__name__, (generator_interposer,), dict())
if not hasattr(T, "_gen_init"):
def __getattribute__(self, a):
ret = object.__getattribute__(self, a)
if isinstance(ret, type_base) and not is_raw_mode():
# We're not in an expression, so the user
# wants the value of this field
ret = ret.get_val()
return ret
def __setattr__(self, field, val):
try:
# Retrieve the field object so we can check if it's
# a type_base object. This will throw an exception
# if the field doesn't exist
fo = object.__getattribute__(self, field)
except:
object.__setattr__(self, field, val)
else:
if isinstance(fo, type_base):
if not is_raw_mode():
# We're not in an expression context, so the
# user really wants us to set the actual value
# of the field
fo.set_val(val)
else:
raise Exception("Attempting to use '=' in a constraint")
else:
object.__setattr__(self, field, val)
def randomize(self):
model = self.get_model()
Randomizer.do_randomize([model])
def build_field_model(self, name):
if self._int_field_info.model is None:
model = FieldCompositeModel(name, self._int_field_info.is_rand, self)
model.typename = T.__name__
self._int_field_info.model = model
# Iterate through the fields and constraints
# First, assign IDs to each of the randomized fields
with expr_mode():
for f in dir(self):
if not f.startswith("__") and not f.startswith("_int"):
fo = getattr(self, f)
if hasattr(fo, "_int_field_info"):
if fo._int_field_info.model is None:
fo._int_field_info.model = fo.build_field_model(f)
model.add_field(fo._int_field_info.model)
# Now, elaborate the constraints
for f in dir(self):
if not f.startswith("__") and not f.startswith("_int"):
fo = getattr(self, f)
if isinstance(fo, constraint_t):
clear_exprs()
block = ConstraintBlockModel(f)
block.srcinfo = fo.srcinfo
push_constraint_scope(block)
try:
fo.c(self)
except Exception as e:
print("Exception while processing constraint: " + str(e))
raise e
fo.set_model(pop_constraint_scope())
model.add_constraint(fo.model)
clear_exprs()
self._int_field_info.model.name = name
return self._int_field_info.model
def get_model(self):
with expr_mode():
if self._int_field_info.model is None:
self._int_field_info.model = self.build_field_model(None)
return self._int_field_info.model
def _get_int(self):
if not hasattr(self, "_gen_int"):
self._gen_int = GeneratorInt()
return self._gen_int
setattr(T, "__getattribute__", __getattribute__)
setattr(T, "__setattr__", __setattr__)
setattr(T, "randomize", randomize)
# setattr(T, "randomize_with", randomize_with)
setattr(T, "build_field_model", build_field_model)
setattr(T, "get_model", get_model)
# setattr(T, "__enter__", __enter__)
# setattr(T, "__exit__", __exit__)
# setattr(T, "do_pre_randomize", do_pre_randomize)
# setattr(T, "do_post_randomize", do_post_randomize)
setattr(T, "_int_field_info", field_info(True))
setattr(T, "_get_int", _get_int)
setattr(T, "_ro_init", True)
return ret