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