shconfig: add `link` command to view the inverse dependency
authorLunaixsky <lunaixsky@qq.com>
Sat, 10 May 2025 19:18:39 +0000 (20:18 +0100)
committerLunaixsky <lunaixsky@qq.com>
Sat, 10 May 2025 19:18:39 +0000 (20:18 +0100)
* fix some typos

lunaix-os/arch/LConfig
lunaix-os/hal/LBuild
lunaix-os/hal/LConfig
lunaix-os/hal/char/LConfig
lunaix-os/scripts/build-tools/README.lconfig.md
lunaix-os/scripts/build-tools/lcfg2/builder.py
lunaix-os/scripts/build-tools/lcfg2/common.py
lunaix-os/scripts/build-tools/lcfg2/nodes.py
lunaix-os/scripts/build-tools/lcfg2/rewriter.py
lunaix-os/scripts/build-tools/lcfg2/rules.py
lunaix-os/scripts/build-tools/shared/shconfig/commands.py

index bb22a10bc6e42b247e872c263373c8a97855494b..a78038fd947a8e6a2e2790e75dbe460def55c59d 100644 (file)
@@ -24,8 +24,19 @@ def architecture_support():
         """ 
             Config ISA support 
         """
-        _arch = env("ARCH")
-        return _arch if _arch else "x86_64"
+
+        match env("ARCH"):
+            case "i386": 
+                return "i386"
+            case "aarch64": 
+                return "aarch64"
+            case "rv64": 
+                return "rv64"
+            case "x86_64": 
+                return "x86_64"
+
+        print("unknown ARCH:", env("ARCH"), "default to x86_64")
+        return "x86_64"
 
     @"Base operand size"
     @readonly
index 5a50d0ef0da53d202632244ea86f8f2804554981..d0475eef8bf70bf2092d04fa973fddc5d555e958 100644 (file)
@@ -1,6 +1,9 @@
-from . import acpi, ahci, char, gfxa, rtc, term, timer, bus
+from . import acpi, ahci, char, rtc, term, timer, bus
 
 if config.use_devicetree:
     from . import devtree
 
+if config.use_graphic_dev:
+    from . import gfxa
+
 src.c += "irq.c"
\ No newline at end of file
index ab7e99e5ac91265a7ae4bc9d6ab98f7211d2df3c..5b6a86f72b52c7bcc969b5641bae967009fa0109 100644 (file)
@@ -28,4 +28,11 @@ def hal():
         """
         require (use_devicetree)
 
-        return 256
\ No newline at end of file
+        return 256
+    
+    @"Graphic Devices"
+    def use_graphic_dev() -> int:
+        """
+            Support of graphical devices and display protocol
+        """
+        return False
\ No newline at end of file
index b4ecff10b63acb187cb0b07b4209c7df1f9b775b..dd2fa0f0c498026e8f8a7737e91c573a72dcbd38 100644 (file)
@@ -7,8 +7,11 @@ def char_device():
 
     @"VGA 80x25 text-mode console"
     def vga_console() -> bool:
-        """ Enable VGA console device (text mode only) """
-
+        """ 
+            Enable VGA console device (text mode only) 
+        """
+        require (use_graphic_dev)
+        
         return True
 
     @"VGA character game device"
@@ -18,5 +21,6 @@ def char_device():
 
             You normally don't need to include this, unless you want some user space fun ;)
         """
+        require (vga_console)
 
         return False
\ No newline at end of file
index 05fb7dbf1423e564d68c769fcdbe8aee900deb4e..457db7250f18183b17ed1c580c535de0636a7379 100644 (file)
@@ -59,7 +59,7 @@ def group1():
 Multiple `LConfig`s may be defined across different sub-directory in large scale
 project for better maintainability
 
-LunaConfig allow you to import content of other LBuild using python's relative import
+LunaConfig allow you to import content of other LConfig using python's relative import
 feature:
 
 ```py
@@ -67,7 +67,7 @@ from . import module
 ```
 
 This import mechanism works like `#include` directive in C preprocessor,
-the `from . import` construct will automatically intercepted by the LBuild interpreter and 
+the `from . import` construct will automatically intercepted by the LConfig interpreter and 
 be replaced with the content from `./module/LConfig`
 
 You can also address file in the deeper hierarchy of the directory tree, for example
@@ -294,7 +294,7 @@ If a dependency can not be satisfied, then the feature is disabled. This will ca
 
 Note that the dependency check only perform on the enablement of the node but not visibility.
 
-### Auto Toggling
+### Auto Toggling (Inverse Dependency)
 
 > usage: `Terms` with `bool` value type
 
