Rewrite the lunabuild toolchain with enhanced feature (#60)
[lunaix-os.git] / lunaix-os / scripts / build-tools / lcfg2 / builder.py
diff --git a/lunaix-os/scripts/build-tools/lcfg2/builder.py b/lunaix-os/scripts/build-tools/lcfg2/builder.py
new file mode 100644 (file)
index 0000000..832807a
--- /dev/null
@@ -0,0 +1,118 @@
+import ast
+import textwrap
+import os
+
+from lib.utils  import ConfigAST, ConfigASTVisitor
+from .common     import NodeProperty, ConfigNodeError, ValueTypeConstrain
+from .nodes      import GroupNode, TermNode
+from .sanitiser  import TreeSanitiser
+
+class NodeBuilder(ConfigASTVisitor):
+    def __init__(self, env):
+        super().__init__()
+
+        self.__env = env
+        self.__level = []
+        self.__noncfg_astns = []
+
+    def __pop_and_merge(self):
+        if len(self.__level) == 0:
+            return
+        
+        if len(self.__level) == 1:
+            self.__level.pop()
+            return
+        
+        node = self.__level.pop()
+        prev = self.__level[-1]
+
+        assert isinstance(prev, GroupNode)
+
+        prev.add_child(node)
+
+    def __push(self, cfg_node):
+        self.__level.append(cfg_node)
+
+    def __check_literal_expr(self, node):
+        return (
+            isinstance(node, ast.Expr) 
+            and isinstance(node.value, ast.Constant) 
+            and isinstance(node.value.value, str)
+        )
+
+    def _visit_fndef(self, node):
+        if hasattr(node, "__builtin"):
+            self.__noncfg_astns.append(node)
+            return
+        
+        cfgn_type = TermNode
+        if not node.returns:
+            cfgn_type = GroupNode
+
+        cfgn = cfgn_type(self.__env, self.current_file(), node.name)
+        
+        self.__push(cfgn)
+
+        try:
+            for decor in node.decorator_list:
+                cfgn.apply_decorator(decor)
+            
+            astns = []
+            help_text = ""
+            for sub in node.body:
+                if isinstance(sub, ast.FunctionDef):
+                    self._visit_fndef(sub)
+                    continue
+
+                if self.__check_literal_expr(sub):
+                    help_text += sub.value.value
+                    continue
+
+                astns.append(sub)
+
+            NodeProperty.Token[cfgn] = node
+            NodeProperty.HelpText[cfgn] = textwrap.dedent(help_text)
+            
+            if cfgn_type is TermNode:
+                NodeProperty.Type[cfgn] = ValueTypeConstrain(cfgn, node.returns)
+            
+            astns.append(ast.Pass())
+            cfgn.set_node_body(astns)
+            
+            self.__env.register_node(cfgn)
+        
+        except ConfigNodeError as e:
+            raise e
+        except Exception as e:
+            msg = e.args[0] if len(e.args) > 0 else type(e).__name__
+            raise cfgn.config_error(msg)
+
+        self.__pop_and_merge()
+
+    def visit(self, node):
+        super().visit(node)
+
+        if isinstance(node, ast.Module):
+            return
+
+        if not isinstance(node, ast.FunctionDef):
+            self.__noncfg_astns.append(node)
+
+    @staticmethod
+    def build(env, rootfile):
+        if not os.path.exists(rootfile):
+            print(f"warning: config file '{rootfile}' not detected, skipped")
+            return
+
+        build = NodeBuilder(env)
+        ast = ConfigAST(rootfile)
+        
+        env.reset()
+        ast.visit(TreeSanitiser())
+        ast.visit(build)
+
+        for node in env.nodes():
+            node.apply_node_body()
+
+        env.set_exec_context(build.__noncfg_astns)
+        env.relocate_children()
\ No newline at end of file