diff --git a/DOCS.md b/DOCS.md
index 44e7cda..2d8ec48 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -264,6 +264,8 @@ Types:
`.` (`msg.prm`) - Allows to access the value of `prm` from `msg`.
+`LOGS level: {debug, info, warning, error, critical}, name0: Any, name1: Any, ...` - Logs a message with specified `level` and `name0`, `name1`, ... as arguments.
+
## Message Scope
### Parameters
diff --git a/aasm/__init__.py b/aasm/__init__.py
index 02992b8..017f2d3 100644
--- a/aasm/__init__.py
+++ b/aasm/__init__.py
@@ -1,7 +1,9 @@
"""Agents Assembly translator"""
-__version__ = "0.0.57"
+__version__ = "0.0.58"
from aasm.generating.code import Code
+from aasm.modules.module import Module
from aasm.generating.python_spade import get_spade_code
+from aasm.generating.python_module import get_modules_for_target
from aasm.utils.exception import PanicException
diff --git a/aasm/generating/code.py b/aasm/generating/code.py
index 8eaacaa..71d5bfc 100644
--- a/aasm/generating/code.py
+++ b/aasm/generating/code.py
@@ -4,15 +4,27 @@
class Code:
- def __init__(self, agent_code_lines: List[str], graph_code_lines: List[str]):
+ def __init__(
+ self,
+ agent_code_lines: List[str],
+ graph_code_lines: List[str],
+ module_code_lines: List[str],
+ ):
self.agent_code_lines: List[str] = agent_code_lines
self.graph_code_lines: List[str] = graph_code_lines
+ self.module_code_lines: List[str] = module_code_lines
self._iter_code_lines: List[str] | None = None
self._iter_idx: int | None = None
self._iter_num_code_lines: int | None = None
def __iter__(self) -> Code:
- self._iter_code_lines = self.agent_code_lines + ["\n"] + self.graph_code_lines
+ self._iter_code_lines = (
+ self.agent_code_lines
+ + ["\n"]
+ + self.graph_code_lines
+ + ["\n"]
+ + self.module_code_lines
+ )
self._iter_num_code_lines = len(self._iter_code_lines)
self._iter_idx = -1
return self
diff --git a/aasm/generating/python_code.py b/aasm/generating/python_code.py
index 8a94c43..e722439 100644
--- a/aasm/generating/python_code.py
+++ b/aasm/generating/python_code.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import List
+from typing import List, Set
class PythonCode:
@@ -8,6 +8,7 @@ def __init__(self, indent_size: int):
self.indent_size = indent_size
self.indent: int = 0
self.code_lines: List[str] = []
+ self.required_imports: Set[str] = set()
def indent_left(self) -> None:
self.indent -= self.indent_size
@@ -15,8 +16,15 @@ def indent_left(self) -> None:
def indent_right(self) -> None:
self.indent += self.indent_size
- def add_line(self, line: str) -> None:
- self.code_lines.append(self.indent * " " + line + "\n")
+ def add_line(
+ self, line: str, required_imports: Set[str] | None = None, add_newline=True
+ ) -> None:
+ if required_imports is not None:
+ self.required_imports.update(required_imports)
+ if add_newline:
+ self.code_lines.append(self.indent * " " + line + "\n")
+ else:
+ self.code_lines.append(self.indent * " " + line)
def add_newline(self) -> None:
self.add_line("")
@@ -25,18 +33,9 @@ def add_newlines(self, count: int) -> None:
for _ in range(count):
self.add_newline()
- def add_template(self, template: str, **kwargs: Any) -> None:
- lines = template.render(kwargs).splitlines()
- current_indent = 0
- for line in lines:
- space_count = len(line) - len(
- line.lstrip(" ")
- ) # templates should have 4 space indents per pep8
- indent_count = space_count // 4
- if indent_count < current_indent:
- for _ in range(current_indent - indent_count):
- self.indent_left()
- elif indent_count > current_indent:
- for _ in range(indent_count - current_indent):
- self.indent_right()
- self.add_line(line.strip())
+ def add_required_imports(self) -> None:
+ lines: List[str] = []
+ for required_import in self.required_imports:
+ lines.append(f"import {required_import}\n")
+ lines.sort()
+ self.code_lines = lines + self.code_lines
diff --git a/aasm/generating/python_graph.py b/aasm/generating/python_graph.py
index 63fc8f3..448c4ea 100644
--- a/aasm/generating/python_graph.py
+++ b/aasm/generating/python_graph.py
@@ -13,6 +13,8 @@
ConnectionDistUniformAmount,
MatrixGraph,
StatisticalGraph,
+ BarabasiGraph,
+ InhomogenousRandomGraph,
)
if TYPE_CHECKING:
@@ -25,8 +27,48 @@ def __init__(self, indent_size: int, graph: Graph | None):
if graph:
self.add_required_imports()
self.add_newlines(2)
+ self.add_class_definitions()
+ self.add_newlines(2)
self.generate_graph(graph)
+ def add_class_definitions(self):
+ self.add_line("class Agent:")
+ self.indent_right()
+ self.add_line(
+ "def __init__(self, jid: str, type: str, connections = None, sim_id: str = "
+ "):"
+ )
+ self.indent_right()
+ self.add_line("self.jid = jid")
+ self.add_line("self.type = type")
+ self.add_line("self.sim_id = sim_id")
+ self.add_line("if connections is not None:")
+ self.indent_right()
+ self.add_line("self.connections = connections")
+ self.indent_left()
+ self.add_line("else:")
+ self.indent_right()
+ self.add_line("self.connections = []")
+ self.indent_left()
+ self.indent_left()
+ self.add_newline()
+ self.add_line("def add_connection(self, connection: str):")
+ self.indent_right()
+ self.add_line("self.connections.append(connection)")
+ self.indent_left()
+ self.add_line("def to_dict(self):")
+ self.indent_right()
+ self.add_line("return {")
+ self.indent_right()
+ self.add_line("'jid': self.jid,")
+ self.add_line("'type': self.type,")
+ self.add_line("'connections': self.connections")
+ self.add_line("'sim_id': self.sim_id")
+ self.add_line("}")
+ self.indent_left()
+ self.indent_left()
+ self.indent_left()
+
def add_required_imports(self) -> None:
self.add_line("import random")
self.add_line("import uuid")
@@ -40,18 +82,23 @@ def generate_graph(self, graph: Graph) -> None:
case MatrixGraph():
self.add_matrix_graph(graph)
+ case BarabasiGraph():
+ self.add_barabasi_graph(graph)
+
+ case InhomogenousRandomGraph():
+ self.add_irg_graph(graph)
+
case _:
raise Exception(f"Unknown graph type: {graph.print()}")
def add_statistical_graph(self, graph: StatisticalGraph) -> None:
- self.add_line("def generate_graph_structure(domain):")
+ self.add_line('def generate_graph_structure(domain, sim_id=""):')
self.indent_right()
if not graph.agents:
self.add_line("return []")
self.indent_left()
return
-
num_agents_expr: List[str] = []
for agent in graph.agents.values():
match agent.amount:
@@ -71,7 +118,15 @@ def add_statistical_graph(self, graph: StatisticalGraph) -> None:
num_agents_expr.append(f"_num_{agent.name}")
self.add_line(f'num_agents = {" + ".join(num_agents_expr)}')
+
+ self.add_line('if sim_id == "":')
+ self.indent_right()
self.add_line("random_id = str(uuid.uuid4())[:5]")
+ self.indent_left()
+ self.add_line("else:")
+ self.indent_right()
+ self.add_line("random_id = sim_id")
+ self.indent_left()
self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(num_agents)]')
self.add_line("agents = []")
self.add_line("next_agent_idx = 0")
@@ -114,6 +169,7 @@ def add_statistical_graph(self, graph: StatisticalGraph) -> None:
self.add_line(
'"connections": random.sample([other_jid for other_jid in jids if other_jid != jid], num_connections),'
)
+ self.add_line('"sim_id": random_id,')
self.indent_left()
self.add_line("})")
self.add_line("next_agent_idx += 1")
@@ -123,7 +179,7 @@ def add_statistical_graph(self, graph: StatisticalGraph) -> None:
self.indent_left()
def add_matrix_graph(self, graph: MatrixGraph):
- self.add_line("def generate_graph_structure(domain):")
+ self.add_line('def generate_graph_structure(domain, sim_id=""):')
self.indent_right()
if not graph.agents:
self.add_line("return []")
@@ -135,15 +191,26 @@ def add_matrix_graph(self, graph: MatrixGraph):
self.add_line(f"scale_factor = 1")
self.add_line(f"n_agent_types = {len(graph.agents)}")
self.add_line("graph_size = scale_factor * n_agent_types")
+ self.add_line('if sim_id == "":')
+ self.indent_right()
self.add_line("random_id = str(uuid.uuid4())[:5]")
+ self.indent_left()
+ self.add_line("else:")
+ self.indent_right()
+ self.add_line("random_id = sim_id")
+ self.indent_left()
self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(graph_size)]')
+ self.add_line("agent_types = []")
self.add_line("agents = []")
self.add_line("indx_sets = []")
for agent in graph.agents:
+ self.add_line(f"agent_types.append('{agent.name}')")
adj_indx = [True if x == 1 else False for x in agent.adj_row.row]
adj_indx = list(compress(range(len(adj_indx)), adj_indx))
self.add_line(f"indx_sets.append({adj_indx})")
+ self.add_line("agent_types = agent_types * scale_factor")
+
self.add_line("for base_agent_index in range(n_agent_types):")
self.indent_right()
self.add_line("indices = indx_sets[base_agent_index]")
@@ -155,7 +222,9 @@ def add_matrix_graph(self, graph: MatrixGraph):
self.indent_left()
self.add_line("for shift in range(scale_factor):")
self.indent_right()
- self.add_line("jid = jids[base_agent_index + shift * n_agent_types]")
+ self.add_line("agent_idx = base_agent_index + shift * n_agent_types")
+ self.add_line("jid = jids[agent_idx]")
+ self.add_line("agent_type = agent_types[agent_idx]")
self.add_line("connections = []")
self.add_line("for i in indices:")
self.indent_right()
@@ -166,8 +235,9 @@ def add_matrix_graph(self, graph: MatrixGraph):
self.add_line("agents.append({")
self.indent_right()
self.add_line('"jid": jid,')
- self.add_line(f'"type": "{agent.name}",')
+ self.add_line(f'"type": agent_type,')
self.add_line('"connections": connections,')
+ self.add_line('"sim_id": random_id,')
self.indent_left()
self.add_line("})")
self.indent_left()
@@ -175,3 +245,164 @@ def add_matrix_graph(self, graph: MatrixGraph):
self.indent_left()
self.add_line("return agents")
self.indent_left()
+
+ def add_barabasi_graph(self, graph: BarabasiGraph):
+ # generate jid list using draw by draw Barabasi-Albert algorithm for the provided graph with its m0 and m
+ self.add_line('def generate_graph_structure(domain, sim_id=""):')
+ self.indent_right()
+ if not graph.agents:
+ self.add_line("return []")
+ self.indent_left()
+ return
+
+ self.add_line(f"agent_types = []")
+
+ num_agents_expr: List[str] = []
+ for agent in graph.agents.values():
+ match agent.amount:
+ case AgentConstantAmount():
+ self.add_line(f"_num_{agent.name} = {agent.amount.value}")
+
+ case AgentPercentAmount():
+ self.add_line(
+ f"_num_{agent.name} = round({agent.amount.value} / 100 * {graph.size})"
+ )
+
+ case _:
+ raise Exception(
+ f"Unknown agent amount type: {agent.amount.print()}"
+ )
+
+ num_agents_expr.append(f"_num_{agent.name}")
+ self.add_line(f"tmp = ['{agent.name}'] * _num_{agent.name}")
+ self.add_line("agent_types.extend(tmp)")
+
+ self.add_line(f'num_agents = {" + ".join(num_agents_expr)}')
+ self.add_line("random.shuffle(agent_types)")
+
+ self.add_line('if sim_id == "":')
+ self.indent_right()
+ self.add_line("random_id = str(uuid.uuid4())[:5]")
+ self.indent_left()
+ self.add_line("else:")
+ self.indent_right()
+ self.add_line("random_id = sim_id")
+ self.indent_left()
+ if graph.m_params is not None:
+ self.add_line(f"m0 = {graph.m_params.m0}")
+ self.add_line(f"m = {graph.m_params.m_inc}")
+ self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(num_agents)]')
+ # self.add_line("agents = []")
+ self.add_line("connection_lists = []")
+ self.add_line("next_agent_idx = 0")
+ # begin by creating the initial complete graph
+ self.add_line("init_jids = random.choices(jids, k=m0)")
+ self.add_line("for jid in init_jids:")
+ self.indent_right()
+ self.add_line("copy = init_jids.copy()")
+ self.add_line("copy.remove(jid)")
+ self.add_line("connection_lists.append(copy)")
+ self.indent_left()
+ self.add_line("next_agent_idx += m0")
+ self.add_line("for i in range(m0, num_agents):")
+ self.indent_right()
+ self.add_line("to_connect = []")
+ self.add_line("ids = list(range(next_agent_idx))")
+ self.add_line("while len(to_connect) < m:")
+ self.indent_right()
+ self.add_line("weights = [len(connection_lists[i]) for i in ids]")
+ self.add_line("to_connect.append(random.choices(ids, weights=weights)[0])")
+ self.add_line("ids.remove(to_connect[-1])")
+ self.indent_left()
+ self.add_line("to_connect_jids = [jids[i] for i in to_connect]")
+ self.add_line("connection_lists.append(to_connect_jids)")
+ self.add_line("for id in to_connect:")
+ self.indent_right()
+ # two sided connections
+ self.add_line("connection_lists[id].append(jids[next_agent_idx])")
+ self.indent_left()
+ self.add_line("next_agent_idx += 1")
+ self.indent_left()
+ self.add_line("agents = []")
+ self.add_line("for i in range(num_agents):")
+ self.indent_right()
+ self.add_line("agents.append({")
+ self.indent_right()
+ self.add_line('"jid": jids[i],')
+ self.add_line('"type": agent_types[i],')
+ self.add_line('"connections": connection_lists[i],')
+ self.add_line('"sim_id": random_id,')
+ self.indent_left()
+ self.add_line("})")
+ self.indent_left()
+ self.add_line("return agents")
+ self.indent_left()
+
+ def add_irg_graph(self, graph: InhomogenousRandomGraph):
+ self.add_line('def generate_graph_structure(domain, sim_id=""):')
+ self.indent_right()
+ if not graph.agents:
+ self.add_line("return []")
+ self.indent_left()
+ return
+ self.add_line("agent_types = []")
+ self.add_line("probo_matrix = {}")
+ num_agents_expr: List[str] = []
+ for agent in graph.agents.values():
+ self.add_line(f"probo_matrix['{agent.name}'] = []")
+ for probo in agent.amounts:
+ self.add_line(f"probo_matrix['{agent.name}'].append({probo.value})")
+ match agent.amount:
+ case AgentConstantAmount():
+ self.add_line(f"_num_{agent.name} = {agent.amount.value}")
+
+ case AgentPercentAmount():
+ self.add_line(
+ f"_num_{agent.name} = round({agent.amount.value} / 100 * {graph.size})"
+ )
+
+ case _:
+ raise Exception(
+ f"Unknown agent amount type: {agent.amount.print()}"
+ )
+
+ num_agents_expr.append(f"_num_{agent.name}")
+ self.add_line(f"tmp = ['{agent.name}'] * _num_{agent.name}")
+ self.add_line("agent_types.extend(tmp)")
+
+ self.add_line(f'num_agents = {" + ".join(num_agents_expr)}')
+
+ self.add_line('if sim_id == "":')
+ self.indent_right()
+ self.add_line("random_id = str(uuid.uuid4())[:5]")
+ self.indent_left()
+ self.add_line("else:")
+ self.indent_right()
+ self.add_line("random_id = sim_id")
+ self.indent_left()
+ self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(num_agents)]')
+ self.add_line(f"order = {graph.order}")
+ self.add_line("agents = []")
+ self.add_line("for i in range(num_agents):")
+ self.indent_right()
+ self.add_line(
+ "agents.append(Agent(jids[i], agent_types[i], sim_id = random_id))"
+ )
+ self.indent_left()
+ self.add_line("for agent in agents:")
+ self.indent_right()
+ self.add_line("for agent2 in agents:")
+ self.indent_right()
+ self.add_line("if agent != agent2:")
+ self.indent_right()
+ self.add_line("conn_index = order.index(agent2.type)")
+ self.add_line("probo = probo_matrix[agent.type][conn_index]")
+ self.add_line("if random.random() * 100 < probo:")
+ self.indent_right()
+ self.add_line("agent.add_connection(agent2.jid)")
+ self.indent_left()
+ self.indent_left()
+ self.indent_left()
+ self.indent_left()
+ self.add_line("return [agent.to_dict() for agent in agents]")
+ self.indent_left()
diff --git a/aasm/generating/python_module.py b/aasm/generating/python_module.py
new file mode 100644
index 0000000..b3a004c
--- /dev/null
+++ b/aasm/generating/python_module.py
@@ -0,0 +1,59 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+
+from aasm.generating.python_code import PythonCode
+from aasm.modules.module import Module
+
+
+def get_modules_for_target(
+ module_code_lines: List[List[str]], target: str
+) -> List[Module]:
+ """
+ Get all modules from `module_code_lines` that target the given `target`.
+
+ :param module_code_lines: the code lines of all modules
+ :param target: the target to filter by
+
+ :return: the modules that target the given `target`
+ """
+ target_modules = []
+ for module_lines in module_code_lines:
+ module = Module(module_lines)
+ if module.does_target(target):
+ target_modules.append(module)
+ return target_modules
+
+
+class PythonModule(PythonCode):
+ def __init__(self, indent_size, module: Module):
+ super().__init__(indent_size)
+ self.module: Module = module
+ self.target: str = "spade"
+ self.add_newlines(2)
+ self.create_module_header()
+ self.add_newline()
+ self.create_module_imports()
+ self.add_newline()
+ self.create_module_implementations()
+
+ def create_module_header(self) -> None:
+ self.add_line(f"# Module: {self.module.name}")
+ for line in self.module.description:
+ self.add_line(f"# {line}", add_newline=False)
+
+ def create_module_imports(self):
+ try:
+ preamble = self.module.preambles[self.target]
+ for line in preamble:
+ self.add_line(line)
+ except KeyError:
+ return
+
+ def create_module_implementations(self):
+ for impl in self.module.impls:
+ if impl[0] == self.target:
+ self.add_line(f"def {impl[1]}():")
+ for line in self.module.impls[impl]:
+ self.add_line(line)
+ self.add_newline()
diff --git a/aasm/generating/python_spade.py b/aasm/generating/python_spade.py
index 65dff5e..990ca8f 100644
--- a/aasm/generating/python_spade.py
+++ b/aasm/generating/python_spade.py
@@ -5,6 +5,8 @@
from aasm.generating.code import Code
from aasm.generating.python_code import PythonCode
from aasm.generating.python_graph import PythonGraph
+from aasm.generating.python_module import PythonModule
+
from aasm.intermediate.action import SendMessageAction
from aasm.intermediate.argument import (
AgentParam,
@@ -19,7 +21,11 @@
)
from aasm.intermediate.behaviour import MessageReceivedBehaviour
from aasm.intermediate.block import Block
-from aasm.intermediate.declaration import ConnectionDeclaration, FloatDeclaration
+from aasm.intermediate.declaration import (
+ ConnectionDeclaration,
+ FloatDeclaration,
+ ModuleVariableDeclaration,
+)
from aasm.intermediate.instruction import (
Add,
AddElement,
@@ -42,6 +48,12 @@
ListRead,
ListWrite,
Logarithm,
+ Logs,
+ LogsCritical,
+ LogsDebug,
+ LogsError,
+ LogsInfo,
+ LogsWarning,
MathOperation,
Modulo,
Multiply,
@@ -63,6 +75,7 @@
WhileLessThan,
WhileLessThanOrEqual,
WhileNotEqual,
+ ModuleInstruction,
)
from aasm.parsing.parse import parse_lines
@@ -72,10 +85,14 @@
from aasm.intermediate.argument import Argument
from aasm.intermediate.behaviour import Behaviour
from aasm.intermediate.message import Message as IntermediateMessage
+ from aasm.modules.module import Module
def get_spade_code(
- aasm_lines: List[str], indent_size: int = 4, debug: bool = False
+ aasm_lines: List[str],
+ indent_size: int = 4,
+ debug: bool = False,
+ modules: None | List[Module] = None,
) -> Code:
"""Generates SPADE code in Python from `aasm_lines`.
@@ -90,6 +107,9 @@ def get_spade_code(
debug: bool, optional
Print the translator debug information to the standard output
+ modules: List[Module], optional
+ A list of Agents Assembly Modules compatible with SPADE platform
+
Returns
-------
SPADE code along with the algorithm for the graph generation
@@ -99,34 +119,52 @@ def get_spade_code(
PanicException
If an error is detected while parsing the `aasm_lines`.
"""
- parsed = parse_lines(aasm_lines, debug)
+ if modules is None:
+ modules = []
+ parsed = parse_lines(aasm_lines, debug, modules)
+ module_code_lines = []
+ for module in modules:
+ module_code_lines += PythonModule(indent_size, module).code_lines
return Code(
- PythonSpadeCode(indent_size, parsed.agents).code_lines,
+ PythonSpadeCode(indent_size, parsed.agents, modules).code_lines,
PythonGraph(indent_size, parsed.graph).code_lines,
+ module_code_lines,
)
class PythonSpadeCode(PythonCode):
- def __init__(self, indent_size: int, agents: List[Agent]):
+ def __init__(self, indent_size: int, agents: List[Agent], modules: List[Module]):
super().__init__(indent_size)
- if agents:
+ self.modules = modules
+ self.target = "spade"
+ self.filter_modules()
+ self.add_module_imports()
+ for agent in agents:
+ self.add_newlines(2)
+ self.generate_agent(agent)
self.add_required_imports()
- for agent in agents:
- self.add_newlines(2)
- self.generate_agent(agent)
-
- def add_required_imports(self) -> None:
- self.add_line("import copy")
- self.add_line("import datetime")
- self.add_line("import random")
- self.add_line("import httpx")
- self.add_line("import numpy")
- self.add_line("import orjson")
- self.add_line("import spade")
- self.add_line("import sys")
+
+ def filter_modules(self):
+ self.modules = [
+ target_mod
+ for target_mod in self.modules
+ for target in target_mod.targets
+ if target.name == self.target
+ ]
+
+ def add_module_imports(self):
+ for module in self.modules:
+ self.add_line(f"import {module.name}")
+
+ # spade_modules = []
+ # for target_mod in self.modules:
+ # for target in target_mod.targets:
+ # if target.name == self.target:
+ # spade_modules.append(target_mod)
+ # self.modules = spade_modules
def generate_agent(self, agent: Agent) -> None:
- self.add_line(f"class {agent.name}(spade.agent.Agent):")
+ self.add_line(f"class {agent.name}(spade.agent.Agent):", {"spade"})
self.indent_right()
self.add_agent_constructor(agent)
@@ -202,14 +240,16 @@ def add_agent_constructor(self, agent: Agent) -> None:
mean = f"self.limit_number({dist_normal_float_param.mean})"
std_dev = f"self.limit_number({dist_normal_float_param.std_dev})"
self.add_line(
- f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.normal({mean}, {std_dev})))'
+ f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.normal({mean}, {std_dev})))',
+ {"numpy"},
)
for dist_exp_float_param in agent.dist_exp_floats.values():
name = dist_exp_float_param.name
lambda_ = f"self.limit_number({dist_exp_float_param.lambda_})"
self.add_line(
- f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.exponential(self.limit_number(1 / {lambda_}))))'
+ f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.exponential(self.limit_number(1 / {lambda_}))))',
+ {"numpy"},
)
for dist_uniform_float_param in agent.dist_unifrom_floats.values():
@@ -217,7 +257,8 @@ def add_agent_constructor(self, agent: Agent) -> None:
a = f"self.limit_number({dist_uniform_float_param.a})"
b = f"self.limit_number({dist_uniform_float_param.b})"
self.add_line(
- f'self.{name} = self.limit_number(kwargs.get("{name}", random.uniform({a}, {b})))'
+ f'self.{name} = self.limit_number(kwargs.get("{name}", random.uniform({a}, {b})))',
+ {"random"},
)
for enum_param in agent.enums.values():
@@ -229,7 +270,8 @@ def add_agent_constructor(self, agent: Agent) -> None:
values = f'[{", ".join(value_list)}]'
percentages = f'[{", ".join(percentage_list)}]'
self.add_line(
- f'self.{enum_param.name} = kwargs.get("{enum_param.name}", random.choices({values}, {percentages})[0])'
+ f'self.{enum_param.name} = kwargs.get("{enum_param.name}", random.choices({values}, {percentages})[0])',
+ {"random"},
)
for connection_list_param in agent.connection_lists.values():
@@ -270,17 +312,19 @@ def add_float_utils(self) -> None:
def add_message_utils(self) -> None:
self.add_line("def get_json_from_spade_message(self, msg):")
self.indent_right()
- self.add_line("return orjson.loads(msg.body)")
+ self.add_line("return orjson.loads(msg.body)", {"orjson"})
self.indent_left()
self.add_newline()
self.add_line("def get_spade_message(self, receiver_jid, body):")
self.indent_right()
- self.add_line("msg = spade.message.Message(to=receiver_jid)")
+ self.add_line("msg = spade.message.Message(to=receiver_jid)", {"spade"})
self.add_line('body["sender"] = str(self.jid)')
self.add_line('msg.metadata["type"] = body["type"]')
self.add_line('msg.metadata["performative"] = body["performative"]')
- self.add_line('msg.body = str(orjson.dumps(body), encoding="utf-8")')
+ self.add_line(
+ 'msg.body = str(orjson.dumps(body), encoding="utf-8")', {"orjson"}
+ )
self.add_line("return msg")
self.indent_left()
@@ -293,7 +337,8 @@ def add_agent_setup(self, agent: Agent) -> None:
self.indent_right()
self.add_no_match_template("BackupBehaviour")
self.add_line(
- "self.add_behaviour(self.BackupBehaviour(start_at=datetime.datetime.now() + datetime.timedelta(seconds=self.backup_delay), period=self.backup_period), BackupBehaviour_template)"
+ "self.add_behaviour(self.BackupBehaviour(start_at=datetime.datetime.now() + datetime.timedelta(seconds=self.backup_delay), period=self.backup_period), BackupBehaviour_template)",
+ {"datetime"},
)
self.indent_left()
@@ -306,7 +351,8 @@ def add_agent_setup(self, agent: Agent) -> None:
for one_time_behaviour in agent.one_time_behaviours.values():
self.add_no_match_template(f"{one_time_behaviour.name}")
self.add_line(
- f"self.add_behaviour(self.{one_time_behaviour.name}(start_at=datetime.datetime.now() + datetime.timedelta(seconds={one_time_behaviour.delay})), {one_time_behaviour.name}_template)"
+ f"self.add_behaviour(self.{one_time_behaviour.name}(start_at=datetime.datetime.now() + datetime.timedelta(seconds={one_time_behaviour.delay})), {one_time_behaviour.name}_template)",
+ {"datetime"},
)
for cyclic_behaviour in agent.cyclic_behaviours.values():
@@ -317,7 +363,8 @@ def add_agent_setup(self, agent: Agent) -> None:
for message_received_behaviour in agent.message_received_behaviours.values():
self.add_line(
- f"{message_received_behaviour.name}_template = spade.template.Template()"
+ f"{message_received_behaviour.name}_template = spade.template.Template()",
+ {"spade"},
)
self.add_line(
f'{message_received_behaviour.name}_template.set_metadata("type", "{message_received_behaviour.received_message.type}")'
@@ -336,18 +383,22 @@ def add_agent_setup(self, agent: Agent) -> None:
self.indent_left()
def add_no_match_template(self, behaviour_name: str) -> None:
- self.add_line(f"{behaviour_name}_template = spade.template.Template()")
+ self.add_line(
+ f"{behaviour_name}_template = spade.template.Template()", {"spade"}
+ )
self.add_line(
f'{behaviour_name}_template.set_metadata("reserved", "no_message_match")'
)
def add_backup_behaviour(self, agent: Agent) -> None:
- self.add_line("class BackupBehaviour(spade.behaviour.PeriodicBehaviour):")
+ self.add_line(
+ "class BackupBehaviour(spade.behaviour.PeriodicBehaviour):", {"spade"}
+ )
self.indent_right()
self.add_line("def __init__(self, start_at, period):")
self.indent_right()
self.add_line("super().__init__(start_at=start_at, period=period)")
- self.add_line("self.http_client = httpx.AsyncClient(timeout=period)")
+ self.add_line("self.http_client = httpx.AsyncClient(timeout=period)", {"httpx"})
self.indent_left()
self.add_newline()
@@ -356,7 +407,8 @@ def add_backup_behaviour(self, agent: Agent) -> None:
self.add_line("data = {")
self.indent_right()
self.add_line(
- '"__timestamp__": int(datetime.datetime.timestamp(datetime.datetime.utcnow())),'
+ '"__timestamp__": int(datetime.datetime.timestamp(datetime.datetime.utcnow())),',
+ {"datetime"},
)
self.add_line('"jid": str(self.agent.jid),')
self.add_line(f'"type": "{agent.name}",')
@@ -416,7 +468,8 @@ def add_backup_behaviour(self, agent: Agent) -> None:
self.add_line("try:")
self.indent_right()
self.add_line(
- 'await self.http_client.post(self.agent.backup_url, headers={"Content-Type": "application/json"}, data=orjson.dumps(data))'
+ 'await self.http_client.post(self.agent.backup_url, headers={"Content-Type": "application/json"}, data=orjson.dumps(data))',
+ {"orjson"},
)
self.indent_left()
self.add_line("except Exception as e:")
@@ -452,7 +505,7 @@ def add_backup_behaviour(self, agent: Agent) -> None:
self.indent_left()
def add_agent_behaviour(self, behaviour: Behaviour, behaviour_type: str) -> None:
- self.add_line(f"class {behaviour.name}({behaviour_type}):")
+ self.add_line(f"class {behaviour.name}({behaviour_type}):", {"spade"})
self.indent_right()
for action in behaviour.actions.values():
@@ -543,7 +596,10 @@ def add_send_message(self, message: IntermediateMessage) -> None:
def parse_arg(self, arg: Argument) -> str:
match arg.type_in_op:
case AgentParam():
- return f"self.agent.{arg.expr}"
+ if arg.expr == "self":
+ return "self.agent.jid"
+ else:
+ return f"self.agent.{arg.expr}"
case EnumValue():
return f'"{arg.expr}"'
@@ -585,6 +641,12 @@ def add_block(self, block: Block) -> None:
value = f"{self.parse_arg(statement.value)}"
self.add_line(f"{statement.name} = {value}")
+ case ModuleVariableDeclaration():
+ self.add_line("")
+ self.add_line("# module variable declaration")
+ value = f"{self.parse_arg(statement.value)}"
+ self.add_line(f"{statement.name} = {value}")
+
case Subset():
self.add_line("")
self.add_line("# subset")
@@ -600,7 +662,8 @@ def add_block(self, block: Block) -> None:
self.add_line("return")
self.indent_left()
self.add_line(
- f"{dst_list} = [copy.deepcopy(elem) for elem in random.sample({src_list}, min({num}, {src_list_len}))]"
+ f"{dst_list} = [copy.deepcopy(elem) for elem in random.sample({src_list}, min({num}, {src_list_len}))]",
+ {"copy", "random"},
)
case Clear():
@@ -657,7 +720,8 @@ def add_block(self, block: Block) -> None:
self.add_line("return")
self.indent_left()
self.add_line(
- f'{msg} = copy.deepcopy(random.choice(list(filter(lambda msg: msg["type"] == {msg}["type"] and msg["performative"] == {msg}["performative"], {msg_list}))))'
+ f'{msg} = copy.deepcopy(random.choice(list(filter(lambda msg: msg["type"] == {msg}["type"] and msg["performative"] == {msg}["performative"], {msg_list}))))',
+ {"copy", "random"},
)
case Set() if isinstance(statement.value.type_in_op, Float):
@@ -689,7 +753,8 @@ def add_block(self, block: Block) -> None:
a = f"self.agent.limit_number({self.parse_arg(statement.a)})"
b = f"self.agent.limit_number({self.parse_arg(statement.b)})"
self.add_line(
- f"{dst} = self.agent.limit_number(random.uniform({a}, {b}))"
+ f"{dst} = self.agent.limit_number(random.uniform({a}, {b}))",
+ {"random"},
)
case NormalDist():
@@ -708,7 +773,8 @@ def add_block(self, block: Block) -> None:
self.add_line("return")
self.indent_left()
self.add_line(
- f"{dst} = self.agent.limit_number(numpy.random.normal({mean}, {std_dev}))"
+ f"{dst} = self.agent.limit_number(numpy.random.normal({mean}, {std_dev}))",
+ {"numpy"},
)
case ExpDist():
@@ -726,7 +792,8 @@ def add_block(self, block: Block) -> None:
self.add_line("return")
self.indent_left()
self.add_line(
- f"{dst} = self.agent.limit_number(numpy.random.exponential(self.agent.limit_number(1 / {lambda_})))"
+ f"{dst} = self.agent.limit_number(numpy.random.exponential(self.agent.limit_number(1 / {lambda_})))",
+ {"numpy"},
)
case Comparaison():
@@ -926,7 +993,7 @@ def add_block(self, block: Block) -> None:
)
self.add_line("return")
self.indent_left()
- self.add_line(f"random.shuffle({list_})")
+ self.add_line(f"random.shuffle({list_})", {"random"})
self.add_line(
f"{list_} = {list_}[:int(self.agent.limit_number({list_len} - {num}))]"
)
@@ -991,14 +1058,16 @@ def add_block(self, block: Block) -> None:
self.add_line("")
self.add_line("# sin")
self.add_line(
- f"{dst} = self.agent.limit_number(numpy.sin(numpy.deg2rad({degree})))"
+ f"{dst} = self.agent.limit_number(numpy.sin(numpy.deg2rad({degree})))",
+ {"numpy"},
)
case Cos():
self.add_line("")
self.add_line("# cos")
self.add_line(
- f"{dst} = self.agent.limit_number(numpy.cos(numpy.deg2rad({degree})))"
+ f"{dst} = self.agent.limit_number(numpy.cos(numpy.deg2rad({degree})))",
+ {"numpy"},
)
case _:
@@ -1016,7 +1085,8 @@ def add_block(self, block: Block) -> None:
self.add_line("")
self.add_line("# power")
self.add_line(
- f"{dst} = self.agent.limit_number(numpy.power({base}, {num}))"
+ f"{dst} = self.agent.limit_number(numpy.power({base}, {num}))",
+ {"numpy"},
)
case Logarithm():
@@ -1034,7 +1104,8 @@ def add_block(self, block: Block) -> None:
numerator = f"self.agent.limit_number(numpy.log({num}))"
denominator = f"self.agent.limit_number(numpy.log({base}))"
self.add_line(
- f"{dst} = self.agent.limit_number({numerator} / {denominator})"
+ f"{dst} = self.agent.limit_number({numerator} / {denominator})",
+ {"numpy"},
)
case _:
@@ -1042,5 +1113,75 @@ def add_block(self, block: Block) -> None:
f"Unknown exponentiation operation statement: {statement.print()}"
)
+ case Logs():
+ logger_msg = '{ "jid": self.agent.jid, "agent": type(self.agent).__name__, "behaviour": type(self).__name__, "action": inspect.stack()[0].function'
+ for arg in statement.args:
+ name = arg.expr + "__" + arg.explain_type_in_op()
+ value = self.parse_arg(arg)
+ logger_msg += f', "{name}": {value}'
+ logger_msg += " }"
+
+ match statement:
+ case LogsDebug():
+ self.add_line("")
+ self.add_line("# logs (debug)")
+ self.add_line(
+ f"if self.agent.logger: self.agent.logger.debug({logger_msg})",
+ {"inspect"},
+ )
+
+ case LogsInfo():
+ self.add_line("")
+ self.add_line("# logs (info)")
+ self.add_line(
+ f"if self.agent.logger: self.agent.logger.info({logger_msg})",
+ {"inspect"},
+ )
+
+ case LogsWarning():
+ self.add_line("")
+ self.add_line("# logs (warning)")
+ self.add_line(
+ f"if self.agent.logger: self.agent.logger.warning({logger_msg})",
+ {"inspect"},
+ )
+
+ case LogsError():
+ self.add_line("")
+ self.add_line("# logs (error)")
+ self.add_line(
+ f"if self.agent.logger: self.agent.logger.error({logger_msg})",
+ {"inspect"},
+ )
+
+ case LogsCritical():
+ self.add_line("")
+ self.add_line("# logs (critical)")
+ self.add_line(
+ f"if self.agent.logger: self.agent.logger.critical({logger_msg})",
+ {"inspect"},
+ )
+
+ case _:
+ raise Exception(
+ f"Unknown logs statement: {statement.print()}"
+ )
+ case ModuleInstruction():
+ self.add_line("")
+ self.add_line(f"# module instruction {statement.op_code}")
+ arguments_string = ", ".join(
+ [self.parse_arg(arg) for arg in statement.args]
+ )
+ if statement.is_block:
+ self.add_line(
+ # NOTE: there are no while statements in the modules, all blocks are ifs
+ f"if {statement.module}.{statement.op_code}({arguments_string}):"
+ )
+ else:
+ self.add_line(
+ f"{statement.module}.{statement.op_code}({arguments_string})"
+ )
+
case _:
+ print(statement)
raise Exception(f"Unknown statement: {statement.print()}")
diff --git a/aasm/intermediate/action.py b/aasm/intermediate/action.py
index ace5069..617e3b8 100644
--- a/aasm/intermediate/action.py
+++ b/aasm/intermediate/action.py
@@ -37,12 +37,21 @@ def is_declared_float(self, name: str) -> bool:
def is_declared_connection(self, name: str) -> bool:
return self.current_block.is_declared_connection(name)
+ def is_declared_module_variable(self, name: str) -> bool:
+ return self.current_block.is_declared_module_variable(name)
+
+ def get_module_variable_type(self, name: str) -> str:
+ return self.current_block.get_module_variable_type(name)
+
def add_float_declaration(self, declaration: Declaration) -> None:
self.current_block.add_float_declaration(declaration)
def add_connection_declaration(self, declaration: Declaration) -> None:
self.current_block.add_connection_declaration(declaration)
+ def add_module_variable_declaration(self, declaration: Declaration) -> None:
+ self.current_block.add_module_variable_declaration(declaration)
+
def add_instruction(self, instruction: Instruction) -> None:
self.current_block.add_statement(instruction)
diff --git a/aasm/intermediate/agent.py b/aasm/intermediate/agent.py
index a3d79d9..5d80b37 100644
--- a/aasm/intermediate/agent.py
+++ b/aasm/intermediate/agent.py
@@ -100,7 +100,17 @@ def print(self) -> None:
print(f"FloatListParam {self.name} = []")
+class ModuleVariableParam:
+ def __init__(self, name: str, type: str):
+ self.name: str = name
+ self.type: str = type
+
+ def print(self) -> None:
+ print(f"ModuleVariableParam {self.name}: {self.type}")
+
+
class Agent:
+ RESERVED_CONNECTION_PARAMS = ["self"]
RESERVED_CONNECTION_LIST_PARAMS = ["connections"]
RESERVED_FLOAT_PARAMS = ["connCount", "msgRCount", "msgSCount"]
@@ -114,12 +124,16 @@ def __init__(self, name: str):
self.connection_lists: Dict[str, ConnectionListParam] = {}
self.message_lists: Dict[str, MessageListParam] = {}
self.float_lists: Dict[str, FloatListParam] = {}
+ self.module_variables: Dict[str, ModuleVariableParam] = {}
self.setup_behaviours: Dict[str, SetupBehaviour] = {}
self.one_time_behaviours: Dict[str, OneTimeBehaviour] = {}
self.cyclic_behaviours: Dict[str, CyclicBehaviour] = {}
self.message_received_behaviours: Dict[str, MessageReceivedBehaviour] = {}
self._last_modified_behaviour: Behaviour | None = None
+ def get_module_variable_type(self, name):
+ return self.module_variables[name].type
+
@property
def last_behaviour(self) -> Behaviour:
if self._last_modified_behaviour is None:
@@ -129,6 +143,7 @@ def last_behaviour(self) -> Behaviour:
@property
def param_names(self) -> List[str]:
return [
+ *Agent.RESERVED_CONNECTION_PARAMS,
*Agent.RESERVED_CONNECTION_LIST_PARAMS,
*Agent.RESERVED_FLOAT_PARAMS,
*list(self.init_floats),
@@ -139,6 +154,7 @@ def param_names(self) -> List[str]:
*list(self.connection_lists),
*list(self.message_lists),
*list(self.float_lists),
+ *list(self.module_variables),
]
@property
@@ -183,6 +199,9 @@ def add_message_list(self, list_param: MessageListParam) -> None:
def add_float_list(self, list_param: FloatListParam) -> None:
self.float_lists[list_param.name] = list_param
+ def add_module_variable(self, variable_param: ModuleVariableParam) -> None:
+ self.module_variables[variable_param.name] = variable_param
+
def add_setup_behaviour(self, behaviour: SetupBehaviour) -> None:
self.setup_behaviours[behaviour.name] = behaviour
self._last_modified_behaviour = behaviour
@@ -239,6 +258,8 @@ def print(self) -> None:
message_list_param.print()
for float_list_param in self.float_lists.values():
float_list_param.print()
+ for module_variable in self.module_variables.values():
+ module_variable.print()
for setup_behaviour in self.setup_behaviours.values():
setup_behaviour.print()
for one_time_behaviour in self.one_time_behaviours.values():
diff --git a/aasm/intermediate/argument.py b/aasm/intermediate/argument.py
index 8a225ee..b2fa2db 100644
--- a/aasm/intermediate/argument.py
+++ b/aasm/intermediate/argument.py
@@ -8,7 +8,7 @@
if TYPE_CHECKING:
from typing import List as TypingList
- from typing import Type
+ from typing import Type, Tuple
from parsing.state import State
@@ -97,14 +97,28 @@ class Literal(ArgumentType):
...
+class ModuleVariable(ArgumentType):
+ ...
+
+
class Argument:
"""Doesn't panic. Use in the action context."""
- def __init__(self, state: State, expr: str):
+ def __init__(self, state: State, expr: str, set_types: bool = True):
self.expr: str = expr
self.types: TypingList[ArgumentType] = []
self.type_in_op: ArgumentType | None = None
- self.set_types(state)
+ if set_types:
+ self.set_types(state)
+
+ def create_all_possible_types(self, state: State) -> TypingList[Argument]:
+ possible_types: TypingList[Argument] = []
+ for type_ in self.types:
+ possible_type = Argument(state, self.expr, set_types=False)
+ possible_type.types = [type_]
+ possible_type.type_in_op = type_
+ possible_types.append(possible_type)
+ return possible_types
def set_types(self, state: State) -> None:
self.check_agent_params(state)
@@ -121,6 +135,9 @@ def check_agent_params(self, state: State) -> None:
elif self.expr in state.last_agent.RESERVED_CONNECTION_LIST_PARAMS:
self.types.append(self.compose(List, ConnectionList, AgentParam, Mutable))
+ elif self.expr in state.last_agent.RESERVED_CONNECTION_PARAMS:
+ self.types.append(self.compose(Connection, AgentParam, Immutable))
+
elif self.expr in state.last_agent.float_param_names:
self.types.append(self.compose(Float, AgentParam, Mutable))
@@ -136,6 +153,11 @@ def check_agent_params(self, state: State) -> None:
elif self.expr in state.last_agent.float_lists:
self.types.append(self.compose(List, FloatList, AgentParam, Mutable))
+ elif self.expr in state.last_agent.module_variables:
+ subtype = state.last_agent.get_module_variable_type(self.expr)
+ new_type = type(subtype, (ModuleVariable,), {})
+ self.types.append(self.compose(new_type, AgentParam, Mutable))
+
for enum_param in state.last_agent.enums.values():
for enum_value in enum_param.enum_values:
if self.expr == enum_value.value:
@@ -150,6 +172,14 @@ def check_action_variables(self, state: State) -> None:
elif state.last_action.is_declared_connection(self.expr):
self.types.append(self.compose(Connection, Declared, Mutable))
+ elif state.last_action.is_declared_module_variable(self.expr):
+ try:
+ subtype = state.last_action.get_module_variable_type(self.expr)
+ new_type = type(subtype, (ModuleVariable,), {})
+ self.types.append(self.compose(new_type, Declared, Mutable))
+ except ValueError:
+ pass
+
def check_numerical_values(self) -> None:
if is_float(self.expr):
self.types.append(self.compose(Float, Immutable, Literal))
@@ -189,6 +219,19 @@ def check_received_message_params(self, state: State) -> None:
self.compose(Connection, ReceivedMessageParam, Immutable)
)
+ elif (
+ prop in state.last_behaviour.received_message.module_variable_params
+ ):
+ subtype = (
+ state.last_behaviour.received_message.get_module_variable_type(
+ prop
+ )
+ )
+ new_type = type(subtype, (ModuleVariable,), {})
+ self.types.append(
+ self.compose(new_type, ReceivedMessageParam, Immutable)
+ )
+
elif self.expr.lower() == "rcv":
self.types.append(self.compose(Message, ReceivedMessage, Immutable))
@@ -210,6 +253,13 @@ def check_send_message_params(self, state: State) -> None:
self.compose(Connection, SendMessageParam, Mutable)
)
+ elif prop in state.last_action.send_message.module_variable_params:
+ subtype = state.last_action.send_message.get_module_variable_type(
+ prop
+ )
+ new_type = type(subtype, (ModuleVariable,), {})
+ self.types.append(self.compose(new_type, SendMessageParam, Mutable))
+
elif self.expr.lower() == "send":
self.types.append(self.compose(Message, SendMessage, Mutable))
@@ -227,17 +277,46 @@ def has_arg(self, argument_type: ArgumentType, key: str, value: str) -> bool:
...
return False
+ def _check_subclasses(self, type_: ArgumentType, klass: Type[ArgumentType]) -> bool:
+ class_strings = [
+ f"{type_class.__module__}.{type_class.__name__}"
+ for type_class in type_.__class__.mro()
+ ]
+ return any(
+ [
+ f"{klass.__module__}.{klass.__name__}" == class_name
+ for class_name in class_strings
+ ]
+ )
+
def has_type(self, *classes: Type[ArgumentType], **args: str) -> bool:
for type_ in self.types:
- if all([isinstance(type_, klass) for klass in classes]) and all(
+ if all([self._check_subclasses(type_, klass) for klass in classes]) and all(
[self.has_arg(type_, key, value) for key, value in args.items()]
):
return True
return False
+ # def has_type(self, *classes: Type[ArgumentType], **args: str) -> bool:
+ # for type_ in self.types:
+ # all_classes = True
+ # for klass in classes:
+ # if not self._check_subclasses(type_, klass):
+ # all_classes = False
+ # break
+ # if all_classes:
+ # all_args = True
+ # for key, value in args.items():
+ # if not self.has_arg(type_, key, value):
+ # all_args = False
+ # break
+ # if all_args:
+ # return True
+ # return False
+
def set_op_type(self, *classes: Type[ArgumentType], **args: str) -> None:
for type_ in self.types:
- if all([isinstance(type_, klass) for klass in classes]) and all(
+ if all([self._check_subclasses(type_, klass) for klass in classes]) and all(
[self.has_arg(type_, key, value) for key, value in args.items()]
):
self.type_in_op = type_
@@ -347,33 +426,61 @@ def list_n_removal_context(self, rhs: Argument) -> bool:
return False
# SET
- def assignment_context(self, rhs: Argument) -> bool:
- if self.has_type(Enum, Mutable) and rhs.has_type(
- EnumValue, from_enum=self.expr
- ):
- self.set_op_type(Enum, Mutable)
- rhs.set_op_type(EnumValue, from_enum=self.expr)
-
- elif self.has_type(Float, Mutable) and rhs.has_type(Float):
- self.set_op_type(Float, Mutable)
- rhs.set_op_type(Float)
-
- elif self.has_type(Message, Mutable) and rhs.has_type(MessageList):
- self.set_op_type(Message, Mutable)
- rhs.set_op_type(MessageList)
-
- elif self.has_type(SendMessageParam, Mutable) and rhs.has_type(Float):
- self.set_op_type(SendMessageParam, Mutable)
- rhs.set_op_type(Float)
-
- elif self.has_type(Connection, Mutable) and rhs.has_type(Connection):
- self.set_op_type(Connection, Mutable)
- rhs.set_op_type(Connection)
-
- else:
- return False
-
- return True
+ def assignment_context(self, rhs: Argument, state: State) -> bool:
+ basic_assigment_types: TypingList[Tuple[Type, Type]] = [
+ (Enum, EnumValue),
+ (Float, Float),
+ (Message, MessageList),
+ (Connection, Connection),
+ (ConnectionList, ConnectionList),
+ (MessageList, MessageList),
+ (FloatList, FloatList),
+ (SendMessageParam, Float),
+ (SendMessageParam, ModuleVariable),
+ ]
+
+ found_flag = False
+ for assignment_possiblity in basic_assigment_types:
+ if self.has_type(assignment_possiblity[0], Mutable) and rhs.has_type(
+ assignment_possiblity[1]
+ ):
+ self.set_op_type(assignment_possiblity[0], Mutable)
+ rhs.set_op_type(assignment_possiblity[1])
+ found_flag = True
+ break
+
+ if self.has_type(ModuleVariable, Mutable) and rhs.has_type(ModuleVariable):
+ lhs_type_name = self._find_module_variable_type(state)
+ rhs_type_name = rhs._find_module_variable_type(state)
+ if lhs_type_name == "" or rhs_type_name == "":
+ return False
+ lhs_type = self.get_modvar_type(lhs_type_name)
+ rhs_type = rhs.get_modvar_type(rhs_type_name)
+ self.set_op_type(lhs_type, Mutable)
+ rhs.set_op_type(rhs_type)
+ found_flag = True
+
+ return found_flag
+
+ def _find_module_variable_type(self, state: State) -> str:
+ subtype = ""
+ if not self.has_type(ModuleVariable):
+ return subtype
+ elif self.expr in state.last_agent.module_variables:
+ subtype = state.last_agent.get_module_variable_type(self.expr)
+ elif state.last_action.is_declared_module_variable(self.expr):
+ subtype = state.last_action.get_module_variable_type(self.expr)
+ elif self.has_type(ReceivedMessageParam):
+ behav = state.last_behaviour
+ if isinstance(behav, MessageReceivedBehaviour):
+ subtype = behav.received_message.get_module_variable_type(self.expr)
+ elif self.has_type(SendMessageParam):
+ action = state.last_action
+ if isinstance(action, SendMessageAction):
+ # get the expr split after . character
+ split_expr = self.expr.split(".")[-1]
+ subtype = action.send_message.get_module_variable_type(split_expr)
+ return subtype
# SUBS
def list_subset_context(self, from_list: Argument, num: Argument) -> bool:
@@ -506,6 +613,10 @@ def list_write_context(self, idx: Argument, value: Argument) -> bool:
return True
+ def get_modvar_type(self, subtype: str) -> Type:
+ new_type = type(subtype, (ModuleVariable,), {})
+ return new_type
+
def explain(self) -> str:
types = f"{self.expr}: [ "
for argument_type in self.types:
@@ -514,10 +625,11 @@ def explain(self) -> str:
types += " ]"
return types
+ def explain_type_in_op(self) -> str:
+ return type(self.type_in_op).__name__ if self.type_in_op else "UNKNOWN"
+
def print(self) -> None:
print(f"Argument {self.expr}")
- print(
- f"Type in op: {type(self.type_in_op).__name__ if self.type_in_op else 'UNKNOWN'}"
- )
+ print(f"Type in op: {self.explain_type_in_op()}")
for argument_type in self.types:
type(argument_type).__name__
diff --git a/aasm/intermediate/block.py b/aasm/intermediate/block.py
index 60950f3..463b487 100644
--- a/aasm/intermediate/block.py
+++ b/aasm/intermediate/block.py
@@ -1,6 +1,8 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, List
+from typing import TYPE_CHECKING, List, Tuple
+
+from aasm.intermediate.declaration import ModuleVariableDeclaration
if TYPE_CHECKING:
from aasm.intermediate.declaration import Declaration
@@ -12,6 +14,7 @@ def __init__(
self,
float_names: List[str] | None = None,
connection_names: List[str] | None = None,
+ module_variables: List[Tuple[str, str]] | None = None,
):
if float_names:
self.float_names: List[str] = float_names
@@ -23,6 +26,11 @@ def __init__(
else:
self.connection_names: List[str] = []
+ if module_variables:
+ self.module_variables: List[Tuple[str, str]] = module_variables
+ else:
+ self.module_variables: List[Tuple[str, str]] = []
+
def add_float_name(self, name: str) -> None:
self.float_names.append(name)
@@ -35,16 +43,31 @@ def add_connection_name(self, name: str) -> None:
def is_connection_name(self, name: str) -> bool:
return name in self.connection_names
+ def add_module_variable(self, name: str, type: str) -> None:
+ self.module_variables.append((name, type))
+
+ def is_module_variable_name(self, name: str) -> bool:
+ return any(name == mod_var[0] for mod_var in self.module_variables)
+
def get_declared_names(self) -> List[str]:
- return [*self.float_names, *self.connection_names]
+ return [
+ *self.float_names,
+ *self.connection_names,
+ *[modvar[0] for modvar in self.module_variables],
+ ]
def get_copy(self) -> Declarations:
- return Declarations(list(self.float_names), list(self.connection_names))
+ return Declarations(
+ list(self.float_names),
+ list(self.connection_names),
+ list(self.module_variables),
+ )
def print(self) -> None:
print("Declarations")
print(f"float_names = {self.float_names}")
print(f"connection_names = {self.connection_names}")
+ print(f"module_variable_names = {self.module_variables}")
class Block:
@@ -66,6 +89,9 @@ def is_declared_float(self, name: str) -> bool:
def is_declared_connection(self, name: str) -> bool:
return self._declarations.is_connection_name(name)
+ def is_declared_module_variable(self, name: str) -> bool:
+ return self._declarations.is_module_variable_name(name)
+
def add_float_declaration(self, declaration: Declaration) -> None:
self._declarations.add_float_name(declaration.name)
self.statements.append(declaration)
@@ -74,6 +100,17 @@ def add_connection_declaration(self, declaration: Declaration) -> None:
self._declarations.add_connection_name(declaration.name)
self.statements.append(declaration)
+ def add_module_variable_declaration(self, declaration: Declaration) -> None:
+ assert isinstance(declaration, ModuleVariableDeclaration)
+ self._declarations.add_module_variable(declaration.name, declaration.subtype)
+ self.statements.append(declaration)
+
+ def get_module_variable_type(self, name: str) -> str:
+ for modvar in self._declarations.module_variables:
+ if modvar[0] == name:
+ return modvar[1]
+ raise ValueError(f"Module variable {name} not found")
+
def add_statement(self, statement: Instruction | Block) -> None:
self.statements.append(statement)
diff --git a/aasm/intermediate/declaration.py b/aasm/intermediate/declaration.py
index 79462e0..7cca9f0 100644
--- a/aasm/intermediate/declaration.py
+++ b/aasm/intermediate/declaration.py
@@ -32,3 +32,13 @@ def __init__(self, name: Argument, value: Argument):
def print(self) -> None:
print(f"ConnectionDeclaration: {self.name}")
super().print()
+
+
+class ModuleVariableDeclaration(Declaration):
+ def __init__(self, name: Argument, value: Argument, subtype: str):
+ super().__init__(name=name, value=value)
+ self.subtype: str = subtype
+
+ def print(self) -> None:
+ print(f"ModuleVariableDeclaration: {self.name}: {self.subtype}")
+ super().print()
diff --git a/aasm/intermediate/graph.py b/aasm/intermediate/graph.py
index 3cb4c41..b22f594 100644
--- a/aasm/intermediate/graph.py
+++ b/aasm/intermediate/graph.py
@@ -1,6 +1,15 @@
from __future__ import annotations
-from typing import Any, Dict
+from typing import Any, Dict, List, Tuple, Callable, List
+
+
+class MParameters:
+ def __init__(self, a: int, b: int):
+ self.m0 = a
+ self.m_inc = b
+
+ def print(self) -> None:
+ print(f"MParameters values = {self.m0}, {self.m_inc}")
class AgentAmount:
@@ -76,7 +85,7 @@ def __init__(self, row: List[int]):
def print(self) -> None:
print("AdjRow")
- for key, value in self.row.items():
+ for key, value in enumerate(self.row):
print(f"{key} = {value}")
@@ -102,10 +111,36 @@ def print(self) -> None:
self.connections.print()
+class BarabasiAgent:
+ def __init__(self, name: str, amount: AgentAmount):
+ self.name = name
+ self.amount = amount
+
+ def print(self) -> None:
+ print(f"BarabasiAgent name = {self.name}")
+ self.amount.print()
+
+
+class InhomogeneousAgent:
+ def __init__(
+ self,
+ name: str,
+ amount: AgentConstantAmount,
+ conn_amounts: List[ConnectionConstantAmount],
+ ):
+ self.name = name
+ self.amount = amount
+ self.amounts = conn_amounts
+
+ def print(self) -> None:
+ print(f"InhomogeneousAgent name = {self.name}")
+ for amount in self.amounts:
+ amount.print()
+
+
class Graph:
def __init__(self):
self.size = None
- self.scale = None
def set_size(self, size: int) -> None:
self.size = size
@@ -113,11 +148,29 @@ def set_size(self, size: int) -> None:
def is_size_defined(self) -> bool:
return self.size is not None
+ def is_types_amount_defined(self) -> bool:
+ return False
+
+ def is_agent_defined(self, agent_type: str) -> bool:
+ return False
+
+ def set_types_amount(self, types_no: int) -> None:
+ raise NotImplementedError()
+
+ def get_types_amount(self) -> int:
+ raise NotImplementedError()
+
def set_scale(self, scale: int) -> None:
- self.scale = scale
+ raise NotImplementedError()
def is_scale_defined(self) -> bool:
- return self.scale is not None
+ return False
+
+ def set_m(self, m: Tuple[int, int]) -> None:
+ raise NotImplementedError()
+
+ def is_m_defined(self) -> bool:
+ return False
def add_agent(self, graph_agent: Any) -> None:
raise NotImplementedError()
@@ -150,16 +203,51 @@ def print(self) -> None:
agent.print()
+class BarabasiGraph(Graph):
+ def __init__(self):
+ super().__init__()
+ self.agents: Dict[str, BarabasiAgent] = {}
+ self.m_params = None
+
+ def add_agent(self, graph_agent: BarabasiAgent) -> None:
+ self.agents[graph_agent.name] = graph_agent
+
+ def is_agent_defined(self, agent_type: str) -> bool:
+ return agent_type in self.agents
+
+ def is_agent_percent_amount_used(self) -> bool:
+ for agent in self.agents.values():
+ if isinstance(agent.amount, AgentPercentAmount):
+ return True
+ return False
+
+ def set_m(self, m: Tuple[int, int]) -> None:
+ self.m_params = MParameters(*m)
+
+ def is_m_defined(self) -> bool:
+ return self.m_params is not None
+
+ def print(self) -> None:
+ super().print()
+ print("BarabasiGraph")
+ for agent in self.agents.values():
+ agent.print()
+
+
class MatrixGraph(Graph):
def __init__(self):
super().__init__()
- self.agents = []
+ self.agents: List[MatrixAgent] = []
+ self.scale = None
def add_agent(self, graph_agent: MatrixAgent) -> None:
self.agents.append(graph_agent)
def is_agent_defined(self, agent_type: str) -> bool:
- return any(self.agents, lambda agent: agent.name == agent_type)
+ check_agent: Callable[[MatrixAgent], bool] = (
+ lambda agent: agent.name == agent_type
+ )
+ return all(check_agent(agent) for agent in self.agents)
def set_scale(self, scale: int) -> None:
self.scale = scale
@@ -170,5 +258,45 @@ def is_scale_defined(self) -> bool:
def print(self) -> None:
super().print()
print("MatrixGraph")
+ for agent in self.agents:
+ agent.print()
+
+
+class InhomogenousRandomGraph(Graph):
+ def __init__(self):
+ super().__init__()
+ self.agents: Dict[str, InhomogeneousAgent] = {}
+ self.types_no = None
+ self.order: list[str] = []
+
+ def add_agent(self, graph_agent: InhomogeneousAgent) -> None:
+ self.order.append(graph_agent.name)
+ self.agents[graph_agent.name] = graph_agent
+
+ def is_agent_defined(self, agent_type: str) -> bool:
+ return agent_type in self.agents
+
+ def is_agent_percent_amount_used(self) -> bool:
+ check_agent: Callable[[InhomogeneousAgent], bool] = lambda amount: isinstance(
+ amount, AgentPercentAmount
+ )
+ if any(check_agent(agent) for agent in self.agents.values()):
+ return True
+ return False
+
+ def is_types_amount_defined(self) -> bool:
+ return self.types_no is not None
+
+ def set_types_amount(self, types_no: int) -> None:
+ self.types_no = types_no
+
+ def get_types_amount(self) -> int:
+ if self.types_no is None:
+ raise ValueError("Types amount is not defined")
+ return self.types_no
+
+ def print(self) -> None:
+ super().print()
+ print("InhomogenousRandomGraph")
for agent in self.agents.values():
agent.print()
diff --git a/aasm/intermediate/instruction.py b/aasm/intermediate/instruction.py
index bbddb37..3f9ec45 100644
--- a/aasm/intermediate/instruction.py
+++ b/aasm/intermediate/instruction.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING
+from typing import List as TypingList
if TYPE_CHECKING:
from aasm.intermediate.argument import Argument
@@ -431,3 +432,77 @@ def print(self) -> None:
self.dst.print()
self.dividend.print()
self.divisor.print()
+
+
+class ModuleInstruction(Instruction):
+ def __init__(
+ self,
+ args: TypingList[Argument],
+ op_code: str = "",
+ module: str = "",
+ is_block=False,
+ ):
+ self.args = args
+ self.op_code = op_code
+ self.module = module
+ self.is_block = is_block
+
+ def print(self) -> None:
+ print(f"{self.module}::{self.op_code}")
+ for arg in self.args:
+ arg.print()
+
+
+class Logs(Instruction):
+ def __init__(self, args: TypingList[Argument]):
+ self.args = args
+
+ def print(self) -> None:
+ print("Logs")
+ for arg in self.args:
+ arg.print()
+
+
+class LogsDebug(Logs):
+ def __init__(self, args: TypingList[Argument]):
+ super().__init__(args=args)
+
+ def print(self) -> None:
+ print("LogsDebug")
+ super().print()
+
+
+class LogsInfo(Logs):
+ def __init__(self, args: TypingList[Argument]):
+ super().__init__(args=args)
+
+ def print(self) -> None:
+ print("LogsInfo")
+ super().print()
+
+
+class LogsWarning(Logs):
+ def __init__(self, args: TypingList[Argument]):
+ super().__init__(args=args)
+
+ def print(self) -> None:
+ print("LogsWarning")
+ super().print()
+
+
+class LogsError(Logs):
+ def __init__(self, args: TypingList[Argument]):
+ super().__init__(args=args)
+
+ def print(self) -> None:
+ print("LogsError")
+ super().print()
+
+
+class LogsCritical(Logs):
+ def __init__(self, args: TypingList[Argument]):
+ super().__init__(args=args)
+
+ def print(self) -> None:
+ print("LogsCritical")
+ super().print()
diff --git a/aasm/intermediate/message.py b/aasm/intermediate/message.py
index 1794423..302c6ab 100644
--- a/aasm/intermediate/message.py
+++ b/aasm/intermediate/message.py
@@ -38,6 +38,17 @@ def print(self) -> None:
super().print()
+class ModuleVariableParam(MessageParam):
+ def __init__(self, name: str, type: str):
+ super().__init__(name)
+ self.type: str = type
+
+ def print(self) -> None:
+ print(f"MessageModuleVariableParam")
+ super().print()
+ print(f"Type: {self.type}")
+
+
class Message:
RESERVED_CONNECTION_PARAMS = ["sender"]
RESERVED_TYPE_PARAMS = ["type", "performative"]
@@ -47,6 +58,7 @@ def __init__(self, msg_type: str, msg_performative: str):
self.performative: str = msg_performative
self.float_params: Dict[str, FloatParam] = {}
self.connection_params: Dict[str, MessageParam] = {}
+ self.module_variable_params: Dict[str, ModuleVariableParam] = {}
@property
def param_names(self) -> List[str]:
@@ -55,6 +67,7 @@ def param_names(self) -> List[str]:
*Message.RESERVED_TYPE_PARAMS,
*list(self.float_params),
*list(self.connection_params),
+ *list(self.module_variable_params),
]
@property
@@ -69,6 +82,10 @@ def unset_params(self) -> List[str]:
if not connection_param.is_value_set:
unset_params.append(name)
+ for name, module_variable_param in self.module_variable_params.items():
+ if not module_variable_param.is_value_set:
+ unset_params.append(name)
+
return unset_params
def are_all_params_set(self) -> bool:
@@ -78,6 +95,10 @@ def are_all_params_set(self) -> bool:
connection_param.is_value_set
for connection_param in self.connection_params.values()
]
+ + [
+ module_variable_param.is_value_set
+ for module_variable_param in self.module_variable_params.values()
+ ]
)
def param_exists(self, name: str) -> bool:
@@ -89,9 +110,17 @@ def add_float(self, float_param: FloatParam) -> None:
def add_connection(self, connection_param: ConnectionParam) -> None:
self.connection_params[connection_param.name] = connection_param
+ def add_module_variable(self, module_variable_param: ModuleVariableParam) -> None:
+ self.module_variable_params[module_variable_param.name] = module_variable_param
+
+ def get_module_variable_type(self, name: str) -> str:
+ return self.module_variable_params[name].type
+
def print(self) -> None:
print(f"Message {self.type}/{self.performative}")
for float_param in self.float_params.values():
float_param.print()
for connection_param in self.connection_params.values():
connection_param.print()
+ for module_variable_param in self.module_variable_params.values():
+ module_variable_param.print()
diff --git a/aasm/intermediate/module.py b/aasm/intermediate/module.py
new file mode 100644
index 0000000..6bbc5ca
--- /dev/null
+++ b/aasm/intermediate/module.py
@@ -0,0 +1,3 @@
+class Module:
+ def __init__(self, name: str):
+ self.name: str = name
diff --git a/aasm/modules/__init__.py b/aasm/modules/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/aasm/modules/instruction.py b/aasm/modules/instruction.py
new file mode 100644
index 0000000..286b8b7
--- /dev/null
+++ b/aasm/modules/instruction.py
@@ -0,0 +1,132 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Dict
+from aasm.intermediate.argument import Argument
+from aasm.modules.type import Type
+
+from aasm.utils.exception import PanicException
+
+from aasm.intermediate.argument import Mutable, Float, ModuleVariable
+from aasm.intermediate.instruction import ModuleInstruction
+
+if TYPE_CHECKING:
+ from aasm.parsing.state import State
+
+
+class Instruction:
+ def __init__(
+ self,
+ module_name: str,
+ available_types: List[Type],
+ opcode: str,
+ args: List[str],
+ ):
+ self.module = module_name
+ if opcode.endswith("*"):
+ self.opcode = opcode[:-1]
+ self.is_block = True
+ else:
+ self.opcode = opcode
+ self.is_block = False
+ self.available_types = available_types
+ self.args_dict: Dict[str, List[str]] = {}
+ self.arg_names = []
+ self._parse_args(args)
+
+ def _parse_args(self, args: List[str]):
+ current_var_name = ""
+ current_var_type = ""
+ first_arg = True
+ for arg in args:
+ if arg.endswith(":"):
+ if not first_arg:
+ self._validate_var_declaration(current_var_name)
+ current_var_name = arg[:-1]
+ self.arg_names.append(current_var_name)
+ current_var_type = ""
+ # verify that the variable name is not already used
+ if current_var_name in self.args_dict:
+ raise PanicException(
+ f"Error in module {self.module}, instruction {self.opcode}. Variable {current_var_name} already defined.",
+ f"Variable {current_var_name} is already defined.",
+ "Rename the variable.",
+ )
+ self.args_dict[current_var_name] = []
+ first_arg = False
+ else:
+ current_var_type = arg
+ self.args_dict[current_var_name].append(current_var_type)
+
+ def _validate_var_declaration(self, current_var_name: str):
+ if len(self.args_dict[current_var_name]) == 0:
+ raise PanicException(
+ f"Error in module {self.module}, instruction {self.opcode}. Missing type for variable {current_var_name}.",
+ f"Variable {current_var_name} has no type specified.",
+ "Specify a type for the variable.",
+ )
+ # verify that the types associated with current_var_name are valid
+ for var_type in self.args_dict[current_var_name]:
+ if var_type not in [tmp_type.name for tmp_type in self.available_types]:
+ raise PanicException(
+ f"Error in module {self.module}, instruction {self.opcode}. Type {var_type} is not defined.",
+ f"Type {var_type} is not defined.",
+ "Define the type.",
+ )
+
+ def op(self, state: State, arguments: List[str]) -> None:
+ state.require(
+ state.in_action,
+ "Not inside any action.",
+ f"{self.opcode} can be used inside actions.",
+ )
+ state.require(
+ len(arguments) == len(self.args_dict.keys()),
+ f"Wrong number of arguments for {self.opcode}.",
+ f"Expected {len(self.args_dict.keys())}, got {len(arguments)}.",
+ )
+
+ # print(f"Parsing arguments from self.args_dict: {self.args_dict} and arguments: {arguments}")
+ parsed_args = [Argument(state, arg) for arg in arguments]
+ state.require(
+ self._validate_types_in_op_context(parsed_args),
+ f"Mismatched types in the {self.module}::{self.opcode} context.: {[arg.explain() for arg in parsed_args]}",
+ f"Refer to module documentation for further help.",
+ )
+
+ state.last_action.add_instruction(
+ ModuleInstruction(parsed_args, self.opcode, self.module, self.is_block)
+ )
+ # for arg in parsed_args:
+ # arg.print()
+ if self.is_block:
+ state.last_action.start_block()
+
+ def _validate_types_in_op_context(self, parsed_args) -> bool:
+ arg_idx = 0
+ types_to_check = []
+ for arg in parsed_args:
+ for arg_type in self.args_dict[self.arg_names[arg_idx]]:
+ if arg_type == "mut":
+ types_to_check.append(Mutable)
+ elif arg_type == "float":
+ types_to_check.append(Float)
+ else:
+ new_type = arg.get_modvar_type(arg_type)
+ types_to_check.append(new_type)
+
+ for arg_type in types_to_check:
+ if not arg.has_type(arg_type):
+ return False
+ arg.set_op_type(*types_to_check)
+ arg_idx += 1
+
+ return True
+
+ def __str__(self) -> str:
+ ret = f"{self.module}.{self.opcode}({', '.join(self.args_dict.keys())})"
+ if self.is_block:
+ ret += "[BLOCK]"
+ return ret
+
+ def __repr__(self) -> str:
+ return str(self)
diff --git a/aasm/modules/module.py b/aasm/modules/module.py
new file mode 100644
index 0000000..ba35952
--- /dev/null
+++ b/aasm/modules/module.py
@@ -0,0 +1,197 @@
+from __future__ import annotations
+
+from typing import List, Dict, Tuple
+
+from aasm.modules.instruction import Instruction
+from aasm.modules.type import Type
+from aasm.utils.exception import PanicException
+
+
+class Target:
+ def __init__(self, target: str):
+ self.name = target
+
+ def __repr__(self):
+ return f"Target[{self.name}]"
+
+ def __str__(self):
+ return f"Target[{self.name}]"
+
+
+class Module:
+ def __init__(self, module_code_lines: List[str]):
+ self.name = None
+ # TODO: Change targets and types into classes
+ self.targets: List[Target] = []
+ self.types: List[Type] = []
+ self.instructions: List[Instruction] = []
+ self.preambles: Dict[str, List[str]] = {}
+ self.impls: Dict[Tuple[str, str], List[str]] = {}
+ self.description: List[str] = []
+
+ self._in_targets = False
+ self._in_instructions = False
+ self._in_preamble = False
+ self._in_impl = False
+ self._in_types = False
+ self._in_description = False
+ self._current_target = None
+ self._current_instruction = None
+
+ self._parse_module_code(module_code_lines)
+ # TODO: validate module -- check that all instructions are implemented for all targets, has a name etc.
+ # self._validate_module()
+
+ def does_target(self, target: str) -> bool:
+ return target in [target.name for target in self.targets]
+
+ def _reset_scope(self):
+ self._in_targets = False
+ self._in_instructions = False
+ self._in_preamble = False
+ self._in_impl = False
+ self._in_types = False
+ self._in_description = False
+ self._current_target = None
+ self._current_instruction = None
+
+ def _parse_module_code(self, lines: List[str]):
+ for line in lines:
+ tokens = line.strip().split()
+ tokens = [token.strip().strip(",") for token in tokens]
+ match tokens:
+ case ["!name", name]:
+ self._reset_scope()
+ self.name = name
+ case ["!types"]:
+ self._reset_scope()
+ self._in_types = True
+ case ["!description"]:
+ self._reset_scope()
+ self._in_description = True
+ case ["!targets"]:
+ self._reset_scope()
+ self._in_targets = True
+ case ["!instructions"]:
+ self._reset_scope()
+ self._in_instructions = True
+ case ["!preamble", target]:
+ self._reset_scope()
+ self._in_preamble = True
+ self._current_target = target
+ case ["!impl", instruction, target]:
+ self._reset_scope()
+ self._in_impl = True
+ self._current_target = target
+ self._current_instruction = instruction
+ case _:
+ if len(tokens) == 0:
+ continue
+ elif tokens[0].startswith("#"):
+ continue
+ elif tokens[0].startswith("!"):
+ raise PanicException(
+ "Invalid line: " + line,
+ "Unkown module directive",
+ "Only module directives can start with !",
+ )
+ elif self._in_targets:
+ if len(tokens) != 1:
+ raise PanicException(
+ "Invalid target line: " + line,
+ "Multiple tokens in target line",
+ "Target lines must have exactly one token: e.g. spade",
+ )
+ self.targets.append(Target(tokens[0]))
+ elif self._in_instructions:
+ if self.name is None:
+ raise PanicException(
+ "Invalid instruction line: " + line,
+ "Module name is undefined",
+ "Module name must be defined before instructions. Define module name with !name [name]",
+ )
+ else:
+ self.instructions.append(
+ Instruction(
+ self.name, self.types, tokens[0], tokens[1:]
+ )
+ )
+ elif self._in_preamble:
+ if self._current_target is None:
+ raise PanicException(
+ "Invalid preamble line: Target is undefined: " + line,
+ "Target is undefined",
+ "Target must be defined before preamble. Define target with !preamble [target]",
+ )
+ self.preambles.setdefault(self._current_target, []).append(line)
+ elif self._in_impl:
+ if self._current_target is None:
+ raise PanicException(
+ "Invalid impl line: Target is undefined: " + line,
+ "Target is undefined",
+ "Target must be defined before impl. Define target with !impl [instruction] [target]",
+ )
+ if self._current_instruction is None:
+ raise PanicException(
+ "Invalid impl line: Instruction is undefined: " + line,
+ "Instruction is undefined",
+ "Instruction must be defined before impl. Define instruction with !impl [instruction] [target]",
+ )
+ self.impls.setdefault(
+ (self._current_target, self._current_instruction), []
+ ).append(line)
+ elif self._in_types:
+ if len(tokens) != 1:
+ raise PanicException(
+ "Invalid type line: " + line,
+ "Multiple tokens in type line",
+ "Type lines must have exactly one token: e.g. int64",
+ )
+ if self.name is None:
+ raise PanicException(
+ "Invalid instruction line: " + line,
+ "Module name is undefined",
+ "Module name must be defined before instructions. Define module name with !name [name]",
+ )
+ else:
+ self.types.append(Type(tokens[0], self.name))
+ elif self._in_description:
+ self.description.append(line)
+ else:
+ raise PanicException(
+ "Invalid line: " + line,
+ "Unkown line",
+ "Line is not a module directive, target, instruction, preamble or impl",
+ )
+
+ def __repr__(self):
+ return (
+ f"Module[{self.name}] ("
+ + repr(self.targets)
+ + "\n"
+ + repr(self.types)
+ + "\n"
+ + repr(self.instructions)
+ + "\n"
+ + repr(self.preambles)
+ + "\n"
+ + repr(self.impls)
+ + ")"
+ )
+
+ def __str__(self):
+ return (
+ f"Module[{self.name}] (\n"
+ + str(self.description)
+ + "\n"
+ + str(self.targets)
+ + "\n"
+ + str(self.types)
+ + "\n"
+ + str(self.instructions)
+ + "\n"
+ + str(self.preambles)
+ + "\n"
+ + str(self.impls)
+ + ")"
+ )
diff --git a/aasm/modules/type.py b/aasm/modules/type.py
new file mode 100644
index 0000000..44f4bf2
--- /dev/null
+++ b/aasm/modules/type.py
@@ -0,0 +1,17 @@
+from aasm.intermediate.argument import ModuleVariable
+
+
+class Type:
+ def __init__(self, name: str, module: str):
+ self.name = name
+ self.type_class = type(name, (ModuleVariable,), {})
+ self.module = module
+
+ def full_qualified_name(self) -> str:
+ return f"{self.module}::{self.name}"
+
+ def __repr__(self):
+ return f"Type[{self.full_qualified_name()}]"
+
+ def __str__(self):
+ return f"Type[{self.full_qualified_name()}]"
diff --git a/aasm/parsing/op/decl.py b/aasm/parsing/op/decl.py
index ed582ea..84a3b67 100644
--- a/aasm/parsing/op/decl.py
+++ b/aasm/parsing/op/decl.py
@@ -3,7 +3,11 @@
from typing import TYPE_CHECKING
from aasm.intermediate.argument import Argument
-from aasm.intermediate.declaration import ConnectionDeclaration, FloatDeclaration
+from aasm.intermediate.declaration import (
+ ConnectionDeclaration,
+ FloatDeclaration,
+ ModuleVariableDeclaration,
+)
from aasm.utils.validation import is_valid_name, print_invalid_names
if TYPE_CHECKING:
@@ -45,4 +49,9 @@ def op_DECL(state: State, name: str, category: str, value: str) -> None:
)
case _:
- state.panic(f"Incorrect declaration: DECL {name} {category} {value}")
+ if category in state.get_module_types():
+ state.last_action.add_module_variable_declaration(
+ ModuleVariableDeclaration(name_arg, value_arg, category)
+ )
+ else:
+ state.panic(f"Incorrect declaration: DECL {name} {category} {value}")
diff --git a/aasm/parsing/op/defg.py b/aasm/parsing/op/defg.py
index fcae3c0..257efd6 100644
--- a/aasm/parsing/op/defg.py
+++ b/aasm/parsing/op/defg.py
@@ -11,6 +11,8 @@
ConnectionDistUniformAmount,
StatisticalAgent,
StatisticalGraph,
+ BarabasiAgent,
+ BarabasiGraph,
)
from aasm.utils.validation import is_float, is_int
@@ -26,10 +28,12 @@ def op_DEFG(state: State, agent_name: str, amount: str, args: List[str]) -> None
"Cannot define agent graph amount outside graph scope.",
"Try defining new graphs using GRAPH.",
)
+ is_statistical = isinstance(state.last_graph, StatisticalGraph)
+ is_barabsi = isinstance(state.last_graph, BarabasiGraph)
state.require(
- isinstance(state.last_graph, StatisticalGraph),
- "DEFG can be used with statistical graphs.",
- "Define statistical graphs with GRAPH statistical.",
+ is_statistical ^ is_barabsi,
+ "DEFG can be used with statistical and barabasi graphs.",
+ "Define statistical graphs with GRAPH statistical|GRAPH barabasi-albert.",
)
state.require(state.agent_exists(agent_name), f"Agent {agent_name} is not defined.")
@@ -55,48 +59,51 @@ def op_DEFG(state: State, agent_name: str, amount: str, args: List[str]) -> None
agent_amount = AgentConstantAmount(amount)
- connection_amount: ConnectionAmount | None = None
- match args:
- case [value]:
- state.require(is_int(value), f"{value} is not a valid integer.")
- state.require(
- int(value) >= 0,
- f"{value} is not a valid connection amount.",
- "Amount must be non-negative.",
- )
-
- connection_amount = ConnectionConstantAmount(value)
-
- case ["dist_normal", mean, std_dev]:
- state.require(is_float(mean), f"{mean} is not a valid float.")
- state.require(is_float(std_dev), f"{std_dev} is not a valid float.")
- state.require(
- float(std_dev) >= 0,
- f"{std_dev} is not a valid standard deviation parameter.",
- "Standard deviation must be non-negative.",
- )
-
- connection_amount = ConnectionDistNormalAmount(mean, std_dev)
-
- case ["dist_exp", lambda_]:
- state.require(is_float(lambda_), f"{lambda_} is not a valid float.")
- state.require(
- float(lambda_) > 0,
- f"{lambda_} is not a valid lambda parameter.",
- "Lambda must be positive.",
- )
-
- connection_amount = ConnectionDistExpAmount(lambda_)
-
- case ["dist_uniform", a, b]:
- state.require(is_float(a), f"{a} is not a valid float.")
- state.require(is_float(b), f"{b} is not a valid float.")
-
- connection_amount = ConnectionDistUniformAmount(a, b)
-
- case _:
- state.panic(f"Incorrect operation: DEFG {agent_name} {amount} {args}")
-
- state.last_graph.add_agent(
- StatisticalAgent(agent_name, agent_amount, connection_amount)
- )
+ if is_statistical:
+ connection_amount: ConnectionAmount | None = None
+ match args:
+ case [value]:
+ state.require(is_int(value), f"{value} is not a valid integer.")
+ state.require(
+ int(value) >= 0,
+ f"{value} is not a valid connection amount.",
+ "Amount must be non-negative.",
+ )
+
+ connection_amount = ConnectionConstantAmount(value)
+
+ case ["dist_normal", mean, std_dev]:
+ state.require(is_float(mean), f"{mean} is not a valid float.")
+ state.require(is_float(std_dev), f"{std_dev} is not a valid float.")
+ state.require(
+ float(std_dev) >= 0,
+ f"{std_dev} is not a valid standard deviation parameter.",
+ "Standard deviation must be non-negative.",
+ )
+
+ connection_amount = ConnectionDistNormalAmount(mean, std_dev)
+
+ case ["dist_exp", lambda_]:
+ state.require(is_float(lambda_), f"{lambda_} is not a valid float.")
+ state.require(
+ float(lambda_) > 0,
+ f"{lambda_} is not a valid lambda parameter.",
+ "Lambda must be positive.",
+ )
+
+ connection_amount = ConnectionDistExpAmount(lambda_)
+
+ case ["dist_uniform", a, b]:
+ state.require(is_float(a), f"{a} is not a valid float.")
+ state.require(is_float(b), f"{b} is not a valid float.")
+
+ connection_amount = ConnectionDistUniformAmount(a, b)
+
+ case _:
+ state.panic(f"Incorrect operation: DEFG {agent_name} {amount} {args}")
+
+ state.last_graph.add_agent(
+ StatisticalAgent(agent_name, agent_amount, connection_amount)
+ )
+ else:
+ state.last_graph.add_agent(BarabasiAgent(agent_name, agent_amount))
diff --git a/aasm/parsing/op/defnode.py b/aasm/parsing/op/defnode.py
index 495b166..56c6fe0 100644
--- a/aasm/parsing/op/defnode.py
+++ b/aasm/parsing/op/defnode.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, List
+from typing import TYPE_CHECKING
from aasm.intermediate.graph import AdjRow, MatrixAgent, MatrixGraph
diff --git a/aasm/parsing/op/deftype.py b/aasm/parsing/op/deftype.py
new file mode 100644
index 0000000..1c00914
--- /dev/null
+++ b/aasm/parsing/op/deftype.py
@@ -0,0 +1,68 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+
+from aasm.intermediate.graph import (
+ InhomogenousRandomGraph,
+ InhomogeneousAgent,
+ AgentConstantAmount,
+ ConnectionConstantAmount,
+)
+
+
+from aasm.utils.validation import is_int, is_float
+
+if TYPE_CHECKING:
+ from aasm.parsing.state import State
+
+
+def op_DEFTYPE(
+ state: State, agent_name: str, amount: str, conn_amounts: List[str]
+) -> None:
+ state.require(state.agent_exists(agent_name), f"Agent {agent_name} is not defined.")
+ state.require(
+ not state.last_graph.is_agent_defined(agent_name),
+ f"Agent {agent_name} is already defined.",
+ )
+ state.require(
+ state.in_graph,
+ "Cannot define agent type outside of graph scope.",
+ "Try defining new graph using GRAPH.",
+ )
+ state.require(
+ isinstance(state.last_graph, InhomogenousRandomGraph),
+ "DEFTYPE can be used only in irg graphs.",
+ "Define irg graphs with GRAPH irg.",
+ )
+ state.require(
+ state.last_graph.is_types_amount_defined(),
+ "The amount of types is not defined.",
+ "Define the amount of types using TYPES.",
+ )
+ agent_amount: AgentConstantAmount | None = None
+ state.require(is_int(amount), f"{amount} is not a valid integer.")
+ agent_amount = AgentConstantAmount(amount)
+ connection_amounts: List[ConnectionConstantAmount] = []
+ state.require(
+ len(conn_amounts) == state.last_graph.get_types_amount(),
+ f"Amount of connection amounts ({len(conn_amounts)}) does not match the amount of types ({state.last_graph.get_types_amount()}).",
+ )
+ for conn_amount in conn_amounts:
+ state.require(
+ is_float(conn_amount), f"{conn_amount} is not a valid floating number."
+ )
+ state.require(
+ float(conn_amount) >= 0,
+ f"{conn_amount} is less than zero.",
+ "Connection amounts must be between 0 and 100.",
+ )
+ state.require(
+ float(conn_amount) <= 100,
+ f"{conn_amount} is greater than 100.",
+ "Connection amounts must be between 0 and 100.",
+ )
+ connection_amounts.append(ConnectionConstantAmount(conn_amount))
+
+ state.last_graph.add_agent(
+ InhomogeneousAgent(agent_name, agent_amount, connection_amounts)
+ )
diff --git a/aasm/parsing/op/graph.py b/aasm/parsing/op/graph.py
index 8dec0c2..719f5ab 100644
--- a/aasm/parsing/op/graph.py
+++ b/aasm/parsing/op/graph.py
@@ -2,7 +2,12 @@
from typing import TYPE_CHECKING
-from aasm.intermediate.graph import MatrixGraph, StatisticalGraph
+from aasm.intermediate.graph import (
+ MatrixGraph,
+ StatisticalGraph,
+ BarabasiGraph,
+ InhomogenousRandomGraph,
+)
if TYPE_CHECKING:
from aasm.parsing.state import State
@@ -33,6 +38,10 @@ def op_GRAPH(state: State, category: str) -> None:
state.add_graph(StatisticalGraph())
case "matrix":
state.add_graph(MatrixGraph())
+ case "barabasi-albert":
+ state.add_graph(BarabasiGraph())
+ case "irg":
+ state.add_graph(InhomogenousRandomGraph())
case _:
state.panic(f"Incorrect operation: GRAPH {category}")
@@ -45,12 +54,24 @@ def op_EGRAPH(state: State) -> None:
)
if (
isinstance(state.last_graph, StatisticalGraph)
- and state.last_graph.is_agent_percent_amount_used()
- ):
+ or isinstance(state.last_graph, InhomogenousRandomGraph)
+ ) and state.last_graph.is_agent_percent_amount_used():
state.require(
state.last_graph.is_size_defined(),
"Graph size is not defined.",
"Graph size must be defined to use agent percent amount.",
)
+ elif isinstance(state.last_graph, BarabasiGraph):
+ state.require(
+ state.last_graph.is_m_defined(),
+ "Graph MParameters is not defined.",
+ "Graph MParameters must be defined to use barabasi graph.",
+ )
+ if state.last_graph.is_agent_percent_amount_used():
+ state.require(
+ state.last_graph.is_size_defined(),
+ "Graph size is not defined.",
+ "Graph size must be defined to use agent percent amount.",
+ )
state.in_graph = False
diff --git a/aasm/parsing/op/logs.py b/aasm/parsing/op/logs.py
new file mode 100644
index 0000000..2f6efae
--- /dev/null
+++ b/aasm/parsing/op/logs.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+
+from aasm.intermediate.argument import Argument
+from aasm.intermediate.instruction import (
+ LogsCritical,
+ LogsDebug,
+ LogsError,
+ LogsInfo,
+ LogsWarning,
+)
+from aasm.utils.validation import print_valid_logs_levels
+
+if TYPE_CHECKING:
+ from parsing.state import State
+
+
+def op_LOGS(state: State, level: str, args: List[str]) -> None:
+ state.require(
+ state.in_action, "Not inside any action", f"LOGS can be used inside actions."
+ )
+
+ intermediate_args: List[Argument] = []
+ for arg in [Argument(state, arg) for arg in args]:
+ intermediate_args.extend(arg.create_all_possible_types(state))
+
+ match level:
+ case "debug":
+ state.last_action.add_instruction(LogsDebug(intermediate_args))
+
+ case "info":
+ state.last_action.add_instruction(LogsInfo(intermediate_args))
+
+ case "warning":
+ state.last_action.add_instruction(LogsWarning(intermediate_args))
+
+ case "error":
+ state.last_action.add_instruction(LogsError(intermediate_args))
+
+ case "critical":
+ state.last_action.add_instruction(LogsCritical(intermediate_args))
+
+ case _:
+ state.panic(
+ f"Unexpected error: LOGS {level} {args}",
+ f"Supported LOGS levels are: {print_valid_logs_levels()}",
+ )
diff --git a/aasm/parsing/op/module.py b/aasm/parsing/op/module.py
new file mode 100644
index 0000000..7e7ba38
--- /dev/null
+++ b/aasm/parsing/op/module.py
@@ -0,0 +1,38 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+from aasm.intermediate.module import Module
+from aasm.utils.validation import is_valid_name, print_invalid_names
+
+if TYPE_CHECKING:
+ from aasm.parsing.state import State
+
+
+def op_MODULE(state: State, name: str) -> None:
+ state.require(
+ not state.in_agent,
+ "Cannot define modules inside agents.",
+ "First end current agent using EAGENT.",
+ )
+ state.require(
+ not state.in_graph,
+ "Cannot define modules inside graphs.",
+ "First end current graph using EGRAPH.",
+ )
+ state.require(
+ not state.module_exists(name),
+ f"Module {name} already exists in the current environment.",
+ )
+
+ state.require(
+ state.module_is_loaded(name),
+ f"Module {name} is not loaded.",
+ )
+
+ # NOTE: I don't think that's necessary for modules
+ # state.require(
+ # is_valid_name(name),
+ # f"{name} is not a correct name.",
+ # f"Names can only contain alphanumeric characters, underscores and cannot be: {print_invalid_names()}.",
+ # )
+ state.add_module(Module(name))
diff --git a/aasm/parsing/op/mparams.py b/aasm/parsing/op/mparams.py
new file mode 100644
index 0000000..427bba5
--- /dev/null
+++ b/aasm/parsing/op/mparams.py
@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from aasm.utils.validation import is_int
+
+if TYPE_CHECKING:
+ from aasm.parsing.state import State
+
+
+def op_MPARAMS(state: State, m0: str, m_inc: str) -> None:
+ state.require(
+ state.in_graph,
+ "Not inside any graph",
+ "MPARAMS can only be used inside graphs.",
+ )
+ state.require(not state.last_graph.is_m_defined(), "MPARAMS already defined.")
+ state.require(is_int(m0) and is_int(m_inc), "MPARAMS must be integers.")
+ m_begin = int(m0)
+ m_increment = int(m_inc)
+ state.require(m_begin >= 0 and m_increment >= 0, "MPARAMS must be non-negative.")
+
+ state.last_graph.set_m((m_begin, m_increment))
diff --git a/aasm/parsing/op/prm.py b/aasm/parsing/op/prm.py
index 017ef90..dd46418 100644
--- a/aasm/parsing/op/prm.py
+++ b/aasm/parsing/op/prm.py
@@ -10,8 +10,10 @@
from aasm.intermediate.agent import FloatListParam as AgentFloatListParam
from aasm.intermediate.agent import InitFloatParam as AgentInitFloatParam
from aasm.intermediate.agent import MessageListParam as AgentMessageListParam
+from aasm.intermediate.agent import ModuleVariableParam as AgentModuleVariableParam
from aasm.intermediate.message import ConnectionParam as MessageConnectionParam
from aasm.intermediate.message import FloatParam as MessageFloatParam
+from aasm.intermediate.message import ModuleVariableParam as MessageModuleVariableParam
from aasm.utils.validation import (
is_float,
is_valid_enum_list,
@@ -100,7 +102,14 @@ def op_agent_PRM(state: State, name: str, category: str, args: List[str]) -> Non
state.last_agent.add_enum(AgentEnumParam(name, enums))
case _:
- state.panic(f"Incorrect operation: (agent) PRM {name} {category} {args}")
+ if category in state.get_module_types():
+ state.last_agent.add_module_variable(
+ AgentModuleVariableParam(name, category)
+ )
+ else:
+ state.panic(
+ f"Incorrect operation: (agent) PRM {name} {category} {args}"
+ )
def op_message_PRM(state: State, name: str, category: str) -> None:
@@ -127,4 +136,9 @@ def op_message_PRM(state: State, name: str, category: str) -> None:
state.last_message.add_connection(MessageConnectionParam(name))
case _:
- state.panic(f"Incorrect operation: (message) PRM {name} {category}")
+ if category in state.get_module_types():
+ state.last_message.add_module_variable(
+ MessageModuleVariableParam(name, category)
+ )
+ else:
+ state.panic(f"Incorrect operation: (message) PRM {name} {category}")
diff --git a/aasm/parsing/op/set.py b/aasm/parsing/op/set.py
index 1b5b733..73993f8 100644
--- a/aasm/parsing/op/set.py
+++ b/aasm/parsing/op/set.py
@@ -16,7 +16,7 @@ def op_SET(state: State, arg1: str, arg2: str) -> None:
lhs = Argument(state, arg1)
rhs = Argument(state, arg2)
state.require(
- lhs.assignment_context(rhs),
+ lhs.assignment_context(rhs, state),
"Mismatched types in the assignment context.",
f"ARG1 {lhs.explain()}, ARG2 {rhs.explain()}",
)
diff --git a/aasm/parsing/op/types.py b/aasm/parsing/op/types.py
new file mode 100644
index 0000000..a85a5ff
--- /dev/null
+++ b/aasm/parsing/op/types.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from aasm.utils.validation import is_int
+
+from aasm.intermediate.graph import InhomogenousRandomGraph
+
+if TYPE_CHECKING:
+ from aasm.parsing.state import State
+
+
+def op_TYPES(state: State, types: str) -> None:
+ state.require(
+ state.in_graph, "Not inside any graph.", "SIZE can be used inside graphs."
+ )
+ state.require(
+ isinstance(state.last_graph, InhomogenousRandomGraph),
+ "TYPES can be used only in irg graphs.",
+ "Define irg graphs with GRAPH irg.",
+ )
+ state.require(
+ not state.last_graph.is_types_amount_defined(), "Types are already defined."
+ )
+ state.require(is_int(types), "Types must be an integer value.", "Example: TYPES 3")
+ state.require(int(types) >= 0, "Types cannot be a negative number.")
+ state.last_graph.set_types_amount(int(types))
diff --git a/aasm/parsing/parse.py b/aasm/parsing/parse.py
index deebc72..e1b24d9 100644
--- a/aasm/parsing/parse.py
+++ b/aasm/parsing/parse.py
@@ -18,6 +18,7 @@
from aasm.parsing.op.len import op_LEN
from aasm.parsing.op.list_inclusion import handle_list_inclusion
from aasm.parsing.op.list_modification import handle_list_modification
+from aasm.parsing.op.logs import op_LOGS
from aasm.parsing.op.lr import op_LR
from aasm.parsing.op.lw import op_LW
from aasm.parsing.op.math import handle_math_statement
@@ -32,15 +33,20 @@
from aasm.parsing.op.send import op_SEND
from aasm.parsing.op.set import op_SET
from aasm.parsing.op.size import op_SIZE
+from aasm.parsing.op.mparams import op_MPARAMS
from aasm.parsing.op.subs import op_SUBS
+from aasm.parsing.op.deftype import op_DEFTYPE
+from aasm.parsing.op.types import op_TYPES
+from aasm.parsing.op.module import op_MODULE
from aasm.parsing.state import State
if TYPE_CHECKING:
from aasm.parsing.state import ParsedData
+ from aasm.modules.module import Module
-def parse_lines(lines: List[str], debug: bool) -> ParsedData:
- state = State(lines, debug)
+def parse_lines(lines: List[str], debug: bool, modules: List[Module]) -> ParsedData:
+ state = State(lines, modules, debug)
for tokens in state.tokens_from_lines():
match tokens:
case ["AGENT", name]:
@@ -150,19 +156,41 @@ def parse_lines(lines: List[str], debug: bool) -> ParsedData:
case ["SCALE", scale]:
op_SCALE(state, scale)
+ case ["MPARAMS", m0, m_inc]:
+ op_MPARAMS(state, m0, m_inc)
+
case ["DEFG", agent_name, amount, *args]:
op_DEFG(state, agent_name, amount, args)
case ["DEFNODE", agent_name, row]:
op_DEFNODE(state, agent_name, row)
+ case ["DEFTYPE", agent_type, amount, *args]:
+ op_DEFTYPE(state, agent_type, amount, args)
+
+ case ["TYPES", amount]:
+ op_TYPES(state, amount)
+
case ["LR", dst, list_, idx]:
op_LR(state, dst, list_, idx)
case ["LW", list_, idx, value]:
op_LW(state, list_, idx, value)
- case _:
- state.panic(f"Unknown tokens: {tokens}")
+ case ["LOGS", level, *args]:
+ op_LOGS(state, level, args)
+
+ case ["MODULE", name]:
+ op_MODULE(state, name)
+
+ case [OPCODE, *args]:
+ found = False
+ for module in state.loaded_modules:
+ for instruction in module.instructions:
+ if instruction.opcode == OPCODE:
+ found = True
+ instruction.op(state, args)
+ if not found:
+ state.panic(f"Unknown tokens: {tokens}")
return state.get_parsed_data()
diff --git a/aasm/parsing/state.py b/aasm/parsing/state.py
index de44b06..88ac918 100644
--- a/aasm/parsing/state.py
+++ b/aasm/parsing/state.py
@@ -7,12 +7,15 @@
from aasm.preprocessor.preprocessor import Preprocessor
from aasm.utils.exception import PanicException
+
if TYPE_CHECKING:
from aasm.intermediate.action import Action
from aasm.intermediate.agent import Agent
from aasm.intermediate.behaviour import Behaviour
from aasm.intermediate.graph import Graph
from aasm.intermediate.message import Message
+ from aasm.intermediate.module import Module
+ from aasm.modules.module import Module as LoadedModule
class ParsedData:
@@ -25,7 +28,7 @@ def __init__(
class State:
- def __init__(self, lines: List[str], debug: bool):
+ def __init__(self, lines: List[str], modules: List[LoadedModule], debug: bool):
self.debug: bool = debug
self.preprocessor = Preprocessor(lines)
self.lines: List[str] = self.preprocessor.run()
@@ -38,6 +41,9 @@ def __init__(self, lines: List[str], debug: bool):
self.agents: Dict[str, Agent] = {}
self.messages: Dict[Tuple[str, str], Message] = {}
self.graph: Graph | None = None
+ # TODO: Refactor this to a single module type
+ self.modules: Dict[str, Module] = {}
+ self.loaded_modules: List[LoadedModule] = modules
@property
def last_agent(self) -> Agent:
@@ -70,6 +76,9 @@ def add_message(self, message: Message) -> None:
def add_graph(self, graph: Graph) -> None:
self.graph = graph
+ def add_module(self, module: Module) -> None:
+ self.modules[module.name] = module
+
def agent_exists(self, name: str) -> bool:
return name in self.agents
@@ -79,9 +88,29 @@ def message_exists(self, msg_type: str, msg_performative: str) -> bool:
def graph_exists(self) -> bool:
return self.graph is not None
+ def module_exists(self, name: str) -> bool:
+ return name in self.modules
+
+ def module_is_loaded(self, name: str) -> bool:
+ return name in [module.name for module in self.loaded_modules]
+
def get_message_instance(self, msg_type: str, msg_performative: str) -> Message:
return deepcopy(self.messages[(msg_type, msg_performative)])
+ def get_module_types(self) -> List[str]:
+ ret_list = []
+ for module in self.modules.values():
+ loaded_module = self._find_module(module.name)
+ if loaded_module is not None:
+ ret_list += [mod_type.name for mod_type in loaded_module.types]
+ return ret_list
+
+ def _find_module(self, name):
+ for module in self.loaded_modules:
+ if module.name == name:
+ return module
+ return None
+
def tokens_from_lines(self) -> Generator[list[str], None, None]:
for line in self.lines:
self.line_num += 1
diff --git a/aasm/translate.py b/aasm/translate.py
index 651568d..5f10c40 100644
--- a/aasm/translate.py
+++ b/aasm/translate.py
@@ -5,15 +5,18 @@
from typing import TYPE_CHECKING, List, Tuple
from aasm.generating.python_spade import get_spade_code
+from aasm.generating.python_module import get_modules_for_target
from aasm.utils.exception import PanicException
if TYPE_CHECKING:
from aasm.generating.code import Code
+ from aasm.modules.module import Module
-def get_args() -> Tuple[str, str, bool]:
+def get_args() -> Tuple[str, str, bool, List[str]]:
parser = ArgumentParser()
parser.add_argument("input_path", type=str, help="path to the input file")
+ parser.add_argument("-I", "--include", nargs="*", help="include paths")
parser.add_argument(
"-d", "--debug", action="store_true", help="toggle the compiler debug mode"
)
@@ -21,7 +24,7 @@ def get_args() -> Tuple[str, str, bool]:
"-o", "--output-path", type=str, default="out", help="the output file path"
)
args = parser.parse_args()
- return args.input_path, args.output_path, args.debug
+ return args.input_path, args.output_path, args.debug, args.include
def get_input(input_path: str) -> List[str]:
@@ -36,11 +39,25 @@ def save_output(output_path: str, code: Code) -> None:
f.write(code_line)
-def main(input_path: str, output_path: str, debug: bool) -> None:
+def main(
+ input_path: str, output_path: str, debug: bool, includes: List[str] | None
+) -> None:
+ loaded_module_files: List[List[str]] = []
+ if includes is not None:
+ try:
+ for include in includes:
+ loaded_module_files.append(get_input(include))
+ except PanicException as e:
+ e.print()
+ exit(1)
+
lines = get_input(input_path)
start_time = datetime.now()
try:
- spade_code = get_spade_code(lines, indent_size=4, debug=debug)
+ spade_modules = get_modules_for_target(loaded_module_files, "spade")
+ spade_code = get_spade_code(
+ lines, indent_size=4, debug=debug, modules=spade_modules
+ )
except PanicException as e:
e.print()
exit(1)
@@ -50,5 +67,5 @@ def main(input_path: str, output_path: str, debug: bool) -> None:
if __name__ == "__main__":
- input_path, output_path, debug = get_args()
- main(input_path, output_path, debug)
+ input_path, output_path, debug, includes = get_args()
+ main(input_path, output_path, debug, includes)
diff --git a/aasm/utils/validation.py b/aasm/utils/validation.py
index f618740..cf855a1 100644
--- a/aasm/utils/validation.py
+++ b/aasm/utils/validation.py
@@ -60,11 +60,11 @@ def get_invalid_names() -> List[str]:
"spade",
"copy",
"uuid",
+ "inspect",
"get_json_from_spade_message",
"get_spade_message",
"logger",
"any",
- "sys",
"limit_number",
"int",
"BackupBehaviour",
@@ -72,6 +72,8 @@ def get_invalid_names() -> List[str]:
"backup_period",
"backup_delay",
"setup",
+ "str",
+ "float",
]
invalid_names.extend(keyword.kwlist)
return invalid_names
@@ -79,3 +81,11 @@ def get_invalid_names() -> List[str]:
def print_invalid_names() -> str:
return ", ".join(get_invalid_names())
+
+
+def get_valid_logs_levels() -> List[str]:
+ return ["debug", "info", "warning", "error", "critical"]
+
+
+def print_valid_logs_levels() -> str:
+ return ", ".join(get_valid_logs_levels())
diff --git a/examples/facebook.aasm b/examples/basic/facebook.aasm
similarity index 93%
rename from examples/facebook.aasm
rename to examples/basic/facebook.aasm
index 6a3bc6d..1fc599e 100644
--- a/examples/facebook.aasm
+++ b/examples/basic/facebook.aasm
@@ -32,6 +32,7 @@ agent user
ebehav
eagent
-graph statistical
- defg user, 15, dist_exp, 0.1
+graph barabasi-albert
+ MPARAMS 5, 3
+ defg user, 150
egraph
diff --git a/examples/facebook_macro.aasm b/examples/basic/facebook_macro.aasm
similarity index 100%
rename from examples/facebook_macro.aasm
rename to examples/basic/facebook_macro.aasm
diff --git a/examples/wolfsheep.aasm b/examples/basic/wolfsheep.aasm
similarity index 100%
rename from examples/wolfsheep.aasm
rename to examples/basic/wolfsheep.aasm
diff --git a/examples/wolfsheep_macro.aasm b/examples/basic/wolfsheep_macro.aasm
similarity index 100%
rename from examples/wolfsheep_macro.aasm
rename to examples/basic/wolfsheep_macro.aasm
diff --git a/examples/graphs/barabasi-albert.aasm b/examples/graphs/barabasi-albert.aasm
new file mode 100644
index 0000000..76b42dc
--- /dev/null
+++ b/examples/graphs/barabasi-albert.aasm
@@ -0,0 +1,54 @@
+message facebook_post, query
+ prm photos, float
+emessage
+
+agent user
+ prm friends, list, conn
+ prm num_seen_photos, float, init, 0
+
+ behav initialize, setup
+ action initialize_friends, modify_self
+ decl max_friends, float, 0
+ len max_friends, connections
+ decl num_friends, float, 0
+ rand num_friends, int, uniform, 0, max_friends
+ subs friends, connections, num_friends
+ eaction
+ ebehav
+
+ behav facebook_activity, cyclic, 30
+ action post_photos, send_msg, facebook_post, query
+ decl num_photos, float, 0
+ rand num_photos, int, uniform, 21, 37
+ set send.photos, num_photos
+ send friends
+ eaction
+ ebehav
+
+ behav read_posts, msg_rcv, facebook_post, query
+ action update_seen_photos, modify_self
+ add num_seen_photos, rcv.photos
+ eaction
+ ebehav
+eagent
+
+agent null
+eagent
+
+agent media_source
+ behav facebook_activity, cyclic, 30
+ action new_post, send_msg, facebook_post, query
+ decl idx, float, 0
+ rand idx, int, uniform, 21, 37
+ set send.photos, idx
+ send connections
+ eaction
+ ebehav
+
+eagent
+
+graph barabasi-albert
+ MPARAMS 5,3
+ DEFG user, 50
+ DEFG media_source, 10
+egraph
diff --git a/examples/graphs/inhomogenous.aasm b/examples/graphs/inhomogenous.aasm
new file mode 100644
index 0000000..febeed1
--- /dev/null
+++ b/examples/graphs/inhomogenous.aasm
@@ -0,0 +1,55 @@
+message facebook_post, query
+ prm photos, float
+emessage
+
+agent user
+ prm friends, list, conn
+ prm num_seen_photos, float, init, 0
+
+ behav initialize, setup
+ action initialize_friends, modify_self
+ decl max_friends, float, 0
+ len max_friends, connections
+ decl num_friends, float, 0
+ rand num_friends, int, uniform, 0, max_friends
+ subs friends, connections, num_friends
+ eaction
+ ebehav
+
+ behav facebook_activity, cyclic, 30
+ action post_photos, send_msg, facebook_post, query
+ decl num_photos, float, 0
+ rand num_photos, int, uniform, 21, 37
+ set send.photos, num_photos
+ send friends
+ eaction
+ ebehav
+
+ behav read_posts, msg_rcv, facebook_post, query
+ action update_seen_photos, modify_self
+ add num_seen_photos, rcv.photos
+ eaction
+ ebehav
+eagent
+
+agent null
+eagent
+
+agent media_source
+ behav facebook_activity, cyclic, 30
+ action new_post, send_msg, facebook_post, query
+ decl idx, float, 0
+ rand idx, int, uniform, 21, 37
+ set send.photos, idx
+ send connections
+ eaction
+ ebehav
+
+eagent
+
+graph irg
+ TYPES 3
+ DEFTYPE user, 20, 15, 50, 70
+ DEFTYPE media_source, 2, 50, 10, 21
+ DEFTYPE null, 1, 80, 0, 0
+egraph
diff --git a/examples/graphs/matrix.aasm b/examples/graphs/matrix.aasm
new file mode 100644
index 0000000..78d5dd1
--- /dev/null
+++ b/examples/graphs/matrix.aasm
@@ -0,0 +1,57 @@
+message facebook_post, query
+ prm photos, float
+emessage
+
+agent user
+ prm friends, list, conn
+ prm num_seen_photos, float, init, 0
+
+ behav initialize, setup
+ action initialize_friends, modify_self
+ decl max_friends, float, 0
+ len max_friends, connections
+ decl num_friends, float, 0
+ rand num_friends, int, uniform, 0, max_friends
+ subs friends, connections, num_friends
+ eaction
+ ebehav
+
+ behav facebook_activity, cyclic, 30
+ action post_photos, send_msg, facebook_post, query
+ decl num_photos, float, 0
+ rand num_photos, int, uniform, 21, 37
+ set send.photos, num_photos
+ send friends
+ eaction
+ ebehav
+
+ behav read_posts, msg_rcv, facebook_post, query
+ action update_seen_photos, modify_self
+ add num_seen_photos, rcv.photos
+ eaction
+ ebehav
+eagent
+
+agent null
+eagent
+
+agent media_source
+ behav facebook_activity, cyclic, 30
+ action new_post, send_msg, facebook_post, query
+ decl idx, float, 0
+ rand idx, int, uniform, 21, 37
+ set send.photos, idx
+ send connections
+ eaction
+ ebehav
+
+eagent
+
+graph matrix
+ SCALE 3
+ DEFNODE user, R01111
+ DEFNODE user, R10111
+ DEFNODE user, R10011
+ DEFNODE media_source, R11100
+ DEFNODE null, R10000
+egraph
diff --git a/examples/graphs/statistical.aasm b/examples/graphs/statistical.aasm
new file mode 100644
index 0000000..1d47bf1
--- /dev/null
+++ b/examples/graphs/statistical.aasm
@@ -0,0 +1,54 @@
+message facebook_post, query
+ prm photos, float
+emessage
+
+agent user
+ prm friends, list, conn
+ prm num_seen_photos, float, init, 0
+
+ behav initialize, setup
+ action initialize_friends, modify_self
+ decl max_friends, float, 0
+ len max_friends, connections
+ decl num_friends, float, 0
+ rand num_friends, int, uniform, 0, max_friends
+ subs friends, connections, num_friends
+ eaction
+ ebehav
+
+ behav facebook_activity, cyclic, 30
+ action post_photos, send_msg, facebook_post, query
+ decl num_photos, float, 0
+ rand num_photos, int, uniform, 21, 37
+ set send.photos, num_photos
+ send friends
+ eaction
+ ebehav
+
+ behav read_posts, msg_rcv, facebook_post, query
+ action update_seen_photos, modify_self
+ add num_seen_photos, rcv.photos
+ eaction
+ ebehav
+eagent
+
+agent null
+eagent
+
+agent media_source
+ behav facebook_activity, cyclic, 30
+ action new_post, send_msg, facebook_post, query
+ decl idx, float, 0
+ rand idx, int, uniform, 21, 37
+ set send.photos, idx
+ send connections
+ eaction
+ ebehav
+
+eagent
+
+graph statistical
+ SIZE 150
+ DEFG user, 20, 10
+ DEFG media_source, 100%, dist_exp, 0.1
+egraph
diff --git a/examples/modules/facebook_uuid.aasm b/examples/modules/facebook_uuid.aasm
new file mode 100644
index 0000000..0351017
--- /dev/null
+++ b/examples/modules/facebook_uuid.aasm
@@ -0,0 +1,53 @@
+MODULE UUID
+
+message facebook_post, query
+ prm photos, float
+ prm msg_id, uuid
+emessage
+
+agent user
+ prm friends, list, conn
+ prm num_seen_photos, float, init, 0
+
+ behav initialize, setup
+ action initialize_friends, modify_self
+ decl max_friends, float, 0
+ len max_friends, connections
+ decl num_friends, float, 0
+ rand num_friends, int, uniform, 0, max_friends
+ subs friends, connections, num_friends
+ eaction
+ ebehav
+
+ behav facebook_activity, cyclic, 30
+ action post_photos, send_msg, facebook_post, query
+ decl num_photos, float, 0
+ DECL to_send_id, uuid, ""
+ rand num_photos, int, uniform, 21, 37
+ GETUUID to_send_id
+ set send.photos, num_photos
+ set send.msg_id, to_send_id
+ send friends
+ eaction
+ ebehav
+
+ behav read_posts, msg_rcv, facebook_post, query
+ action update_seen_photos, modify_self
+ add num_seen_photos, rcv.photos
+ DECL random_id, uuid, ""
+ GETUUID random_id
+ ISEQ rcv.msg_id, random_id
+ # this should never occur
+ CLR friends
+ EBLOCK
+ ISNEQ rcv.msg_id, random_id
+ add num_seen_photos, 1
+ EBLOCK
+ eaction
+ ebehav
+eagent
+
+graph barabasi-albert
+ MPARAMS 5, 3
+ defg user, 150
+egraph
diff --git a/examples/modules/uuid.aasm.mod b/examples/modules/uuid.aasm.mod
new file mode 100644
index 0000000..5ec84f1
--- /dev/null
+++ b/examples/modules/uuid.aasm.mod
@@ -0,0 +1,38 @@
+!name UUID
+
+!description
+Simple module to use uuids.
+Provides functionality to utilise uuids.
+targets: spade
+Introduces one type: uuid.
+Instructions:
+GETUUID will generate a new uuid and store it in the declared variable.
+ISEQ will compare two uuids and return true if they are equal.
+ISNEQ will compare two uuids and return true if they are not equal.
+
+!targets
+spade
+
+!types
+uuid
+
+!instructions
+GETUUID a: mut uuid
+ISEQ* a: uuid, b: uuid
+ISNEQ* a: uuid, b: uuid
+
+!preamble spade
+import uuid
+
+!impl GETUUID spade
+a = str(uuid.uuid4())
+
+!impl ISEQ spade
+if a == b:
+ return True
+return False
+
+!impl ISNEQ spade
+if a != b:
+ return True
+return False