@@ -323,7 +323,7 @@ For configuration language being a python superset will have a risk of abusing d
 
 For prevention of this potential drawback, LunaConfig implemented a syntactical validator to identify these possible bad-practice and issue warning (or rise a fatal-error depending on the user setting).
 
-Currently, LunaConfig detect the misuses based on these rule rules:
+Currently, LunaConfig detect the misuses based on these rules:
 
 + `dynamic-logic`: The presence of conditional branching that could lead to complex logic. However, pattern matching is allowed.
 + `while-loop`, `for-loop`: The presence of any loop structure.
index 40fabfb0d74e48e4fa3d6b4172ba452936fbc721..235126c2d3d10387bfe5f1e442b800386dad43c6 100644 (file)
@@ -106,5 +106,8 @@ class NodeBuilder(ConfigASTVisitor):
         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
index 94cdcea201bfb4a2ccd8c4b91229fdf1d64fdfd2..d968e671a44dad098ce1fbcf65115f3591621138 100644 (file)
@@ -19,6 +19,7 @@ class NodeProperty:
     Enabled     = PropertyAccessor("$enabled")
     Status      = PropertyAccessor("$status")
     Dependency  = PropertyAccessor("$depends")
+    Linkage     = PropertyAccessor("$linkage")
     Hidden      = PropertyAccessor("hidden")
     Parent      = PropertyAccessor("parent")
     Label       = PropertyAccessor("label")
@@ -132,4 +133,18 @@ class NodeDependency:
             return
         
         dep = NodeDependency(node, expr)
-        NodeProperty.Dependency[node] = dep
\ No newline at end of file
+        NodeProperty.Dependency[node] = dep
+
+
+class NodeInverseDependency:
+    def __init__(self):
+        self.__map = {}
+
+    def add_linkage(self, name, expr):
+        if name not in self.__map:
+            self.__map[name] = [expr]
+        else:
+            self.__map[name].append(expr)
+
+    def linkages(self):
+        return self.__map.items()
\ No newline at end of file
index 19ead504bb9cf91402f4be5c4295fdbcc35ac1ce..3b423949d006859b17c2108d2595738603a2f028 100644 (file)
@@ -1,6 +1,6 @@
 import ast
 
-from lib.utils  import SourceLogger, Schema
+from lib.utils   import SourceLogger, Schema
 from .common     import NodeProperty, ConfigNodeError, NodeDependency
 from .lazy       import LazyLookup
 from .rewriter   import ConfigNodeASTRewriter
@@ -40,8 +40,13 @@ class ConfigNode:
         NodeProperty.Readonly[self] = False
         NodeProperty.Status[self]   = "Empty"
 
-    def set_node_body(self, ast_nodes, rewriter = ConfigNodeASTRewriter):
-        new_ast = ast.Module(ast_nodes, [])
+    def set_node_body(self, ast_nodes):
+        self.__exec = ast.Module(ast_nodes, [])
+        
+    def apply_node_body(self, rewriter = ConfigNodeASTRewriter):
+        assert isinstance(self.__exec, ast.Module)
+
+        new_ast = self.__exec
         validator.validate(self, new_ast)
         
         new_ast = rewriter(self).rewrite(new_ast)
index 4134bce7f62447ef155ea993257aba1c364ab185..a86ab52852079cbe5f801f9f7bde8f2363350156 100644 (file)
@@ -2,7 +2,7 @@ import ast
 
 from lib.utils  import Schema
 from .lazy       import Lazy
-from .common     import NodeProperty
+from .common     import NodeProperty, NodeInverseDependency
 
 class RewriteRule:
     MaybeBuiltin = Schema(
@@ -33,6 +33,16 @@ class ConfigNodeASTRewriter(ast.NodeTransformer):
 
         self.__when_epxr = None
 
+    def __put_linkage(self, to_node, item):
+        node = self.__cfg_node._env.get_node(to_node)
+        link = NodeProperty.Linkage[node]
+        
+        if not link:
+            link = NodeInverseDependency()
+            NodeProperty.Linkage[node] = link
+        
+        link.add_linkage(self.__cfg_node._name, ast.unparse(item))
+
     def __subscript_accessor(self, name, ctx, token):
         return ast.Subscript(
             value=ast.Name("__lzLut__", ctx=ast.Load()),
@@ -67,7 +77,7 @@ class ConfigNodeASTRewriter(ast.NodeTransformer):
 
         if RewriteRule.WhenToggler != node:
             raise cfgn.config_error(
-                f"invalid when(...) expression: {ast.unparse(node)}")
+                f"when(...): invalid expression: {ast.unparse(node)}")
 
         if RewriteRule.WhenTogglerItem == node:
             and_list.append(node)
@@ -77,10 +87,16 @@ class ConfigNodeASTRewriter(ast.NodeTransformer):
         for i in range(len(and_list)):
             item = and_list[i]
             operator = item.ops[0]
+
+            if RewriteRule.WhenTogglerItem != item:
+                raise cfgn.config_error(
+                        f"when(...): non-trivial subclause : {ast.unparse(node)}")
             
             name = Lazy.from_type(cfgn, Lazy.NodeValue, item.left.id)
             acc = self.__subscript_accessor(name, ast.Load(), node)
             
+            self.__put_linkage(item.left.id, item)
+
             if isinstance(operator, ast.Is):
                 operator = ast.Eq()
             else:
@@ -122,7 +138,7 @@ class ConfigNodeASTRewriter(ast.NodeTransformer):
         return None
     
     def visit_Return(self, node):
-        if NodeProperty.WhenToggle[self.__cfg_node]:
+        if self.__when_epxr:
             return None
         return self.generic_visit(node)
     
index 0297b554c874139be8d072eaa86db8bcb33eedb6..3bf5683a1baf5c11b1a67b69ab7c74a6ea4ed6db 100644 (file)
@@ -32,8 +32,6 @@ class SyntaxRule(RuleCollection):
     
     TrivialReturn  = Schema(Schema.Union(
         TrivialValue,
-        TrivialTest,
-        InlineIf,
         ast.JoinedStr
     ))
 
index 8c4e6706cdc72616e927583c14d70008d96b1213..825ffa92c5ddb49f70d927febd4322f543e2e4ba 100644 (file)
@@ -20,7 +20,7 @@ class Commands(CmdTable):
             raise ShconfigException(f"no such config: {name}")
         return node
     
-    def __get_opt_line(self, node):
+    def __get_opt_line(self, node, color_hint = False):
         aligned = 40
         name    = f"CONFIG_{node._name.upper()}"
         value   = NodeProperty.Value[node]
@@ -36,7 +36,11 @@ class Commands(CmdTable):
                 
         line = f"[{status}] {name}"
         to_pad = max(aligned - len(line), 4)
-        return f"{line} {'.' * to_pad} {val_txt}"
+        line = f"{line} {'.' * to_pad} {val_txt}"
+
+        if color_hint and not enabled:
+            line = f"\x1b[90;49m{line}\x1b[0m"
+        return line
     
     @cmd("help", "h")
     def __fn_help(self):
@@ -70,7 +74,7 @@ class Commands(CmdTable):
         ]
 
         for node in self.__env.terms():
-            lines.append(self.__get_opt_line(node))    
+            lines.append(self.__get_opt_line(node, True))    
 
         pydoc.pager("\n".join(lines))
 
@@ -99,21 +103,22 @@ class Commands(CmdTable):
         Show the dependency chain and boolean conditionals
         """
 
-        def __print_dep_recursive(env, node, level = 0):
-            indent = " "*(level * 4)
+        def __print_dep_recursive(env, node, inds = 0):
+            indent = " "*inds
             dep: NodeDependency = NodeProperty.Dependency[node]
-
-            print(f"{indent}+ {node._name} -> {NodeProperty.Value[node]}")
+            
+            state = 'enabled' if NodeProperty.Value[node] else 'disabled'
+            print(f"{indent}* {node._name} (currently {state})")
             if dep is None:
                 return
             
-            print(f"{indent}= {dep._expr}")
+            print(f"  {indent}predicate: {dep._expr}")
+            print(f"  {indent}dependents:")
             for name in dep._names:
                 n = env.get_node(name)
-                __print_dep_recursive(env, n, level + 1)
+                __print_dep_recursive(env, n, inds + 6)
 
         node = self.__get_node(name)
-        print(node._name)
         __print_dep_recursive(self.__env, node)
 
     @cmd("opt", "val", "v")
@@ -138,4 +143,23 @@ class Commands(CmdTable):
         print()
         print(textwrap.indent(help.strip(), "  |\t", lambda _:True))
         print()
+
+
+    @cmd("effect", "link")
+    def __fn_effect(self, name: str):
+        """
+        Show the effects of this option on other options
+        """
+        
+        node = self.__get_node(name)
+        link = NodeProperty.Linkage[node]
+
+        if not link:
+            return
+        
+        for other, exprs in link.linkages():
+            print(f" {other}:")
+            for expr in exprs:
+                print(f"   > when {expr}")
+            print()
     
\ No newline at end of file