add validator to restrict the flexibility of LConfig
[lunaix-os.git] / lunaix-os / scripts / build-tools / lcfg2 / builder.py
1 import ast
2 import textwrap
3
4 from lib.utils  import ConfigAST, ConfigASTVisitor
5 from .common     import NodeProperty, ConfigNodeError, ValueTypeConstrain
6 from .nodes      import GroupNode, TermNode
7 from .sanitiser  import TreeSanitiser
8
9 class NodeBuilder(ConfigASTVisitor):
10     def __init__(self, env):
11         super().__init__()
12
13         self.__env = env
14         self.__level = []
15         self.__noncfg_astns = []
16
17     def __pop_and_merge(self):
18         if len(self.__level) == 0:
19             return
20         
21         if len(self.__level) == 1:
22             self.__level.pop()
23             return
24         
25         node = self.__level.pop()
26         prev = self.__level[-1]
27
28         assert isinstance(prev, GroupNode)
29
30         prev.add_child(node)
31
32     def __push(self, cfg_node):
33         self.__level.append(cfg_node)
34
35     def __check_literal_expr(self, node):
36         return (
37             isinstance(node, ast.Expr) 
38             and isinstance(node.value, ast.Constant) 
39             and isinstance(node.value.value, str)
40         )
41
42     def _visit_fndef(self, node):
43         if hasattr(node, "__builtin"):
44             self.__noncfg_astns.append(node)
45             return
46         
47         cfgn_type = TermNode
48         if not node.returns:
49             cfgn_type = GroupNode
50
51         cfgn = cfgn_type(self.__env, self.current_file(), node.name)
52         
53         self.__push(cfgn)
54
55         try:
56             for decor in node.decorator_list:
57                 cfgn.apply_decorator(decor)
58             
59             astns = []
60             help_text = ""
61             for sub in node.body:
62                 if isinstance(sub, ast.FunctionDef):
63                     self._visit_fndef(sub)
64                     continue
65
66                 if self.__check_literal_expr(sub):
67                     help_text += sub.value.value
68                     continue
69
70                 astns.append(sub)
71
72             NodeProperty.Token[cfgn] = node
73             NodeProperty.HelpText[cfgn] = textwrap.dedent(help_text)
74             
75             if cfgn_type is TermNode:
76                 NodeProperty.Type[cfgn] = ValueTypeConstrain(cfgn, node.returns)
77             
78             astns.append(ast.Pass())
79             cfgn.set_node_body(astns)
80             
81             self.__env.register_node(cfgn)
82         
83         except ConfigNodeError as e:
84             raise e
85         except Exception as e:
86             msg = e.args[0] if len(e.args) > 0 else type(e).__name__
87             raise cfgn.config_error(msg)
88
89         self.__pop_and_merge()
90
91     def visit(self, node):
92         super().visit(node)
93
94         if isinstance(node, ast.Module):
95             return
96
97         if not isinstance(node, ast.FunctionDef):
98             self.__noncfg_astns.append(node)
99
100     @staticmethod
101     def build(env, rootfile):
102         build = NodeBuilder(env)
103         ast = ConfigAST(rootfile)
104         
105         env.reset()
106         ast.visit(TreeSanitiser())
107         ast.visit(build)
108
109         env.set_exec_context(build.__noncfg_astns)
110         env.relocate_children()