Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ coverage.xml
site/

# ignore log files
temp/
temp/

pfdl_scheduler/plugins/parser/**
5 changes: 4 additions & 1 deletion pfdl_grammar/PFDLLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def nextToken(self):
return self.denter.next_token()
}

// The comment below allows the PFDL plugin system to insert the lexer rules at this point
// This ensures that newly added rules are not skipped for rules below like 'STARTS_WITH_LOWER_C_STR'
// {Plugin_Insertion_Point}

// Main grammar
STRUCT: 'Struct';
TASK: 'Task';
Expand All @@ -41,7 +45,6 @@ PARALLEL: 'Parallel';
CONDITION: 'Condition';
PASSED: 'Passed';
FAILED: 'Failed';
ON_DONE: 'OnDone';
END: 'End';
NUMBER_P: 'number';
STRING_P: 'string';
Expand Down
24 changes: 21 additions & 3 deletions pfdl_grammar/PFDLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,35 @@ options {
}

program:
(NL | struct | task)* EOF;
program_statement* EOF;

program_statement:
NL | struct | task | instance;

struct:
STRUCT STARTS_WITH_UPPER_C_STR INDENT (variable_definition NL+)+ DEDENT END;
STRUCT STARTS_WITH_UPPER_C_STR (COLON struct_id)? INDENT (
variable_definition NL+
)+ DEDENT END;

struct_id: STARTS_WITH_UPPER_C_STR;

task:
TASK STARTS_WITH_LOWER_C_STR INDENT task_in? statement+ task_out? DEDENT END;
TASK STARTS_WITH_LOWER_C_STR INDENT task_in? taskStatement+ task_out? DEDENT END;

instance:
struct_id STARTS_WITH_LOWER_C_STR INDENT (
attribute_assignment NL
)+ DEDENT END;

task_in:
IN INDENT (variable_definition NL+)+ DEDENT;

task_out:
OUT INDENT (STARTS_WITH_LOWER_C_STR NL+)+ DEDENT;

taskStatement:
statement;

statement:
service_call
| task_call
Expand Down Expand Up @@ -80,6 +95,9 @@ primitive:
attribute_access:
STARTS_WITH_LOWER_C_STR (DOT STARTS_WITH_LOWER_C_STR array?)+;

attribute_assignment:
STARTS_WITH_LOWER_C_STR COLON (value | json_object);

array:
ARRAY_LEFT (INTEGER | STARTS_WITH_LOWER_C_STR)? ARRAY_RIGHT;

Expand Down
2 changes: 1 addition & 1 deletion pfdl_scheduler/model/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __radd__(self, other) -> str:
return other + str(self)

def __eq__(self, __o: object) -> bool:
if isinstance(__o, Array):
if hasattr(__o, "values") and hasattr(__o, "length") and hasattr(__o, "type_of_elements"):
return (
self.values == __o.values
and self.length == __o.length
Expand Down
118 changes: 118 additions & 0 deletions pfdl_scheduler/model/instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright The PFDL Contributors
#
# Licensed under the MIT License.
# For details on the licensing terms, see the LICENSE file.
# SPDX-License-Identifier: MIT

"""Contains Instance class."""

# standard libraries
import copy
from numbers import Number
from typing import Dict, Union

# 3rd party libs
from antlr4.ParserRuleContext import ParserRuleContext

# local sources
## PFDL base sources
from pfdl_scheduler.model.array import Array
from pfdl_scheduler.pfdl_base_classes import PFDLBaseClasses
from pfdl_scheduler.validation.error_handler import ErrorHandler


class Instance:
"""Represents an Instance in the PFDL.

Attributes:
name: A string representing the name of the Instance.
attributes: A dict mapping the attribute names with their values.
struct_name: A string refering to the Struct this Instance instanciates.
context: ANTLR context object of this class.
attribute_contexts: A dict that maps the attribute names to their ANTLR contexts.
"""

def __init__(
self,
name: str = "",
attributes: Dict[str, Union[str, Number, bool, "Instance"]] = None,
struct_name: str = "",
context: ParserRuleContext = None,
) -> None:
"""Initialize the object.

Args:
name: A string representing the name of the Instance.
attributes: A dict mapping the attribute names with their values.
struct_name: A string refering to the Struct this Instance instanciates.
context: ANTLR context object of this class.
"""
self.name: str = name

if attributes:
self.attributes: Dict[str, Union[str, Number, bool, "Instance"]] = attributes
else:
self.attributes: Dict[str, Union[str, Number, bool, "Instance"]] = {}

self.struct_name: str = struct_name
self.context: ParserRuleContext = context
self.attribute_contexts: Dict = {}

def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for attr, value in self.__dict__.items():
try:
setattr(result, attr, copy.deepcopy(value, memo))
except Exception:
setattr(result, attr, value)
return result

@classmethod
def from_json(
cls,
json_object: Dict,
error_handler: ErrorHandler,
struct_context: ParserRuleContext,
pfdl_base_classes=PFDLBaseClasses,
):
return parse_json(json_object, error_handler, struct_context, pfdl_base_classes)


def parse_json(
json_object: Dict,
error_handler: ErrorHandler,
instance_context: ParserRuleContext,
pfdl_base_classes=PFDLBaseClasses,
) -> Instance:
"""Parses the JSON Struct initialization.

Returns:
An Instance object representing the initialized instance.
"""
instance = pfdl_base_classes.get_class("Instance")()
instance.context = instance_context
for identifier, value in json_object.items():
if isinstance(value, (int, str, bool)):
instance.attributes[identifier] = value
elif isinstance(value, list):
array = pfdl_base_classes.get_class("Array")()
instance.attributes[identifier] = array
for element in value:
if isinstance(element, (int, float, str, bool)):
if isinstance(element, bool):
array.type_of_elements = "boolean"
elif isinstance(element, (int, float)):
array.type_of_elements = "number"
else:
array.type_of_elements = "string"
array.append_value(element)
elif isinstance(element, dict):
inner_struct = parse_json(element, error_handler)
array.append_value(inner_struct)
elif isinstance(value, dict):
inner_struct = parse_json(value, error_handler, instance_context, pfdl_base_classes)
instance.attributes[identifier] = inner_struct

return instance
8 changes: 8 additions & 0 deletions pfdl_scheduler/model/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Dict

# local sources
from pfdl_scheduler.model.instance import Instance
from pfdl_scheduler.model.struct import Struct
from pfdl_scheduler.model.task import Task

Expand All @@ -25,20 +26,23 @@ class Process:
Attributes:
structs: A dict for mapping the Struct names to the Struct objects.
task: A dict for mapping the Task names to the Task objects.
instances: A dict for mappign the Instance names to the Instance objects.
start_task_name: the name of the start task of the PFDL program (typically "productionTask").
"""

def __init__(
self,
structs: Dict[str, Struct] = None,
tasks: Dict[str, Task] = None,
instances: Dict[str, Instance] = None,
start_task_name: str = "productionTask",
) -> None:
"""Initialize the object.

Args:
structs: A dict for mapping the Struct names to the Struct objects.
tasks: A dict for mapping the Task names to the Task objects.
instances: A dict for mappign the Instance names to the Instance objects.
start_task_name: the name of the start task of the PFDL program (typically "productionTask").
"""
if structs:
Expand All @@ -49,4 +53,8 @@ def __init__(
self.tasks: Dict[str, Task] = tasks
else:
self.tasks: Dict[str, Task] = {}
if instances:
self.instances: Dict[str, Task] = instances
else:
self.instances: Dict[str, Task] = {}
self.start_task_name = start_task_name
8 changes: 4 additions & 4 deletions pfdl_scheduler/model/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
from antlr4.ParserRuleContext import ParserRuleContext

# local sources
from pfdl_scheduler.model.instance import Instance
from pfdl_scheduler.model.struct import Struct
from pfdl_scheduler.model.array import Array


@dataclass
class Service:
"""Represents a Service or Service Call in the PFDL.

Expand All @@ -36,7 +36,7 @@ class Service:
def __init__(
self,
name: str = "",
input_parameters: List[Union[str, List[str], Struct]] = None,
input_parameters: List[Union[str, List[str], Instance]] = None,
output_parameters: Dict[str, Union[str, Array]] = None,
context: ParserRuleContext = None,
) -> None:
Expand All @@ -51,9 +51,9 @@ def __init__(
self.name: str = name

if input_parameters:
self.input_parameters: List[Union[str, List[str], Struct]] = input_parameters
self.input_parameters: List[Union[str, List[str], Instance]] = input_parameters
else:
self.input_parameters: List[Union[str, List[str], Struct]] = []
self.input_parameters: List[Union[str, List[str], Instance]] = []

if output_parameters:
self.output_parameters: OrderedDict[str, Union[str, Array]] = output_parameters
Expand Down
35 changes: 27 additions & 8 deletions pfdl_scheduler/model/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# standard libraries
import copy
from dataclasses import dataclass
from typing import Dict, Union
from typing import Dict, Type, Union
import json

# 3rd party libraries
Expand All @@ -31,6 +31,8 @@ class Struct:
name: A string representing the name of the Struct.
attributes: A dict which maps the attribute names to the defined type
or a value (if its a instantiated struct).
parent_struct_name: A string representin the identifier of the parent struct
from which this struct inherits the attributes
context: ANTLR context object of this class.
context_dict: Maps other attributes with ANTLR context objects.
"""
Expand All @@ -39,6 +41,7 @@ def __init__(
self,
name: str = "",
attributes: Dict[str, Union[str, Array, "Struct"]] = None,
parent_struct_name: str = "",
context: ParserRuleContext = None,
) -> None:
"""Initialize the object.
Expand All @@ -47,18 +50,26 @@ def __init__(
name: A string representing the name of the Struct.
attributes: A dict which maps the attribute names to the defined type
or a value (if its a instantiated struct).
parent_struct_name: A string representin the identifier of the parent struct
from which this struct inherits the attributes
context: ANTLR context object of this class.
"""
self.name: str = name
if attributes:
self.attributes: Dict[str, Union[str, Array, "Struct"]] = attributes
else:
self.attributes: Dict[str, Union[str, Array, "Struct"]] = {}
self.parent_struct_name: str = parent_struct_name
self.context: ParserRuleContext = context
self.context_dict: Dict = {}

def __eq__(self, __o: object) -> bool:
if isinstance(__o, Struct):
if (
hasattr(__o, "name")
and hasattr(__o, "attributes")
and hasattr(__o, "context")
and hasattr(__o, "context_dict")
):
return (
self.name == __o.name
and self.attributes == __o.attributes
Expand All @@ -80,7 +91,11 @@ def __deepcopy__(self, memo):

@classmethod
def from_json(
cls, json_string: str, error_handler: ErrorHandler, struct_context: ParserRuleContext
cls,
json_string: str,
error_handler: ErrorHandler,
struct_context: ParserRuleContext,
struct_class: Type,
) -> "Struct":
"""Creates a Struct instance out of the given JSON string.

Expand All @@ -92,24 +107,28 @@ def from_json(
The Struct which was created from the JSON string.
"""
json_object = json.loads(json_string)
struct = parse_json(json_object, error_handler, struct_context)
struct = parse_json(json_object, error_handler, struct_context, struct_class)
return struct


def parse_json(
json_object: Dict, error_handler: ErrorHandler, struct_context: ParserRuleContext
json_object: Dict,
error_handler: ErrorHandler,
struct_context: ParserRuleContext,
struct_class: Type,
) -> Struct:
"""Parses the JSON Struct initialization.

Args:
json_object: A JSON object describing the Struct.
error_handler: An ErrorHandler instance used for printing errors.
struct_context: The ANTLR struct context the struct corresponds to.
struct_class: The class of the struct from which the struct object is created.

Returns:
A Struct object representing the initialized Struct.
"""
struct = Struct()
struct = struct_class()
struct.context = struct_context

for identifier, value in json_object.items():
Expand All @@ -129,9 +148,9 @@ def parse_json(
array.type_of_elements = "string"
array.append_value(element)
elif isinstance(element, dict):
inner_struct = parse_json(element, error_handler, struct_context)
inner_struct = parse_json(element, error_handler, struct_context, struct_class)
array.append_value(inner_struct)
elif isinstance(value, dict):
inner_struct = parse_json(value, error_handler, struct_context)
inner_struct = parse_json(value, error_handler, struct_context, struct_class)
struct.attributes[identifier] = inner_struct
return struct
Loading
Loading