quizcomp.parser.ast

  1import typing
  2
  3import edq.util.serial
  4import markdown_it.token
  5import markdown_it.tree
  6
  7import quizcomp.parser.common
  8
  9# Pull specific attributes from nodes of these specified types.
 10AST_NODE_ATTRIBUTES: typing.Dict[str, typing.List[str]] = {
 11    'code_block': [
 12        'info',
 13    ],
 14    'fence': [
 15        'info',
 16    ],
 17    'heading': [
 18        'tag',
 19    ],
 20}
 21
 22# Pull these attributes out of these specific token types when building the AST.
 23AST_TOKEN_ATTRS: typing.Dict[str, typing.List[str]] = {
 24    'image': [
 25        'src',
 26    ],
 27    'link': [
 28        'href',
 29    ],
 30    'td': [
 31        'style',
 32    ],
 33    'th': [
 34        'style',
 35    ],
 36}
 37
 38# Like AST_TOKEN_ATTRS, but for the `meta` property.
 39AST_TOKEN_METAS: typing.Dict[str, typing.List[str]] = {
 40    'container_block': [
 41        quizcomp.parser.common.TOKEN_META_KEY_ROOT,
 42        quizcomp.parser.common.TOKEN_META_KEY_STYLE,
 43    ]
 44}
 45
 46class ASTNode(edq.util.serial.PODSerializer):
 47    """ A simple representation for an AST node. """
 48
 49    serialization_omit_none = True
 50    serialization_omit_empty = True
 51
 52    def __init__(self,
 53            type: typing.Union[str, None] = None,
 54            children: typing.Union[typing.List['ASTNode'], None] = None,
 55            text: str = '',
 56            **kwargs: typing.Any,
 57            ) -> None:
 58        if (type is None):
 59            raise ValueError("AST nodes cannot have a missing type.")
 60
 61        self.type: str = type
 62        """ The type of AST node. """
 63
 64        if (children is None):
 65            children = []
 66
 67        self.children: typing.List['ASTNode'] = children
 68        """ The children of this node. """
 69
 70        self.text: str = text
 71        """
 72        The text represented by this node (but not its children).
 73        If these is not text, this will be an empty string.
 74        """
 75
 76        self.attributes: typing.Dict[str, typing.Any] = kwargs
 77        """ Additional attributes attached to this node. """
 78
 79    def get(self, key: str, default_value: typing.Any) -> typing.Any:
 80        """ Get an attribute of this node. """
 81
 82        return self.attributes.get(key, default_value)
 83
 84def build(tokens: typing.Sequence[markdown_it.token.Token]) -> ASTNode:
 85    """ Build an AST from a stream of tokens. """
 86
 87    tree = markdown_it.tree.SyntaxTreeNode(tokens)
 88    return _walk_ast(tree)
 89
 90def _walk_ast(node: markdown_it.tree.SyntaxTreeNode) -> ASTNode:
 91    data: typing.Dict[str, typing.Any] = {
 92        'type': node.type,
 93    }
 94
 95    if (node.type in quizcomp.parser.common.CONTENT_NODES):
 96        data['text'] = node.content
 97
 98    for name in AST_NODE_ATTRIBUTES.get(node.type, []):
 99        value = getattr(node, name)
100        if ((value is not None) and (value != '')):
101            data[name] = value
102
103    for name in AST_TOKEN_ATTRS.get(node.type, []):
104        value = node.attrGet(name)
105        if (value is not None):
106            data[name] = value
107
108    for name in AST_TOKEN_METAS.get(node.type, []):
109        value = node.meta.get(name, None)
110        if (value is not None):
111            data[name] = value
112
113    if (len(node.children) > 0):
114        data['children'] = [_walk_ast(child) for child in node.children]
115
116    return ASTNode(**data)
AST_NODE_ATTRIBUTES: Dict[str, List[str]] = {'code_block': ['info'], 'fence': ['info'], 'heading': ['tag']}
AST_TOKEN_ATTRS: Dict[str, List[str]] = {'image': ['src'], 'link': ['href'], 'td': ['style'], 'th': ['style']}
AST_TOKEN_METAS: Dict[str, List[str]] = {'container_block': ['qg_root', 'qg_style']}
class ASTNode(edq.util.serial.PODSerializer):
47class ASTNode(edq.util.serial.PODSerializer):
48    """ A simple representation for an AST node. """
49
50    serialization_omit_none = True
51    serialization_omit_empty = True
52
53    def __init__(self,
54            type: typing.Union[str, None] = None,
55            children: typing.Union[typing.List['ASTNode'], None] = None,
56            text: str = '',
57            **kwargs: typing.Any,
58            ) -> None:
59        if (type is None):
60            raise ValueError("AST nodes cannot have a missing type.")
61
62        self.type: str = type
63        """ The type of AST node. """
64
65        if (children is None):
66            children = []
67
68        self.children: typing.List['ASTNode'] = children
69        """ The children of this node. """
70
71        self.text: str = text
72        """
73        The text represented by this node (but not its children).
74        If these is not text, this will be an empty string.
75        """
76
77        self.attributes: typing.Dict[str, typing.Any] = kwargs
78        """ Additional attributes attached to this node. """
79
80    def get(self, key: str, default_value: typing.Any) -> typing.Any:
81        """ Get an attribute of this node. """
82
83        return self.attributes.get(key, default_value)

A simple representation for an AST node.

ASTNode( type: Optional[str] = None, children: Optional[List[ASTNode]] = None, text: str = '', **kwargs: Any)
53    def __init__(self,
54            type: typing.Union[str, None] = None,
55            children: typing.Union[typing.List['ASTNode'], None] = None,
56            text: str = '',
57            **kwargs: typing.Any,
58            ) -> None:
59        if (type is None):
60            raise ValueError("AST nodes cannot have a missing type.")
61
62        self.type: str = type
63        """ The type of AST node. """
64
65        if (children is None):
66            children = []
67
68        self.children: typing.List['ASTNode'] = children
69        """ The children of this node. """
70
71        self.text: str = text
72        """
73        The text represented by this node (but not its children).
74        If these is not text, this will be an empty string.
75        """
76
77        self.attributes: typing.Dict[str, typing.Any] = kwargs
78        """ Additional attributes attached to this node. """
serialization_omit_none = True

Do not include None (null) fields in serialization.

serialization_omit_empty = True

Do not include empty fields in serialization. An empty field meets one of the following conditions:

  • Has a __len__ method which returns 0.
  • Has a _serialization_is_empty method that returns true.
type: str

The type of AST node.

children: List[ASTNode]

The children of this node.

text: str

The text represented by this node (but not its children). If these is not text, this will be an empty string.

attributes: Dict[str, Any]

Additional attributes attached to this node.

def get(self, key: str, default_value: Any) -> Any:
80    def get(self, key: str, default_value: typing.Any) -> typing.Any:
81        """ Get an attribute of this node. """
82
83        return self.attributes.get(key, default_value)

Get an attribute of this node.

def build(tokens: Sequence[markdown_it.token.Token]) -> ASTNode:
85def build(tokens: typing.Sequence[markdown_it.token.Token]) -> ASTNode:
86    """ Build an AST from a stream of tokens. """
87
88    tree = markdown_it.tree.SyntaxTreeNode(tokens)
89    return _walk_ast(tree)

Build an AST from a stream of tokens.