Make OD-API for consistent for constraints, LHS patterns, RHS actions.
This commit is contained in:
parent
1eb8a84553
commit
9c68b288c1
8 changed files with 108 additions and 79 deletions
39
api/od.py
39
api/od.py
|
|
@ -211,3 +211,42 @@ class ODAPI:
|
||||||
|
|
||||||
def create_object(self, object_name: Optional[str], class_name: str):
|
def create_object(self, object_name: Optional[str], class_name: str):
|
||||||
return self.od.create_object(object_name, class_name)
|
return self.od.create_object(object_name, class_name)
|
||||||
|
|
||||||
|
|
||||||
|
# internal use
|
||||||
|
# Get API methods as bound functions, to pass as globals to 'eval'
|
||||||
|
# Readonly version is used for:
|
||||||
|
# - Conformance checking
|
||||||
|
# - Pattern matching (LHS/NAC of rule)
|
||||||
|
def bind_api_readonly(odapi):
|
||||||
|
funcs = {
|
||||||
|
'read_value': odapi.state.read_value,
|
||||||
|
'get': odapi.get,
|
||||||
|
'get_value': odapi.get_value,
|
||||||
|
'get_target': odapi.get_target,
|
||||||
|
'get_source': odapi.get_source,
|
||||||
|
'get_slot': odapi.get_slot,
|
||||||
|
'get_slot_value': odapi.get_slot_value,
|
||||||
|
'get_slot_value_default': odapi.get_slot_value_default,
|
||||||
|
'get_all_instances': odapi.get_all_instances,
|
||||||
|
'get_name': odapi.get_name,
|
||||||
|
'get_type_name': odapi.get_type_name,
|
||||||
|
'get_outgoing': odapi.get_outgoing,
|
||||||
|
'get_incoming': odapi.get_incoming,
|
||||||
|
'has_slot': odapi.has_slot,
|
||||||
|
}
|
||||||
|
return funcs
|
||||||
|
|
||||||
|
# internal use
|
||||||
|
# Get API methods as bound functions, to pass as globals to 'eval'
|
||||||
|
# Read/write version is used for:
|
||||||
|
# - Graph rewriting (RHS of rule)
|
||||||
|
def bind_api(odapi):
|
||||||
|
funcs = {
|
||||||
|
**bind_api_readonly(odapi),
|
||||||
|
'create_object': odapi.create_object,
|
||||||
|
'create_link': odapi.create_link,
|
||||||
|
'delete': odapi.delete,
|
||||||
|
'set_slot_value': odapi.set_slot_value,
|
||||||
|
}
|
||||||
|
return funcs
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,16 @@ def indent(multiline_string, how_much):
|
||||||
lines = multiline_string.split('\n')
|
lines = multiline_string.split('\n')
|
||||||
return '\n'.join([' '*how_much+l for l in lines])
|
return '\n'.join([' '*how_much+l for l in lines])
|
||||||
|
|
||||||
def display_value(val: any, type_name: str, indentation=0):
|
def display_value(val: any, type_name: str, indentation=0, newline_character='\n'):
|
||||||
if type_name == "ActionCode":
|
if type_name == "ActionCode":
|
||||||
if '\n' in val:
|
if '\n' in val:
|
||||||
return '```\n'+indent(val, indentation+4)+'\n'+' '*indentation+'```'
|
orig = '```\n'+indent(val, indentation+4)+'\n'+' '*indentation+'```'
|
||||||
|
escaped = orig.replace('\n', newline_character)
|
||||||
|
return escaped
|
||||||
else:
|
else:
|
||||||
return '`'+val+'`'
|
return '`'+val+'`'
|
||||||
elif type_name == "String":
|
elif type_name == "String":
|
||||||
return '"'+val+'"'
|
return '"'+val+'"'.replace('\n', newline_character)
|
||||||
elif type_name == "Integer" or type_name == "Boolean":
|
elif type_name == "Integer" or type_name == "Boolean":
|
||||||
return str(val)
|
return str(val)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,8 @@ def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""):
|
||||||
slot = m_od.get_slot(obj_node, attr_name)
|
slot = m_od.get_slot(obj_node, attr_name)
|
||||||
if slot != None:
|
if slot != None:
|
||||||
val, type_name = od.read_primitive_value(bottom, slot, mm)
|
val, type_name = od.read_primitive_value(bottom, slot, mm)
|
||||||
output += f"\n{attr_name} => {display_value(val, type_name)}"
|
escaped_newline = ";"
|
||||||
|
output += f"\n{attr_name} => {display_value(val, type_name, newline_character=escaped_newline)}"
|
||||||
output += '\n}'
|
output += '\n}'
|
||||||
|
|
||||||
output += '\n'
|
output += '\n'
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from transformation import rewriter
|
||||||
from services.bottom.V0 import Bottom
|
from services.bottom.V0 import Bottom
|
||||||
from services.primitives.integer_type import Integer
|
from services.primitives.integer_type import Integer
|
||||||
from concrete_syntax.plantuml import renderer as plantuml
|
from concrete_syntax.plantuml import renderer as plantuml
|
||||||
|
from concrete_syntax.plantuml.make_url import make_url as make_plantuml_url
|
||||||
from concrete_syntax.textual_od import parser, renderer
|
from concrete_syntax.textual_od import parser, renderer
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
@ -112,7 +113,9 @@ def main():
|
||||||
# object to match
|
# object to match
|
||||||
man:{prefix}Man {{
|
man:{prefix}Man {{
|
||||||
# match only men heavy enough
|
# match only men heavy enough
|
||||||
{prefix}weight = `v > 60`;
|
{prefix}weight = ```
|
||||||
|
get_value(this) > 60
|
||||||
|
```;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
# object to delete
|
# object to delete
|
||||||
|
|
@ -134,7 +137,7 @@ def main():
|
||||||
# matched object
|
# matched object
|
||||||
man:{prefix}Man {{
|
man:{prefix}Man {{
|
||||||
# man gains weight
|
# man gains weight
|
||||||
{prefix}weight = `v + 5`;
|
{prefix}weight = `get_value(this) + 5`;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
# object to create
|
# object to create
|
||||||
|
|
@ -216,7 +219,7 @@ def main():
|
||||||
# Render conformance
|
# Render conformance
|
||||||
uml += plantuml.render_trace_conformance(state, snapshot_dsl_m_id, dsl_mm_id)
|
uml += plantuml.render_trace_conformance(state, snapshot_dsl_m_id, dsl_mm_id)
|
||||||
|
|
||||||
return uml
|
return make_plantuml_url(uml)
|
||||||
|
|
||||||
# plantuml_str = render_all_matches()
|
# plantuml_str = render_all_matches()
|
||||||
plantuml_str = render_rewrite()
|
plantuml_str = render_rewrite()
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from concrete_syntax.common import indent
|
||||||
from util.eval import exec_then_eval
|
from util.eval import exec_then_eval
|
||||||
|
|
||||||
from api.cd import CDAPI
|
from api.cd import CDAPI
|
||||||
from api.od import ODAPI
|
from api.od import ODAPI, bind_api_readonly
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ class Conformance:
|
||||||
for ref_inst_name, ref_inst in self.odapi.get_all_instances(ref_name):
|
for ref_inst_name, ref_inst in self.odapi.get_all_instances(ref_name):
|
||||||
sub_m = UUID(self.bottom.read_value(ref_inst))
|
sub_m = UUID(self.bottom.read_value(ref_inst))
|
||||||
nested_errors = Conformance(self.state, sub_m, sub_mm).check_nominal()
|
nested_errors = Conformance(self.state, sub_m, sub_mm).check_nominal()
|
||||||
errors += [f"In ModelRef ({m_name}):" + err for err in nested_errors]
|
errors += [f"In ModelRef ({ref_name}):" + err for err in nested_errors]
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
@ -219,38 +219,6 @@ class Conformance:
|
||||||
errors.append(f"Target cardinality of type '{assoc_name}' ({count}) out of bounds ({lc}..{uc}) in '{obj_name}'.")
|
errors.append(f"Target cardinality of type '{assoc_name}' ({count}) out of bounds ({lc}..{uc}) in '{obj_name}'.")
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def evaluate_constraint(self, code, **kwargs):
|
|
||||||
"""
|
|
||||||
Evaluate constraint code (Python code)
|
|
||||||
"""
|
|
||||||
|
|
||||||
funcs = {
|
|
||||||
'read_value': self.state.read_value,
|
|
||||||
'get': self.odapi.get,
|
|
||||||
'get_value': self.odapi.get_value,
|
|
||||||
'get_target': self.odapi.get_target,
|
|
||||||
'get_source': self.odapi.get_source,
|
|
||||||
'get_slot': self.odapi.get_slot,
|
|
||||||
'get_slot_value': self.odapi.get_slot_value,
|
|
||||||
'get_all_instances': self.odapi.get_all_instances,
|
|
||||||
'get_name': self.odapi.get_name,
|
|
||||||
'get_type_name': self.odapi.get_type_name,
|
|
||||||
'get_outgoing': self.odapi.get_outgoing,
|
|
||||||
'get_incoming': self.odapi.get_incoming,
|
|
||||||
'has_slot': self.odapi.has_slot,
|
|
||||||
}
|
|
||||||
# print("evaluating constraint ...", code)
|
|
||||||
loc = {**kwargs, }
|
|
||||||
result = exec_then_eval(
|
|
||||||
code,
|
|
||||||
{'__builtins__': {'isinstance': isinstance, 'print': print,
|
|
||||||
'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len, 'set': set, 'dict': dict},
|
|
||||||
**funcs
|
|
||||||
}, # globals
|
|
||||||
loc # locals
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def check_constraints(self):
|
def check_constraints(self):
|
||||||
"""
|
"""
|
||||||
Check whether all constraints defined for a model are respected
|
Check whether all constraints defined for a model are respected
|
||||||
|
|
@ -288,7 +256,7 @@ class Conformance:
|
||||||
description = f"Local constraint of \"{type_name}\" in \"{obj_name}\""
|
description = f"Local constraint of \"{type_name}\" in \"{obj_name}\""
|
||||||
# print(description)
|
# print(description)
|
||||||
try:
|
try:
|
||||||
result = self.evaluate_constraint(code, this=obj_id) # may raise
|
result = exec_then_eval(code, _globals=bind_api_readonly(self.odapi), _locals={'this': obj_id}) # may raise
|
||||||
check_result(result, description)
|
check_result(result, description)
|
||||||
except:
|
except:
|
||||||
errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
|
errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
|
||||||
|
|
@ -310,10 +278,10 @@ class Conformance:
|
||||||
if code != None:
|
if code != None:
|
||||||
description = f"Global constraint \"{tm_name}\""
|
description = f"Global constraint \"{tm_name}\""
|
||||||
try:
|
try:
|
||||||
result = self.evaluate_constraint(code, model=self.model)
|
result = exec_then_eval(code, _globals=bind_api_readonly(self.odapi)) # may raise
|
||||||
|
check_result(result, description)
|
||||||
except:
|
except:
|
||||||
errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
|
errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
|
||||||
check_result(result, description)
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def precompute_structures(self):
|
def precompute_structures(self):
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
from api.cd import CDAPI
|
from api.cd import CDAPI
|
||||||
|
from api.od import ODAPI, bind_api_readonly
|
||||||
|
from util.eval import exec_then_eval
|
||||||
from state.base import State
|
from state.base import State
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from services.bottom.V0 import Bottom
|
from services.bottom.V0 import Bottom
|
||||||
from services.scd import SCD
|
from services.scd import SCD
|
||||||
from services.od import OD
|
from services import od as services_od
|
||||||
from transformation.matcher.matcher import Graph, Edge, Vertex, MatcherVF2
|
from transformation.matcher.matcher import Graph, Edge, Vertex, MatcherVF2
|
||||||
from transformation import ramify
|
from transformation import ramify
|
||||||
import itertools
|
import itertools
|
||||||
|
|
@ -76,7 +78,7 @@ UUID_REGEX = re.compile(r"[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-
|
||||||
# ModelRefs are flattened
|
# ModelRefs are flattened
|
||||||
def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
||||||
# with Timer("model_to_graph"):
|
# with Timer("model_to_graph"):
|
||||||
od = OD(model, metamodel, state)
|
od = services_od.OD(model, metamodel, state)
|
||||||
scd = SCD(model, state)
|
scd = SCD(model, state)
|
||||||
scd_mm = SCD(metamodel, state)
|
scd_mm = SCD(metamodel, state)
|
||||||
|
|
||||||
|
|
@ -208,6 +210,7 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
||||||
|
|
||||||
# compute subtype relations and such:
|
# compute subtype relations and such:
|
||||||
cdapi = CDAPI(state, host_mm)
|
cdapi = CDAPI(state, host_mm)
|
||||||
|
odapi = ODAPI(state, host_m, host_mm)
|
||||||
|
|
||||||
# Function object for pattern matching. Decides whether to match host and guest vertices, where guest is a RAMified instance (e.g., the attributes are all strings with Python expressions), and the host is an instance (=object diagram) of the original model (=class diagram)
|
# Function object for pattern matching. Decides whether to match host and guest vertices, where guest is a RAMified instance (e.g., the attributes are all strings with Python expressions), and the host is an instance (=object diagram) of the original model (=class diagram)
|
||||||
class RAMCompare:
|
class RAMCompare:
|
||||||
|
|
@ -251,16 +254,26 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
||||||
if hasattr(g_vtx, 'modelref'):
|
if hasattr(g_vtx, 'modelref'):
|
||||||
if not hasattr(h_vtx, 'modelref'):
|
if not hasattr(h_vtx, 'modelref'):
|
||||||
return False
|
return False
|
||||||
g_ref_m, g_ref_mm = g_vtx.modelref
|
|
||||||
h_ref_m, h_ref_mm = h_vtx.modelref
|
python_code = services_od.read_primitive_value(self.bottom, g_vtx.node_id, pattern_mm)[0]
|
||||||
nested_matches = [m for m in match_od(state, h_ref_m, h_ref_mm, g_ref_m, g_ref_mm)]
|
return exec_then_eval(python_code,
|
||||||
|
_globals=bind_api_readonly(odapi),
|
||||||
|
_locals={'this': h_vtx.node_id})
|
||||||
|
|
||||||
|
# nested_matches = [m for m in match_od(state, h_ref_m, h_ref_mm, g_ref_m, g_ref_mm)]
|
||||||
|
|
||||||
|
|
||||||
|
# print('begin recurse')
|
||||||
|
# g_ref_m, g_ref_mm = g_vtx.modelref
|
||||||
|
# h_ref_m, h_ref_mm = h_vtx.modelref
|
||||||
# print('nested_matches:', nested_matches)
|
# print('nested_matches:', nested_matches)
|
||||||
if len(nested_matches) == 0:
|
# if len(nested_matches) == 0:
|
||||||
return False
|
# return False
|
||||||
elif len(nested_matches) == 1:
|
# elif len(nested_matches) == 1:
|
||||||
return True
|
# return True
|
||||||
else:
|
# else:
|
||||||
raise Exception("We have a problem: there is more than 1 match in the nested models.")
|
# raise Exception("We have a problem: there is more than 1 match in the nested models.")
|
||||||
|
# print('end recurse')
|
||||||
|
|
||||||
# Then, match by value
|
# Then, match by value
|
||||||
|
|
||||||
|
|
@ -280,23 +293,15 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
||||||
if h_vtx.value == IS_MODELREF:
|
if h_vtx.value == IS_MODELREF:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# # print(g_vtx.value, h_vtx.value)
|
# python_code = g_vtx.value
|
||||||
# def get_slot(h_vtx, slot_name: str):
|
# try:
|
||||||
# slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
|
# return exec_then_eval(python_code,
|
||||||
# return slot_node
|
# _globals=bind_api_readonly(odapi),
|
||||||
|
# _locals={'this': h_vtx.node_id})
|
||||||
# def read_int(slot: UUID):
|
# except Exception as e:
|
||||||
# i = Integer(slot, self.bottom.state)
|
# print(e)
|
||||||
# return i.read()
|
# return False
|
||||||
|
return True
|
||||||
try:
|
|
||||||
return eval(g_vtx.value, {}, {
|
|
||||||
'v': h_vtx.value,
|
|
||||||
# 'get_slot': functools.partial(get_slot, h_vtx),
|
|
||||||
# 'read_int': read_int,
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Convert to format understood by matching algorithm
|
# Convert to format understood by matching algorithm
|
||||||
h_names, host = model_to_graph(state, host_m, host_mm)
|
h_names, host = model_to_graph(state, host_m, host_mm)
|
||||||
|
|
@ -309,7 +314,7 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
||||||
if guest_name in g_names
|
if guest_name in g_names
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher = MatcherVF2(host, guest, RAMCompare(Bottom(state), OD(host_mm, host_m, state)))
|
matcher = MatcherVF2(host, guest, RAMCompare(Bottom(state), services_od.OD(host_mm, host_m, state)))
|
||||||
for m in matcher.match(graph_pivot):
|
for m in matcher.match(graph_pivot):
|
||||||
# print("\nMATCH:\n", m)
|
# print("\nMATCH:\n", m)
|
||||||
# Convert mapping
|
# Convert mapping
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@
|
||||||
# - ? that's it?
|
# - ? that's it?
|
||||||
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from api.od import ODAPI, bind_api
|
||||||
from services.bottom.V0 import Bottom
|
from services.bottom.V0 import Bottom
|
||||||
from transformation import ramify
|
from transformation import ramify
|
||||||
from services import od
|
from services import od
|
||||||
from services.primitives.string_type import String
|
from services.primitives.string_type import String
|
||||||
from services.primitives.actioncode_type import ActionCode
|
from services.primitives.actioncode_type import ActionCode
|
||||||
from services.primitives.integer_type import Integer
|
from services.primitives.integer_type import Integer
|
||||||
|
from util.eval import exec_then_eval
|
||||||
|
|
||||||
def process_rule(state, lhs: UUID, rhs: UUID):
|
def process_rule(state, lhs: UUID, rhs: UUID):
|
||||||
bottom = Bottom(state)
|
bottom = Bottom(state)
|
||||||
|
|
@ -41,6 +43,8 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
||||||
|
|
||||||
to_delete, to_create, common = process_rule(state, lhs_m, rhs_m)
|
to_delete, to_create, common = process_rule(state, lhs_m, rhs_m)
|
||||||
|
|
||||||
|
odapi = ODAPI(state, host_m, mm)
|
||||||
|
|
||||||
# Perform deletions
|
# Perform deletions
|
||||||
for pattern_name_to_delete in to_delete:
|
for pattern_name_to_delete in to_delete:
|
||||||
# For every name in `to_delete`, look up the name of the matched element in the host graph
|
# For every name in `to_delete`, look up the name of the matched element in the host graph
|
||||||
|
|
@ -95,7 +99,8 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
||||||
if type_name == "ActionCode":
|
if type_name == "ActionCode":
|
||||||
# Assume the string is a Python expression to evaluate
|
# Assume the string is a Python expression to evaluate
|
||||||
python_expr = ActionCode(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
|
python_expr = ActionCode(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
|
||||||
result = eval(python_expr, {}, {})
|
|
||||||
|
result = exec_then_eval(python_expr, _globals=bind_api(odapi))
|
||||||
# Write the result into the host model.
|
# Write the result into the host model.
|
||||||
# This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
|
# This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
|
||||||
if isinstance(result, int):
|
if isinstance(result, int):
|
||||||
|
|
@ -152,8 +157,10 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
||||||
print(' -> is modelref')
|
print(' -> is modelref')
|
||||||
old_value, _ = od.read_primitive_value(bottom, host_el, mm)
|
old_value, _ = od.read_primitive_value(bottom, host_el, mm)
|
||||||
rhs_el, = bottom.read_outgoing_elements(rhs_m, pattern_el_name)
|
rhs_el, = bottom.read_outgoing_elements(rhs_m, pattern_el_name)
|
||||||
expr, _ = od.read_primitive_value(bottom, rhs_el, pattern_mm)
|
python_expr, _ = od.read_primitive_value(bottom, rhs_el, pattern_mm)
|
||||||
result = eval(expr, {}, {'v': old_value})
|
result = exec_then_eval(python_expr,
|
||||||
|
_globals=bind_api(odapi),
|
||||||
|
_locals={'this': host_el})
|
||||||
# print('eval result=', result)
|
# print('eval result=', result)
|
||||||
if isinstance(result, int):
|
if isinstance(result, int):
|
||||||
# overwrite the old value, in-place
|
# overwrite the old value, in-place
|
||||||
|
|
|
||||||
10
util/eval.py
10
util/eval.py
|
|
@ -1,9 +1,13 @@
|
||||||
# based on https://stackoverflow.com/a/39381428
|
# based on https://stackoverflow.com/a/39381428
|
||||||
# Parses and executes a block of Python code, and returns the eval result of the last statement
|
# Parses and executes a block of Python code, and returns the eval result of the last statement
|
||||||
import ast
|
import ast
|
||||||
def exec_then_eval(code, _globals, _locals):
|
def exec_then_eval(code, _globals={}, _locals={}):
|
||||||
block = ast.parse(code, mode='exec')
|
block = ast.parse(code, mode='exec')
|
||||||
# assumes last node is an expression
|
# assumes last node is an expression
|
||||||
last = ast.Expression(block.body.pop().value)
|
last = ast.Expression(block.body.pop().value)
|
||||||
exec(compile(block, '<string>', mode='exec'), _globals, _locals)
|
extended_globals = {
|
||||||
return eval(compile(last, '<string>', mode='eval'), _globals, _locals)
|
'__builtins__': {'isinstance': isinstance, 'print': print, 'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len, 'set': set, 'dict': dict },
|
||||||
|
**_globals,
|
||||||
|
}
|
||||||
|
exec(compile(block, '<string>', mode='exec'), extended_globals, _locals)
|
||||||
|
return eval(compile(last, '<string>', mode='eval'), extended_globals, _locals)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue