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_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_emptymethod that returns true.
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.