Rewrite the lunabuild toolchain with enhanced feature (#60)
authorLunaixsky <lunaixsky@qq.com>
Sun, 11 May 2025 16:09:23 +0000 (17:09 +0100)
committerGitHub <noreply@github.com>
Sun, 11 May 2025 16:09:23 +0000 (17:09 +0100)
* rewrite the lunabuild toolchain with enhanced feature

* reduce the boilerplate code in LConfig and LBuild
* introduce new syntax suger for better expressiveness
* support predicate based config depedency checking
* more efficient lazy evaluation

* polish the shconfig with new cmds and auto-completion

* temporary removed the menuconfig (for further inspection)
* add feature of aliasing commands
* add feature of auto-completion of config options

* clean up redundant lines

* add validator to restrict the flexibility of LConfig

* allow a bool config option change value based on other option's value
  similar to "select" in kconfig, but it is distributed to the actual
  affecting flags rather than centered around the master option

* update readme on build system

* clean up: unused node property
* clean up: add space after node directives for clarity
* clean up: reduce the size of whitelist of non-trivial-value rule

* shconfig: add `link` command to view the inverse dependency

* fix some typos

* migrate stock user space build to new build system

* fix corner cases in luna_build.py when no LConfig is used
* add back the missing arch_bits_* flags
* fix sometimes the shconfig being triggered multiple times

* refine the documentation, add extra warning messages

* shconfig: add "find" command to enable fuzzy searching
* shconfig: use shlex.split to disassemble the command line

99 files changed:
lunaix-os/LBuild
lunaix-os/LConfig
lunaix-os/README.md
lunaix-os/arch/LBuild
lunaix-os/arch/LConfig
lunaix-os/arch/generic/LBuild
lunaix-os/arch/x86/LBuild
lunaix-os/arch/x86/LConfig
lunaix-os/arch/x86/hal/LBuild
lunaix-os/hal/LBuild
lunaix-os/hal/LConfig
lunaix-os/hal/acpi/LBuild
lunaix-os/hal/ahci/LBuild
lunaix-os/hal/ahci/LConfig
lunaix-os/hal/bus/LBuild
lunaix-os/hal/bus/LConfig
lunaix-os/hal/char/LBuild
lunaix-os/hal/char/LConfig
lunaix-os/hal/char/uart/LBuild
lunaix-os/hal/char/uart/LConfig
lunaix-os/hal/devtree/LBuild
lunaix-os/hal/gfxa/LBuild
lunaix-os/hal/gfxa/vga/LBuild
lunaix-os/hal/rtc/LBuild
lunaix-os/hal/term/LBuild
lunaix-os/hal/timer/LBuild
lunaix-os/install_headers.mk
lunaix-os/kernel/LBuild
lunaix-os/kernel/LConfig
lunaix-os/kernel/block/LBuild
lunaix-os/kernel/debug/LBuild
lunaix-os/kernel/device/LBuild
lunaix-os/kernel/ds/LBuild
lunaix-os/kernel/exe/LBuild
lunaix-os/kernel/exe/elf-generic/LBuild
lunaix-os/kernel/fs/LBuild
lunaix-os/kernel/fs/LConfig
lunaix-os/kernel/fs/ext2/LBuild
lunaix-os/kernel/fs/ext2/LConfig
lunaix-os/kernel/fs/iso9660/LBuild
lunaix-os/kernel/fs/ramfs/LBuild
lunaix-os/kernel/fs/twifs/LBuild
lunaix-os/kernel/mm/LBuild
lunaix-os/kernel/mm/LConfig
lunaix-os/kernel/process/LBuild
lunaix-os/libs/LBuild
lunaix-os/makefile
lunaix-os/makeinc/kbuild_deps.mkinc
lunaix-os/makeinc/lunabuild.mkinc
lunaix-os/makeinc/toolchain.mkinc
lunaix-os/scripts/build-tools/README.lbuild.md
lunaix-os/scripts/build-tools/README.lconfig.md [new file with mode: 0644]
lunaix-os/scripts/build-tools/README.md [new file with mode: 0644]
lunaix-os/scripts/build-tools/integration/build_gen.py [deleted file]
lunaix-os/scripts/build-tools/integration/config_io.py [deleted file]
lunaix-os/scripts/build-tools/integration/lbuild_bridge.py [deleted file]
lunaix-os/scripts/build-tools/integration/libmenu.py [deleted file]
lunaix-os/scripts/build-tools/integration/libtui.py [deleted file]
lunaix-os/scripts/build-tools/integration/lunamenu.py [deleted file]
lunaix-os/scripts/build-tools/integration/render_ishell.py [deleted file]
lunaix-os/scripts/build-tools/lbuild/api.py [deleted file]
lunaix-os/scripts/build-tools/lbuild/build.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lbuild/common.py
lunaix-os/scripts/build-tools/lbuild/contract.py [deleted file]
lunaix-os/scripts/build-tools/lbuild/scope.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/api.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/builtins.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/common.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/lcnodes.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/types.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/utils.py [deleted file]
lunaix-os/scripts/build-tools/lcfg2/__init__.py [moved from lunaix-os/scripts/build-tools/integration/__init__.py with 100% similarity]
lunaix-os/scripts/build-tools/lcfg2/ast_validator.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/builder.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/common.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/config.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/lazy.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/nodes.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/rewriter.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/rules.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/sanitiser.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lib/sandbox.py [deleted file]
lunaix-os/scripts/build-tools/lib/utils.py
lunaix-os/scripts/build-tools/luna_build.py
lunaix-os/scripts/build-tools/shared/__init__.py [moved from lunaix-os/scripts/build-tools/lcfg/__init__.py with 100% similarity]
lunaix-os/scripts/build-tools/shared/build_gen.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/export.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/scopes.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/shconfig/__init__.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/shconfig/commands.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/shconfig/common.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/shconfig/main.py [new file with mode: 0644]
lunaix-os/usr/LBuild
lunaix-os/usr/LConfig [deleted file]
lunaix-os/usr/libc/LBuild
lunaix-os/usr/libc/arch/i386/LBuild
lunaix-os/usr/libc/arch/x86_64/LBuild
lunaix-os/usr/libc/makefile
lunaix-os/usr/makefile

index dcee304424dc51737ad7ee7b0b83604639f367cb..04a0f54c88797b3ca0f20f08528261d4975514a3 100644 (file)
@@ -1,27 +1,23 @@
-use("kernel")
-use("libs")
-use("arch")
-use("hal")
+from . import kernel, libs, arch, hal
 
 
-headers([
-    "includes",
-    "includes/usr"
-])
+src.h += "includes", "includes/usr"
 
 
-# compliation setting
+flag.cc += "-ffreestanding", "-fno-pie"
 
 
-compile_opts([
-    "-ffreestanding",
-    "-fno-pie"
-])
+flag.cc += (
+    "-Wall -Wextra -Werror",
+    "-Wno-unknown-pragmas",
+    "-Wno-unused-function",
+    "-Wno-unused-variable",
+    "-Wno-unused-but-set-variable",
+    "-Wno-unused-parameter",
+    "-Wno-discarded-qualifiers",
+    "-Werror=incompatible-pointer-types"
+)
 
 
-linking_opts([
-    "-nostdlib", 
-    "-nolibc", 
-    "-z noexecstack", 
-    "-no-pie", 
-])
+flag.cc += (
+    "-fno-omit-frame-pointer",
+    "-finline-small-functions",
+)
 
 
-linking_opts([
-    "-Wl,--build-id=none"
-])
\ No newline at end of file
+flag.ld += "-nostdlib", "-nolibc", "-z noexecstack", "-no-pie", "-Wl,--build-id=none"
\ No newline at end of file
index 0e27e4e4e88ac716d243f9deb1fb313f12f6754b..65cfb07f68eddbdea37fbb89b15ff9b74d7b33e7 100644 (file)
@@ -1,73 +1,68 @@
-import time
 from datetime import datetime, date
 
 from datetime import datetime, date
 
-include("kernel")
-include("arch")
-include("hal")
+from . import kernel, arch, hal
 
 
-@Term("Kernel Version")
-@ReadOnly
-def lunaix_ver():
-    """
-    Lunaix kernel version
-    """
-
-    type(str)
-    
+@native
+def get_patch_seq():
     today = date.today()
     year = today.year
     start_of_year = datetime(year, 1, 1).date()
     seq_num = (today - start_of_year).days
     
     today = date.today()
     year = today.year
     start_of_year = datetime(year, 1, 1).date()
     seq_num = (today - start_of_year).days
     
-    default("%s v0.%d%d"%(v(arch), year - 2000, seq_num))
+    return "%d%d"%(year - 2000, seq_num)
+
+@"Kernel Version"
+@readonly
+def lunaix_ver() -> str:
+    """
+    Lunaix kernel version
+    """
+    
+    return f"{arch.val} v0.0.{get_patch_seq()}"
 
 
-@Collection("Kernel Debug and Testing")
+@"Kernel Debug and Testing"
 def debug_and_testing():
     """
     General settings for kernel debugging feature
     """
 
 def debug_and_testing():
     """
     General settings for kernel debugging feature
     """
 
-    @Term("Supress assertion")
-    def no_assert():
+    @"Supress assertion"
+    def no_assert() -> bool:
         """
         Supress all assertion fail activity.
         Note: Enable this is highly NOT recommended and would result system
               extermly unstable
         """
         """
         Supress all assertion fail activity.
         Note: Enable this is highly NOT recommended and would result system
               extermly unstable
         """
-        type(bool)
-        default(False)
 
 
-    @Term("Report on stalled thread")
-    def check_stall():
+        return False
+
+    @"Report on stalled thread"
+    def check_stall() -> bool:
         """
         Check and report on any thread that spend too much time in kernel.
         """
 
         """
         Check and report on any thread that spend too much time in kernel.
         """
 
-        type(bool)
-        default(True)
+        return True
         
         
-    @Term("Max kernel time allowance")
-    def stall_timeout():
+    @"Max kernel time allowance"
+    def stall_timeout() -> int:
         """
         Set the maximum time (in seconds) spent in kernel before considered
         to be stalled.
         """
         """
         Set the maximum time (in seconds) spent in kernel before considered
         to be stalled.
         """
+        require (check_stall)
 
 
-        type(int)
-        default(10)
-
-        return v(check_stall)
+        return 10
     
     
-    @Term("Max number of preemptions")
-    def stall_max_preempts():
+    @"Max number of preemptions"
+    def stall_max_preempts() -> int:
         """
         Set the maximum number of preemptions that a task can take
         before it is considered to be stucked in some loops.
 
         Setting it to 0 disable this check
         """
         """
         Set the maximum number of preemptions that a task can take
         before it is considered to be stucked in some loops.
 
         Setting it to 0 disable this check
         """
+        require (check_stall)
 
 
-        type(int)
-        default(0)
+        return 0
 
 
-        return v(check_stall)
\ No newline at end of file
index 95df5cb0703e6905ede2ac03cb792c45cf243737..a5a2cdabfd5f8651bc80a190cf617509fd91512e 100644 (file)
@@ -1,26 +1,23 @@
-# LunaixOS 源代码
+# Luniax Kernel
 
 
-我知道这个目录结构看起来相当的劝退。特别是并没有像初代Linux那种一个文件夹里就只是一堆文件的简单朴素。但至少,就我而言,把结构细分一点儿总是好的。
+This is the source code tree for lunaix kernel.
 
 
-## 目录结构
+## Organisation
+
+| Directory | Description |
+| -------   | ------      |
+| `arch`    | ISA specific code |
+| `hal`     | drivers |
+| `includes`| headers |
+| `kernel`  | kernel source files |
+| `link`    | linker scripts |
+| `libs`    | kernel library |
+| `makeinc` | includes for makefile |
+| `scripts` | auxiliary scripts |
+| `tests`   | kernel test cases |
+| `usr`     | stock user space environment (debug only) |
+
+## Build System
+
+See: [Build system of Lunaix](./scripts/build-tools/README.md)
 
 
-+ `arch` 平台,CPU架构相关代码。
-+ `hal`  硬件抽象层,包含了平台设备基本驱动的实现。
-+ `includes`  所有头文件
-+ `makeinc` makefile配置文件
-+ `kernel` 这里就是内核了
-  + `block` 块IO抽象层
-  + `debug`  内核调试服务器
-  + `device` 设备(通用)抽象层
-  + `ds` 提供一些基本的数据结构支持。
-  + `exe` 可执行文件的解析与加载。
-  + `fs` 文件系统。
-  + `mm` 各类内存管理器。
-  + `peripheral` 外部设备驱动(如键盘)。
-  + `process` 进程相关
-  + `time` 为内核提供基本的时间,计时服务。
-  + `tty` 提供基本的,CGA服务。
-+ `libs` 一些内核使用的运行时库,主要提供是内核模式下的一些C标准库里的实现。
-+ `link` 链接器脚本
-+ `scripts` 其他脚本(如:用于代码生成)
-+ `usr` 用户空间代码库,包含了一些实用的用户程序,编译过程独立与内核。
index 703d6921761d430143348276221e2a9afbf44554..45251daacbfdddbfaa7010f845fb1c2ea513e937 100644 (file)
@@ -1,10 +1,11 @@
-use("generic")
+from . import generic
 
 
-use({
-    config("arch"): {
-        "i386": "x86",
-        "x86_64": "x86",
-        "aarch64": "arm",
-        "rv64": "riscv"
-    }
-})
\ No newline at end of file
+match config.arch:
+    case "i386" | "x86_64":
+        from . import x86
+    # case "aarch64":
+    #     from . import arm64
+    # case "rv64":
+    #     from . import rv64
+    case _:
+        from . import x86
index 7afea85ce59caf5d1bfcf721d14d1dc47f26cf39..e9eacee132de309e8b2131b5a1345478268f7c19 100644 (file)
@@ -1,27 +1,54 @@
-include("x86/LConfig")
+from . import x86
 
 
-@Collection("Platform")
+@"Platform"
 def architecture_support():
     """
         Config ISA related features
     """
 
 def architecture_support():
     """
         Config ISA related features
     """
 
-    @Term("Architecture")
-    def arch():
+    @flag
+    def arch_x86_32() -> bool:
+        when (arch is "i386")
+    
+    @flag
+    def arch_x86_64() -> bool:
+        when (arch is "x86_64")
+    
+    @flag
+    def arch_x86() -> bool:
+        when (arch is "i386")
+        when (arch is "x86_64")
+
+    @flag
+    def arch_bits_64() -> bool:
+        when (arch_bits is 64)
+
+    @flag
+    def arch_bits_32() -> bool:
+        when (arch_bits is 32)
+
+    @"Architecture"
+    def arch() -> "i386" | "x86_64":
         """ 
             Config ISA support 
         """
         """ 
             Config ISA support 
         """
-        # type(["i386", "x86_64", "aarch64", "rv64"])
-        type(["i386", "x86_64"])
-        default("x86_64")
 
 
-        env_val = env("ARCH")
-        if env_val:
-            set_value(env_val)
+        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"
 
 
-    @Term("Base operand size")
-    @ReadOnly
-    def arch_bits():
+    @"Base operand size"
+    @readonly
+    def arch_bits() -> 32 | 64:
         """ 
             Defines the base size of a general register of the 
             current selected ISA.
         """ 
             Defines the base size of a general register of the 
             current selected ISA.
@@ -29,16 +56,15 @@ def architecture_support():
             This the 'bits' part when we are talking about a CPU
         """
 
             This the 'bits' part when we are talking about a CPU
         """
 
-        type(["64", "32"])
-        match v(arch):
+        match arch.val:
             case "i386": 
             case "i386": 
-                default("32")
+                return 32
             case "aarch64": 
             case "aarch64": 
-                default("64")
+                return 64
             case "rv64": 
             case "rv64": 
-                default("64")
+                return 64
             case "x86_64": 
             case "x86_64": 
-                default("64")
+                return 64
             case _:
             case _:
-                default("32")
+                return 32
         
\ No newline at end of file
         
\ No newline at end of file
index b57fdfa00fadea0cf8115d2c1583659a7ef0814f..0a3d37d257d9eb3db53af2d7687b514338eb7eba 100644 (file)
@@ -1,10 +1,10 @@
-headers("includes")
+src.h += "includes"
 
 
-sources([
+src.c += (
     "bootmem.c",
     "trace.c",
     "vmutils.c",
     "procvm.c",
     "arch.c",
     "kremap.c"
     "bootmem.c",
     "trace.c",
     "vmutils.c",
     "procvm.c",
     "arch.c",
     "kremap.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index ed4b4f8ac9e10f4396fbd1b7b9f0470f311ce447..cd3432807938f3c9a338b341084119712ce206ff 100644 (file)
@@ -1,26 +1,26 @@
-use("hal")
+from . import hal
 
 
-sources([
+src.c += (
     "exceptions/interrupts.c",
     "exceptions/isrdef.c",
     "exceptions/intrhnds.S",
     "exceptions/interrupts.c",
     "exceptions/isrdef.c",
     "exceptions/intrhnds.S",
-])
+)
 
 
-sources([
+src.c += (
     "boot/mb_parser.c",
     "boot/kpt_setup.c",
     "boot/boot_helper.c",
     "boot/mb_parser.c",
     "boot/kpt_setup.c",
     "boot/boot_helper.c",
-])
+)
 
 
-sources([
+src.c += (
     "mm/fault.c",
     "mm/tlb.c",
     "mm/pmm.c",
     "mm/gdt.c",
     "mm/vmutils.c"
     "mm/fault.c",
     "mm/tlb.c",
     "mm/pmm.c",
     "mm/gdt.c",
     "mm/vmutils.c"
-])
+)
 
 
-sources([
+src.c += (
     "klib/fast_crc.c",
     "klib/fast_str.c",
     "exec/exec.c",
     "klib/fast_crc.c",
     "klib/fast_str.c",
     "exec/exec.c",
@@ -31,11 +31,11 @@ sources([
     "hart.c",
     "failsafe.S",
     "syscall_lut.S"
     "hart.c",
     "failsafe.S",
     "syscall_lut.S"
-])
+)
 
 
-sources({
-    config("arch"): {
-        "x86_64": [
+match config.arch:
+    case "x86_64":
+        src.c += (
             "hart64.c",
             "syscall64.S",
             "exceptions/interrupt64.S",
             "hart64.c",
             "syscall64.S",
             "exceptions/interrupt64.S",
@@ -44,8 +44,9 @@ sources({
             "boot/x86_64/prologue64.S",
             "boot/x86_64/kremap64.c",
             "exec/elf64.c"
             "boot/x86_64/prologue64.S",
             "boot/x86_64/kremap64.c",
             "exec/elf64.c"
-        ],
-        "i386": [
+        )
+    case "i386":
+        src.c += (
             "hart32.c",
             "syscall32.S",
             "exceptions/interrupt32.S",
             "hart32.c",
             "syscall32.S",
             "exceptions/interrupt32.S",
@@ -54,28 +55,21 @@ sources({
             "boot/i386/prologue32.S",
             "boot/i386/kremap32.c",
             "exec/elf32.c"
             "boot/i386/prologue32.S",
             "boot/i386/kremap32.c",
             "exec/elf32.c"
-        ]
-    }
-})
+        )
 
 
-headers([
-    "includes"
-])
+src.h += "includes"
 
 
-
-if config("arch") == "x86_64":
-    compile_opts([
+if config.arch == "x86_64":
+    flag.cc += (
         "-m64", 
         "-fno-unwind-tables", 
         "-fno-asynchronous-unwind-tables",
         "-mcmodel=large"
         "-m64", 
         "-fno-unwind-tables", 
         "-fno-asynchronous-unwind-tables",
         "-mcmodel=large"
-    ])
-    linking_opts([
-        "-m64",
-    ])
+    )
+    flag.ld += "-m64"
 else:
 else:
-    compile_opts("-m32")
-    linking_opts("-m32")
+    flag.cc += "-m32"
+    flag.ld += "-m32"
 
 
-if not config("x86_enable_sse_feature"):
-    compile_opts("-mno-sse")
\ No newline at end of file
+if not config.x86_enable_sse_feature:
+    flag.cc += "-mno-sse"
\ No newline at end of file
index 2b345246214d989aa9f5f40c2561a4ad1bb87503..6720ccddbd6ddd370650db87b51b7760acb3a5be 100644 (file)
@@ -1,22 +1,28 @@
 
 
-@Group
+@(parent := architecture_support)
 def x86_configurations():
 def x86_configurations():
-    
-    add_to_collection(architecture_support)
 
 
-    @Term("Use SSE2/3/4 extension")
-    def x86_enable_sse_feature():
+    require (arch_x86)
+
+    @flag
+    def x86_bl_mb() -> bool:
+        when (x86_bl is "mb")
+    
+    @flag
+    def x86_bl_mb2() -> bool:
+        when (x86_bl is "mb2")
+    
+    @"Use SSE2/3/4 extension"
+    def x86_enable_sse_feature() -> bool:
         """ 
             Config whether to allow using SSE feature for certain
             optimization
         """
         """ 
             Config whether to allow using SSE feature for certain
             optimization
         """
-        
-        type(bool)
-        default(False)
 
 
+        return False
 
 
-    @Term("Bootloader Model")
-    def x86_bl():
+    @"Bootloader Model"
+    def x86_bl() -> "mb" | "mb2":
         """
             Select the bootloader interface
             
         """
             Select the bootloader interface
             
@@ -26,9 +32,4 @@ def x86_configurations():
                 none:    do not use any interface
         """
 
                 none:    do not use any interface
         """
 
-        type(["mb"])
-        # type(["mb", "mb2", "none"])
-        default("mb")
-
-
-    return v(arch) in ["i386", "x86_64"]
\ No newline at end of file
+        return "mb"
\ No newline at end of file
index 870f743cf2a499f22421d02504d2c52712bc66ec..d3ba47cd2d68183710d9c639553ab3ca91f24d6a 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "apic.c",
     "rngx86.c",
     "cpu.c",
     "apic.c",
     "rngx86.c",
     "cpu.c",
@@ -6,4 +6,4 @@ sources([
     "apic_timer.c",
     "mc146818a.c",
     "pci.c"
     "apic_timer.c",
     "mc146818a.c",
     "pci.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 6ff2713d2e5de227f582fd588ce6b683e671e035..d0475eef8bf70bf2092d04fa973fddc5d555e958 100644 (file)
@@ -1,15 +1,9 @@
-use("acpi")
-use("ahci")
-use("char")
-use("gfxa")
-use("rtc")
-use("term")
-use("timer")
-use("bus")
+from . import acpi, ahci, char, rtc, term, timer, bus
 
 
-if config("use_devicetree"):
-    use("devtree")
+if config.use_devicetree:
+    from . import devtree
 
 
-sources([
-    "irq.c"
-])
\ No newline at end of file
+if config.use_graphic_dev:
+    from . import gfxa
+
+src.c += "irq.c"
\ No newline at end of file
index 9a70d02d05337371993496b90112bb44ec22f9e8..5b6a86f72b52c7bcc969b5641bae967009fa0109 100644 (file)
@@ -1,13 +1,11 @@
-include("char")
-include("bus")
-include("ahci")
+from . import char, bus, ahci
 
 
-@Collection("Devices & Peripherials")
+@"Devices & Peripherials"
 def hal():
     """ Lunaix hardware asbtraction layer """
 
 def hal():
     """ Lunaix hardware asbtraction layer """
 
-    @Term("Devicetree for hardware discovery")
-    def use_devicetree():
+    @"Devicetree for hardware discovery"
+    def use_devicetree() -> bool:
         """
             Decide whether to use Devicetree for platform
             resource topology sensing.
         """
             Decide whether to use Devicetree for platform
             resource topology sensing.
@@ -18,18 +16,23 @@ def hal():
             devicetree might be mandatory and perhaps the only
             way.
         """
             devicetree might be mandatory and perhaps the only
             way.
         """
+        require (not arch_x86)
 
 
-        type(bool)
-        default(not v(arch).startswith("x86"))
+        return False
 
 
-    @ReadOnly
-    @Term("Maximum size of device tree blob (in KiB)")
-    def dtb_maxsize():
+    @"Maximum size of device tree blob (in KiB)"
+    @readonly
+    def dtb_maxsize() -> int:
         """
             Maximum size for a firmware provided device tree blob
         """
         """
             Maximum size for a firmware provided device tree blob
         """
+        require (use_devicetree)
 
 
-        type(int)
-        default(256)
-
-        return v(use_devicetree)
\ 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 fb5ee72365a389a00ce0908aa164a769c275a961..5a59b3318c0cd64e128d74125aa97aeb739079a6 100644 (file)
@@ -1,5 +1,5 @@
-sources([
+src.c += (
     "parser/madt_parser.c",
     "parser/mcfg_parser.c",
     "acpi.c"
     "parser/madt_parser.c",
     "parser/mcfg_parser.c",
     "acpi.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 7da89abe6dd9091e128a120b371f37d8184b83d4..ec6018d3bb6f4520780dd0f0abf635f3f2a3a761 100644 (file)
@@ -1,5 +1,5 @@
-if config("ahci_enable"):
-    sources([
+if config.ahci_enable:
+    src.c += (
         "ahci_pci.c",
         "hbadev_export.c",
         "ahci.c",
         "ahci_pci.c",
         "hbadev_export.c",
         "ahci.c",
@@ -7,4 +7,4 @@ if config("ahci_enable"):
         "io_event.c",
         "atapi.c",
         "ata.c"
         "io_event.c",
         "atapi.c",
         "ata.c"
-    ])
\ No newline at end of file
+    )
\ No newline at end of file
index a199abfce77c858345e5db13b44ab1a60acea305..ea0b305bc0adbece3026e1d8295a4540f96504ea 100644 (file)
@@ -1,18 +1,14 @@
 
 
-@Collection("AHCI")
+@"AHCI"
+@(parent := hal)
 def sata_ahci():
 
 def sata_ahci():
 
-    add_to_collection(hal)
-
-    @Term("Enable AHCI support")
-    def ahci_enable():
+    @"Enable AHCI support"
+    def ahci_enable() -> bool:
         """ Enable the support of SATA AHCI. 
         """ Enable the support of SATA AHCI. 
-            Must require PCI at current stage """
+            Must require  PCI at current stage """
+        require (pci_enable)
         
         
-        type(bool)
-        default(True)
-
-        if not v(pci_enable):
-            set_value(False)
+        return True
 
         
\ No newline at end of file
 
         
\ No newline at end of file
index 3b5ff38ba1cdee2a21bd59d94282fb9b20fb8c0a..3e280aa731aa2bd607e65b2fc3fcc839c9fe8917 100644 (file)
@@ -1,3 +1,3 @@
 
 
-if config("pci_enable"):
-    sources("pci.c")
\ No newline at end of file
+if config.pci_enable:
+    src.c += "pci.c"
\ No newline at end of file
index e4281d294cd6018da966ab66f952c6e193f0a89a..82d97f812f1d227ada2392616f22569fe9e514a8 100644 (file)
@@ -1,35 +1,25 @@
 
 
-@Collection("Buses & Interconnects")
+@"Buses & Interconnects"
+@(parent := hal)
 def bus_if():
     """ System/platform bus interface """
 
 def bus_if():
     """ System/platform bus interface """
 
-    add_to_collection(hal)
-
-    @Term("PCI")
-    def pci_enable():
+    @"PCI"
+    def pci_enable() -> bool:
         """ Peripheral Component Interconnect (PCI) Bus """
         """ Peripheral Component Interconnect (PCI) Bus """
-        type(bool)
-        default(True)
+        return True
 
 
-    @Term("PCI Express")
-    def pcie_ext():
+    @"PCI Express"
+    def pcie_ext() -> bool:
         """ Enable support of PCI-Express extension """
         """ Enable support of PCI-Express extension """
-        type(bool)
-        default(False)
+        require (pci_enable)
 
 
-        return v(pci_enable)
+        return False
 
 
-    @Term("Use PMIO for PCI")
-    def pci_pmio():
+    @"Use PMIO for PCI"
+    def pci_pmio() -> bool:
         """ Use port-mapped I/O interface for controlling PCI """
         """ Use port-mapped I/O interface for controlling PCI """
-        type(bool)
-
-        has_pcie = v(pcie_ext)
-        is_x86 = v(arch) in [ "i386", "x86_64" ]
-
-        default(not has_pcie)
-        
-        if not is_x86 or has_pcie:
-            set_value(False)
+        require (not pcie_ext and pci_enable)
+        require (arch_x86)
 
 
-        return is_x86 and v(pci_enable)
\ No newline at end of file
+        return True
index dbc15be35a3dc8a14f3d344468518dcf57b93dcb..55e3c8271e187971186e4337bb0c4863e140215e 100644 (file)
@@ -1,15 +1,13 @@
-use("uart")
+from . import uart
 
 
-sources([
+if config.vga_console:
+    src.c += "lxconsole.c"
+
+if config.chargame_console:
+    src.c += "chargame.c"
+
+src.c += (
     "devnull.c",
     "serial.c",
     "devzero.c",
     "devnull.c",
     "serial.c",
     "devzero.c",
-])
-
-if config("vga_console"):
-
-    sources("lxconsole.c")
-
-if config("chargame_console"):
-    
-    sources("chargame.c")
\ No newline at end of file
+)
\ No newline at end of file
index 8e662fa5be4494450cfc8e0ca6e5633787ae6576..dd2fa0f0c498026e8f8a7737e91c573a72dcbd38 100644 (file)
@@ -1,25 +1,26 @@
-include("uart")
+from . import uart
 
 
-@Collection("Character Devices")
+@"Character Devices"
+@(parent := hal)
 def char_device():
     """ Controlling support of character devices """
 
 def char_device():
     """ Controlling support of character devices """
 
-    add_to_collection(hal)
-
-    @Term("VGA 80x25 text-mode console")
-    def vga_console():
-        """ Enable VGA console device (text mode only) """
-
-        type(bool)
-        default(True)
+    @"VGA 80x25 text-mode console"
+    def vga_console() -> bool:
+        """ 
+            Enable VGA console device (text mode only) 
+        """
+        require (use_graphic_dev)
+        
+        return True
 
 
-    @Term("VGA character game device")
-    def chargame_console():
+    @"VGA character game device"
+    def chargame_console() -> bool:
         """ 
             Enable VGA Charactor Game console device (text mode only) 
 
             You normally don't need to include this, unless you want some user space fun ;)
         """
         """ 
             Enable VGA Charactor Game console device (text mode only) 
 
             You normally don't need to include this, unless you want some user space fun ;)
         """
+        require (vga_console)
 
 
-        type(bool)
-        default(False)
\ No newline at end of file
+        return False
\ No newline at end of file
index 9ed3119546bcc55d62d4224534141153c2c8b42b..a25cf34735102ad91f37181c28d15f44c5aee6e4 100644 (file)
@@ -1,12 +1,12 @@
-sources([
+src.c += (
     "16x50_base.c",
     "16x50_pmio.c",
     "16x50_mmio.c",
     "16x50_dev.c",
     "16x50_base.c",
     "16x50_pmio.c",
     "16x50_mmio.c",
     "16x50_dev.c",
-])
+)
 
 
-if config("xt_16x50"):
-    sources("16x50_isa.c")
+if config.xt_16x50:
+    src.c += "16x50_isa.c"
 
 
-if config("pci_16x50"):
-    sources("16x50_pci.c")
\ No newline at end of file
+if config.pci_16x50:
+    src.c += "16x50_pci.c"
\ No newline at end of file
index bbb019df967d4a77c3947ee3daf9d4117bb91d7d..61014ad159cf8e8f5e848d1be19d524b70c326a2 100644 (file)
@@ -1,28 +1,18 @@
 
 
-@Collection("16x50 Serial Controller")
+@"16x50 Serial Controller"
+@(parent := char_device)
 def uart_16x50():
     """ 16x50 serial controller  """
 
 def uart_16x50():
     """ 16x50 serial controller  """
 
-    # hal/char/LConfig::char_device
-    add_to_collection(char_device)
-
-    @Term("16x50 XT-Compat")
-    def xt_16x50():
+    @"16x50 XT-Compat"
+    def xt_16x50() -> bool:
         """ Enable the 16x50 for PC-compatible platform  """
         """ Enable the 16x50 for PC-compatible platform  """
-
-        type(bool)
-
-        default(True)
+        require (arch_x86)
         
         
-        is_x86 = v(arch) in ["i386", "x86_64"]
-        if not is_x86:
-            set_value(False)
-
-        return is_x86
+        return True
     
     
-    @Term("16x50 PCI")
-    def pci_16x50():
+    @"16x50 PCI"
+    def pci_16x50() -> bool:
         """ Enable the support of PCI 16x50 """
         """ Enable the support of PCI 16x50 """
-        type(bool)
 
 
-        default(True)
\ No newline at end of file
+        return True
\ No newline at end of file
index 000d52b4ab085169fe27cb818476753cb1afd342..496a325d12c2a8929774ae1e7ca2075a4ff10a42 100644 (file)
@@ -1,6 +1,6 @@
-sources([
+src.c += (
     "dt_interrupt.c",
     "dt.c",
     "dtm.c",
     "dtspec.c"
     "dt_interrupt.c",
     "dt.c",
     "dtm.c",
     "dtspec.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 54d50d131eb44d4c60310e87441ea305fb9e4488..980a0a5a452899729fadebde79a010033aee3596 100644 (file)
@@ -1,5 +1,3 @@
-use("vga")
+from . import vga
 
 
-sources([
-    "gfxm.c"
-])
\ No newline at end of file
+src.c += "gfxm.c"
\ No newline at end of file
index dc8991a04f73ed935e3405d8d191d7df1159f555..701a7cfa561702e44797d645fc245f1cdee6bcbb 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "vga_pmio_ops.c",
     "vga_gfxm_ops.c",
     "vga.c",
     "vga_mmio_ops.c",
     "vga_pci.c",
     "vga_rawtty.c",
     "vga_pmio_ops.c",
     "vga_gfxm_ops.c",
     "vga.c",
     "vga_mmio_ops.c",
     "vga_pci.c",
     "vga_rawtty.c",
-])
\ No newline at end of file
+)
\ No newline at end of file
index debfd14cb2b1df6a7ce80000a63c046f257ba378..69dd357802bbc16f3decae3d2f43c847b6bb42cd 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "rtc_device.c"
-])
\ No newline at end of file
+src.c += "rtc_device.c"
\ No newline at end of file
index ea5b2c396ea6c616f5ee240319fa0d5a72968e53..554483e3b75e21e7d414563d1c74bf6d384aacb8 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "term.c",
     "console.c",
     "term_io.c",
     "default_ops.c",
     "lcntls/ansi_cntl.c",
     "lcntls/lcntl.c",
     "term.c",
     "console.c",
     "term_io.c",
     "default_ops.c",
     "lcntls/ansi_cntl.c",
     "lcntls/lcntl.c",
-])
\ No newline at end of file
+)
\ No newline at end of file
index e5b22561dea8134716822bcbd2484b0aef0ea882..a79fe9aa17be835048dc296b3193e1346fa51548 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "timer_device.c"
-])
\ No newline at end of file
+src.c += "timer_device.c"
\ No newline at end of file
index d63637548a2725efd7b4b4ebf602f195af9ef4b2..fee40f08962426ca062062e1350c3b2bc8f8b42c 100644 (file)
@@ -4,6 +4,8 @@ ifndef PREFIX
        $(error "Must specify PREFIX to header install location")
 endif
 
        $(error "Must specify PREFIX to header install location")
 endif
 
+CFLAGS += $(kcflags)
+
 USR_HEADER := includes/usr
 
 HEADERS := $(shell cat $(USR_HEADER)/headers)
 USR_HEADER := includes/usr
 
 HEADERS := $(shell cat $(USR_HEADER)/headers)
index 3e0a7be39e3a5e81495d66da3d00be896944f868..31428ffb95dd7fec9ed59fcdc544fa5d28c484a9 100644 (file)
@@ -1,13 +1,6 @@
-use("block")
-use("debug")
-use("device")
-use("ds")
-use("exe")
-use("fs")
-use("mm")
-use("process")
+from . import block, debug, device, ds, exe, fs, mm, process
 
 
-sources([
+src.c += (
     "boot_helper.c",
     "kcmd.c",
     "kinit.c",
     "boot_helper.c",
     "kcmd.c",
     "kinit.c",
@@ -21,5 +14,5 @@ sources([
     "kprint/kp_records.c",
     "kprint/kprintf.c",
     "time/clock.c",
     "kprint/kp_records.c",
     "kprint/kprintf.c",
     "time/clock.c",
-    "time/timer.c",
-])
\ No newline at end of file
+    "time/timer.c"
+)
\ No newline at end of file
index a46e85ff827f40b524e755ef1022496fbbfc742a..0fad37b7d3016371786e5f65af6c5d8d5941eabd 100644 (file)
@@ -1,7 +1,6 @@
-@Collection
+from . import fs, mm
+
+@"Kernel Feature"
 def kernel_feature():
     """ Config kernel features """
     pass
 def kernel_feature():
     """ Config kernel features """
     pass
-
-include("fs")
-include("mm")
index 10586c59438048e2bf346ff1dcbe76e9221ce26e..a44b0691702d07a792c505c8030a522b0824c743 100644 (file)
@@ -1,7 +1,7 @@
-sources([
+src.c += (
     "blkpart_gpt.c",
     "blk_mapping.c",
     "blkio.c",
     "block.c",
     "blkbuf.c"
     "blkpart_gpt.c",
     "blk_mapping.c",
     "blkio.c",
     "block.c",
     "blkbuf.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index d9ddb47cb983c1b846467a2854ef775c7278740a..19831a72c52ea174f93900bb1eb6c71cf3fd0be2 100644 (file)
@@ -1,5 +1,5 @@
-sources([
+src.c += (
     "failsafe.c",
     "trace.c",
     # "gdbstub.c"
     "failsafe.c",
     "trace.c",
     # "gdbstub.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 989a81b1bdbeb2e38582b5b4595196ffa7794340..e589701f963f09ec32a1fddf91ba870ad020c94a 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "device.c",
     "potentem.c",
     "devdb.c",
     "devfs.c",
     "input.c",
     "poll.c",
     "device.c",
     "potentem.c",
     "devdb.c",
     "devfs.c",
     "input.c",
     "poll.c",
-])
+)
index a5c082fdb26f0e878198a4ef52d2140d95c30e29..867936d8e0310fed0a73b8524b8354690c797323 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "waitq.c",
     "buffer.c",
     "rbuffer.c",
     "waitq.c",
     "buffer.c",
     "rbuffer.c",
@@ -8,4 +8,4 @@ sources([
     "hstr.c",
     "fifo.c",
     "rwlock.c"
     "hstr.c",
     "fifo.c",
     "rwlock.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 3ab25b49f44a6005ee1612e6787a0f2e34c9135d..1a23f2a7d108d01adb7945422f4759521ce05b46 100644 (file)
@@ -1,5 +1,3 @@
-use("elf-generic")
+import_("elf-generic")
 
 
-sources([
-    "exec.c"
-])
\ No newline at end of file
+src.c += "exec.c"
\ No newline at end of file
index 80f3a1e93ef255872a0d956d9f081750d9b1c63a..3cb3702707dd45a7093ea777b1890642726abf4a 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "elfbfmt.c",
     "ldelf.c"
     "elfbfmt.c",
     "ldelf.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 638662f8319721f8b3752f743b542d7d5c91c355..0a1d2ac26ef7b65c7fdbb0b5d2d71a122bd98056 100644 (file)
@@ -1,13 +1,12 @@
-use("twifs")
-use("ramfs")
+from . import twifs, ramfs
 
 
-if configured("fs_iso9660"):
-    use("iso9660")
+if config.fs_ext2:
+    from . import ext2
 
 
-if configured("fs_ext2"):
-    use("ext2")
+if config.fs_iso9660:
+    from . import iso9660
 
 
-sources([
+src.c += (
     "twimap.c",
     "pcache.c",
     "mount.c",
     "twimap.c",
     "pcache.c",
     "mount.c",
@@ -17,5 +16,5 @@ sources([
     "path_walk.c",
     "fsm.c",
     "fs_export.c",
     "path_walk.c",
     "fsm.c",
     "fs_export.c",
-])
+)
 
 
index 880d6c68a102c502c6d7f7d66c8d9f8a83d58806..dfa8fe4df79c90efe9fa4b709e88e07d6171ca82 100644 (file)
@@ -1,23 +1,18 @@
+from . import ext2
 
 
-@Collection("File Systems")
+@"File Systems"
+@(parent := kernel_feature)
 def file_system():
     """ Config feature related to file system supports """
 
 def file_system():
     """ Config feature related to file system supports """
 
-    add_to_collection(kernel_feature)
-
-    @Term("ext2 support")
-    def fs_ext2():
+    @"ext2 support"
+    def fs_ext2() -> bool:
         """ Enable ext2 file system support """
 
         """ Enable ext2 file system support """
 
-        type(bool)
-        default(True)
+        return True
     
     
-    @Term("iso9660 support")
-    def fs_iso9660():
+    @"iso9660 support"
+    def fs_iso9660() -> bool:
         """ Enable iso9660 file system support """
 
         """ Enable iso9660 file system support """
 
-        type(bool)
-        default(True)
-
-
-include("ext2")
\ No newline at end of file
+        return True
index a9700fe12d3bdba743336080f7086f56fa7085b8..30129e69a3ab74d9136d7016628b492d8ed38c58 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "alloc.c",
     "dir.c",
     "file.c",
     "group.c",
     "inode.c",
     "mount.c"
     "alloc.c",
     "dir.c",
     "file.c",
     "group.c",
     "inode.c",
     "mount.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index f5cb0a66f1dfb9b3ebb2076af799c673c7328ddc..435832166bd19bf5580f309c30c16a82701430aa 100644 (file)
@@ -1,11 +1,10 @@
 
 
-@Collection("ext2")
+@"ext2"
+@(parent := file_system)
 def ext2_fs():
 def ext2_fs():
-    add_to_collection(file_system)
 
 
-    @Term("Debug Messages")
-    def ext2_debug_msg():
-        type(bool)
-        default(False)
+    require (fs_ext2)
 
 
-    return v(fs_ext2)
\ No newline at end of file
+    @"Debug Messages"
+    def ext2_debug_msg() -> bool:
+        return False
\ No newline at end of file
index aaa5edcce3e2b83e6fb253d50850c2f4d94c200a..06eea1c0e3b8594ae9c418be998bdaa1e416541d 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "inode.c",
     "file.c",
     "mount.c",
     "directory.c",
     "utils.c",
     "rockridge.c"
     "inode.c",
     "file.c",
     "mount.c",
     "directory.c",
     "utils.c",
     "rockridge.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index fbc9ec0f8edef23b9e87dfe9b81e9abe73d1a697..85a23f8f37c0e004044b9bb61b76b08e2e7cbe61 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "ramfs.c"
-])
\ No newline at end of file
+src.c += "ramfs.c"
\ No newline at end of file
index b4270ce16ebf3ab8cfa0e495b7bdb9e8a40f674e..32feb297e1e01ce113d6e8582af63dfc65556908 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "twifs.c"
-])
\ No newline at end of file
+src.c += "twifs.c"
\ No newline at end of file
index e350f502168e1db4895071cbc6f95f3d61c4eead..0c9b82be58cc6f854cca2833aa6439fadec6f680 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "mmap.c",
     "valloc.c",
     "cake.c",
     "mmap.c",
     "valloc.c",
     "cake.c",
@@ -13,4 +13,4 @@ sources([
     "cake_export.c",
     "vmap.c",
     "dmm.c"
     "cake_export.c",
     "vmap.c",
     "dmm.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 17247b0d692ddb8e53e55a838ea6c770252e949b..c6502bd08c56bfdacda39b3b07a5b4d4a032de7d 100644 (file)
@@ -1,92 +1,92 @@
 
 
-@Collection("Memory Management")
+@"Memory Management"
+@(parent := kernel_feature)
 def memory_subsystem():
     """ Config the memory subsystem """
 
 def memory_subsystem():
     """ Config the memory subsystem """
 
-    @Collection("Physical Memory")
+    @"Physical Memory"
     def physical_mm():
         """ Physical memory manager  """
 
     def physical_mm():
         """ Physical memory manager  """
 
-        @Term("Allocation policy")
-        def pmalloc_method():
+        @flag
+        def pmalloc_method_simple() -> bool:
+            when (pmalloc_method is "simple")
+        
+        @flag
+        def pmalloc_method_buddy() -> bool:
+            when (pmalloc_method is "buddy")
+        
+        @flag
+        def pmalloc_method_ncontig() -> bool:
+            when (pmalloc_method is "ncontig")
+
+        @"Allocation policy"
+        def pmalloc_method() -> "simple" | "buddy" | "ncontig":
             """ Allocation policy for phiscal memory  """
             
             """ Allocation policy for phiscal memory  """
             
-            type(["simple", "buddy", "ncontig"])
-            default("simple")
+            return "simple"
 
 
-        @Group("Simple")
+        @"PMalloc Thresholds"
         def pmalloc_simple_po_thresholds():
         def pmalloc_simple_po_thresholds():
+
+            require  (pmalloc_method_simple)
             
             
-            @Term("Maximum cached order-0 free pages")
-            def pmalloc_simple_max_po0():
+            @"Maximum cached order-0 free pages"
+            def pmalloc_simple_max_po0() -> int:
                 """ free list capacity for order-0 pages  """
                 
                 """ free list capacity for order-0 pages  """
                 
-                type(int)
-                default(4096)
+                return 4096
 
 
-            @Term("Maximum cached order-1 free pages")
-            def pmalloc_simple_max_po1():
+            @"Maximum cached order-1 free pages"
+            def pmalloc_simple_max_po1() -> int:
                 """ free list capacity for order-1 pages  """
 
                 """ free list capacity for order-1 pages  """
 
-                type(int)
-                default(2048)
+                return 2048
             
             
-            @Term("Maximum cached order-2 free pages")
-            def pmalloc_simple_max_po2():
+            @"Maximum cached order-2 free pages"
+            def pmalloc_simple_max_po2() -> int:
                 """ free list capacity for order-2 pages  """
 
                 """ free list capacity for order-2 pages  """
 
-                type(int)
-                default(2048)
+                return 2048
             
             
-            @Term("Maximum cached order-3 free pages")
-            def pmalloc_simple_max_po3():
+            @"Maximum cached order-3 free pages"
+            def pmalloc_simple_max_po3() -> int:
                 """ free list capacity for order-3 pages  """
                 
                 """ free list capacity for order-3 pages  """
                 
-                type(int)
-                default(2048)
+                return 2048
             
             
-            @Term("Maximum cached order-4 free pages")
-            def pmalloc_simple_max_po4():
+            @"Maximum cached order-4 free pages"
+            def pmalloc_simple_max_po4() -> int:
                 """ free list capacity for order-4 pages  """
 
                 """ free list capacity for order-4 pages  """
 
-                type(int)
-                default(512)
+                return 512
             
             
-            @Term("Maximum cached order-5 free pages")
-            def pmalloc_simple_max_po5():
+            @"Maximum cached order-5 free pages"
+            def pmalloc_simple_max_po5() -> int:
                 """ free list capacity for order-5 pages  """
 
                 """ free list capacity for order-5 pages  """
 
-                type(int)
-                default(512)
+                return 512
             
             
-            @Term("Maximum cached order-6 free pages")
-            def pmalloc_simple_max_po6():
+            @"Maximum cached order-6 free pages"
+            def pmalloc_simple_max_po6() -> int:
                 """ free list capacity for order-6 pages  """
 
                 """ free list capacity for order-6 pages  """
 
-                type(int)
-                default(128)
+                return 128
             
             
-            @Term("Maximum cached order-7 free pages")
-            def pmalloc_simple_max_po7():
+            @"Maximum cached order-7 free pages"
+            def pmalloc_simple_max_po7() -> int:
                 """ free list capacity for order-7 pages  """
 
                 """ free list capacity for order-7 pages  """
 
-                type(int)
-                default(128)
+                return 128
             
             
-            @Term("Maximum cached order-8 free pages")
-            def pmalloc_simple_max_po8():
+            @"Maximum cached order-8 free pages"
+            def pmalloc_simple_max_po8() -> int:
                 """ free list capacity for order-8 pages  """
 
                 """ free list capacity for order-8 pages  """
 
-                type(int)
-                default(64)
+                return 64
             
             
-            @Term("Maximum cached order-9 free pages")
-            def pmalloc_simple_max_po9():
+            @"Maximum cached order-9 free pages"
+            def pmalloc_simple_max_po9() -> int:
                 """ free list capacity for order-9 pages  """
 
                 """ free list capacity for order-9 pages  """
 
-                type(int)
-                default(32)
-
-            return v(pmalloc_method) == "simple"
-
-    add_to_collection(kernel_feature)
\ No newline at end of file
+                return 32
\ No newline at end of file
index 3fc72ec1a7a028d7f2bef2ae15900780d93cf05f..8386486d511a0054928239787cbf41744016594a 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "signal.c",
     "sched.c",
     "fork.c",
     "signal.c",
     "sched.c",
     "fork.c",
@@ -8,4 +8,4 @@ sources([
     "thread.c",
     "preemption.c",
     "switch.c",
     "thread.c",
     "preemption.c",
     "switch.c",
-])
\ No newline at end of file
+)
\ No newline at end of file
index 2f647b4803913cf93d76959339e3a31be7fa37e1..52866eeab60f3ae42f8c1542157c18702ec78e83 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "crc.c",
     "hash.c",
     "klibc/itoa.c",
     "crc.c",
     "hash.c",
     "klibc/itoa.c",
@@ -9,4 +9,4 @@ sources([
     "klibc/string/strcpy.c",
     "klibc/string/strlen.c",
     "klibc/string/trim.c"
     "klibc/string/strcpy.c",
     "klibc/string/strlen.c",
     "klibc/string/trim.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 5d6d2af897512b574a17f21f3772dd2290a90198..ce65d6798795f09db3d3cd2480ae930b0d32c5b2 100644 (file)
@@ -76,7 +76,6 @@ clean:
        @$(MAKE) -f kernel.mk clean -I $(mkinc_dir)
        
        @rm -rf $(kbuild_dir) || exit 1
        @$(MAKE) -f kernel.mk clean -I $(mkinc_dir)
        
        @rm -rf $(kbuild_dir) || exit 1
-       @rm -rf .builder || exit 1
 
 
 # --- begin misc --- #
 
 
 # --- begin misc --- #
index 92e11b1788faad5ead1c1a9bed7219600bb7ae55..31f8b5ade5c22c699fd1298aebbe90e8902fc9d3 100644 (file)
@@ -3,13 +3,17 @@ include lunabuild.mkinc
 include utils.mkinc
 
 include $(lbuild_mkinc)
 include utils.mkinc
 
 include $(lbuild_mkinc)
+include $(lconfig_mkinc)
 
 
-ksrc_objs := $(addsuffix .o,$(_LBUILD_SRCS))
-ksrc_deps := $(addsuffix .d,$(_LBUILD_SRCS))
-khdr_opts := $(addprefix -include ,$(_LBUILD_HDRS))
-kinc_opts := $(addprefix -I,$(_LBUILD_INCS))
+ksrc_objs := $(addsuffix .o,$(BUILD_SRCS))
+ksrc_deps := $(addsuffix .d,$(BUILD_SRCS))
+khdr_opts := $(addprefix -include ,$(BUILD_HDR))
+kinc_opts := $(addprefix -I,$(BUILD_INC))
 config_h += -include $(lbuild_config_h)
 
 kcflags := $(khdr_opts) $(kinc_opts) $(config_h)
 
 config_h += -include $(lbuild_config_h)
 
 kcflags := $(khdr_opts) $(kinc_opts) $(config_h)
 
+CFLAGS += $(BUILD_CFLAGS)
+LDFLAGS += $(BUILD_LDFLAGS)
+
 -include $(ksrc_deps)
\ No newline at end of file
 -include $(ksrc_deps)
\ No newline at end of file
index aee8e495a70f776a876caf1b2acaa67e90e00433..2e08b388254948fa93e2519d41cc2fd483086140 100644 (file)
@@ -1,29 +1,44 @@
 lbuild_dir := $(CURDIR)/.builder
 lbuild_dir := $(CURDIR)/.builder
-lbuild_config_h := $(lbuild_dir)/configs.h
-lbuild_mkinc := $(lbuild_dir)/lbuild.mkinc
+lbuild_config_h := $(lbuild_dir)/config.h
+lbuild_mkinc := $(lbuild_dir)/build.mkinc
+lconfig_mkinc := $(lbuild_dir)/config.mkinc
 lconfig_save := $(CURDIR)/.config.json
 
 lconfig_save := $(CURDIR)/.config.json
 
-lbuild_opts := --lconfig-file LConfig
-
 all_lconfigs = $(shell find $(CURDIR) -name "LConfig")
 all_lconfigs = $(shell find $(CURDIR) -name "LConfig")
+all_lbuilds = $(shell find $(CURDIR) -name "LBuild")
+
+.PHONY: __gen_config __gen_build __gen_both config
 
 
-LCONFIG_FLAGS += --config $(lbuild_opts) --config-save $(lconfig_save)
+define __gen_config
+       @echo restarting configuration...
+       @$(LBUILD) --gen-config $(lbuild_dir)
+endef
+
+define __gen_build
+       @echo restarting configuration...
+       @$(LBUILD) --gen-build $(lbuild_dir)
+endef
+
+define __gen_both
+       @echo restarting configuration...
+       @$(LBUILD) --gen-build --gen-config $(lbuild_dir)
+endef
 
 export
 $(lconfig_save): $(all_lconfigs)
 
 export
 $(lconfig_save): $(all_lconfigs)
-       @echo restarting configuration...
-       @$(LBUILD) $(LCONFIG_FLAGS)  --force -o $(lbuild_dir)/
+       $(call __gen_config)
 
 export
 
 export
-$(lbuild_config_h): $(lconfig_save)
-       @$(LBUILD) $(LCONFIG_FLAGS) -o $(@D)
+$(lconfig_mkinc): $(all_lconfigs)
+       $(call __gen_config)
 
 export
 
 export
-$(lbuild_mkinc): $(lbuild_config_h)
-       @$(LBUILD) LBuild $(lbuild_opts) -o $(@D)
+$(lbuild_config_h): $(all_lbuilds)
+       $(call __gen_build)
 
 
-.PHONY: config
 export
 export
-config: $(all_lconfigs)
-       @$(LBUILD) $(LCONFIG_FLAGS) --force\
-                          -o $(lbuild_dir)/
+$(lbuild_mkinc): $(all_lbuilds)
+       $(call __gen_build)
+
+config: $(all_lbuilds) $(all_lconfigs)
+       $(call __gen_both)
index 246b9d15196ff53d78a318494311f206a1b07d2c..fcc6f423e82bac32796630a6d9b2b4282b2652ab 100644 (file)
@@ -4,29 +4,12 @@ AS := $(CX_PREFIX)as
 AR := $(CX_PREFIX)ar
 LBUILD ?= $(shell realpath ./scripts/build-tools/luna_build.py)
 
 AR := $(CX_PREFIX)ar
 LBUILD ?= $(shell realpath ./scripts/build-tools/luna_build.py)
 
-
-W := -Wall -Wextra -Werror \
-               -Wno-unknown-pragmas \
-               -Wno-unused-function \
-               -Wno-unused-variable\
-               -Wno-unused-but-set-variable \
-               -Wno-unused-parameter \
-               -Wno-discarded-qualifiers\
-               -Werror=incompatible-pointer-types
-
-OFLAGS := -fno-omit-frame-pointer      \
-             -finline-small-functions
-
-CFLAGS := -std=gnu99 $(OFLAGS) $(W) -g
-
 ifeq ($(MODE),debug)
        O := -Og
 else
        O := -O2
 endif
 
 ifeq ($(MODE),debug)
        O := -Og
 else
        O := -O2
 endif
 
-CFLAGS += $(O)
-
 LDFLAGS := $(O)
 LDFLAGS := $(O)
-
+CFLAGS  := -std=gnu99 -g $(O)
 MKFLAGS := --no-print-directory
\ No newline at end of file
 MKFLAGS := --no-print-directory
\ No newline at end of file
index 8a25a8e64ffe5da52c3e219643a2c231ec50a4fb..d391973885b8f259b27220e5d7e2febfcae08d2d 100644 (file)
@@ -3,99 +3,85 @@
 LunaBuild is programmable source file selection tool. It does not build things
 by itself, rather, it selects which source file should be feed as input to
 other tranditional build tools such as make. The selection logic is completely
 LunaBuild is programmable source file selection tool. It does not build things
 by itself, rather, it selects which source file should be feed as input to
 other tranditional build tools such as make. The selection logic is completely
-programmable and convey through `LBuild` file which is essentially a python
-script. As the primary design goal for LunaBuild is simple, lightweight and
-standalone. It just plain python, with some extra predefined functions and
-automatic variables, so does not force user to learn some other weird domain
-specific language (yes, that's you, CMake!).
+programmable and convey through `LBuild` using python syntax. 
+Since the primary design goal for LunaBuild is simple, lightweight and
+standalone. It introduce minimal customisations on the python syntax for better
+readbility and it is essentially modified python.
 
 
-## Usage
+## Functionalities
 
 
-Invoke `./luna_build.py <root_lbuild> -o <out_dir>`. It will output two file
-to the `<out_dir>`: `sources.list` and `headers.list`. Contains all the source
-files and header files to be used by the build process.
+### Native Python Environment
 
 
-## Core Functions
+LunaBuild is a superset of python, meaning that all existing functionalities of 
+python language is still intacted and supported.
 
 
-LunaBuild provide following function to help user select source files.
+This will gives you maximum flexibility on defining your build logic.
 
 
-### [func] `use(lbuild_path)`
+### Import other `LBuild`
 
 
-Include another LBuild file. `lbuild_path` is the path relative to current
-directory, pointed to the file. It can be also pointed to a directory, for
-which the LBuild file is inferred as `$lbuild_path/LBuild`.
+Multiple `LBuild`s may be defined across different sub-directory in large scale
+project for better maintainability
 
 
-For example:
+LunaBuild allow you to import content of other LBuild using python's relative import
+feature:
 
 ```py
 
 ```py
-use("dir")
-use("dir/LBuild")
+from . import subdirectory
 ```
 
 ```
 
-both are equivalent.
+This import mechanism works like `#include` directive in C preprocessor,
+the `from . import` construct will automatically intercepted by the LBuild interpreter and 
+be replaced with the content from `./subdirectory/LBuild`
 
 
-### [func] `sources(src_list)`
-
-Select a list of source files, all paths used are relative to current
-directory. For example,
+You can also address file in the deeper hierarchy of the directory tree, for example
 
 ```py
 
 ```py
-sources([ "a.c", "b.c", "c.c" ])
+from .sub1.sub2 import sub3
 ```
 
 ```
 
-### [func] `headers(src_list)`
+This will be translated into `./sub1/sub2/sub3/LBuild`
 
 
-Select a list of header file or include directory, all paths used are
-relative to current directory. For example,
+It can also be used in any valid python conditional branches to support 
+conditional import
 
 ```py
 
 ```py
-headers([ "includes/", "includes/some.h" ])
+if feature1_enabled:
+    from . import feature1
+elif feature3_enabled
+    from . import feature3
 ```
 
 ```
 
-### [func] `configured(name)`
-
-Check whether a configuration is present, the name for the configuration
-is defined by external configuration provider.
-
-### [func] `config(name)`
-
-Read the value of a configuration, raise exception is not exists.
 
 
-### [var] `_script`
+### Scoped Data Banks
 
 
-Automatic variable, a path to the current build file.
+Almost every build systems is evolved around the list data structure, as its
+main task is to collect all interested source files, headers or other build 
+process relative information. This is also true for LunaBuild.
 
 
-## Short-hands
+To better represent this in a more readable way, LunaBuild introduce the concept 
+of scoped data banks, represented as a automatic global object in the script.
 
 
-LunaBuild provide some useful short-hand notations
-
-### Conditional Select
-
-Suppose you have two source files `src_a.c` and `src_b.c`, for which
-their selection will be depends on the value of a configuration
-`WHICH_SRC`. A common way is to use `if-else` construct
+Take a look in the example:
 
 ```py
 
 ```py
-if config("WHICH_SRC") == "select_a":
-    sources("src_a.c")
-elif config("WHICH_SRC") == "select_b":
-    sources("src_b.c")
+src.c += "source1.c", "source2.c"
 ```
 
 ```
 
-LunaBuild allow you to short hand it as such:
+This will append `"source1.c"` and `"source2.c"` into the list named `c` under
+the scope of `src`. Which can be clearly interpreted as "collection of c source files"
 
 
-```py
-sources({
-    config("WHICH_SRC"): {
-        "select_a": "src_a.c",
-        "select_b": "src_b.c",
-        # more...
-    }
-})
-```
+It is also important to notice that not all databanks are in the forms of list.
+Some data banks served special purpose, as we will see below.
 
 
-It can also be extended easily for multiple choices and allow nesting.
+LunaBuild defines the following databanks and scope:
 
 
-You may also notice we no longer wrap the parameter with square bracket,
-this is also another short-hand, the array notation is not needed when
-there is only one element to add.
++ `src` (source files):
+    + `c` (c files, type: `[]`)
+    + `h` (headers or include directories, type: `[]`)
++ `flag` (source files):
+    + `cc` (compiler flags, type: `[]`)
+    + `ld` (linker flags, type: `[]`)
++ `config` (configuration options from `LConfig`)
+    + `CONFIG_<OptionName>` (any valid config name, type: `Any`)
++ `env` (environmental variables)
+    + `<Name>` (any valid env variable name, type: `Any`)
diff --git a/lunaix-os/scripts/build-tools/README.lconfig.md b/lunaix-os/scripts/build-tools/README.lconfig.md
new file mode 100644 (file)
index 0000000..56148e8
--- /dev/null
@@ -0,0 +1,381 @@
+# LunaConfig
+
+LunaConfig is a configuration language build on top of python. It allows user 
+to define options and the dependencies then reuse these options in their source 
+code for customisable build.
+
+Lunaix use LunaConfig to toggle various optional modules, subsystems or features
+and allow parameterised kernel build.
+
+
+## Design Motivation
+
+LunaConfig is designed as an improvement over the old-school Kconfig, 
+particularly to address its lack of hierarchical representation. Kconfig 
+organises options into a large, flat list, even though they appear in a 
+hierarchical menu structure. As a result, it can be very difficult to 
+determine which menu a configuration option belongs to without diving into 
+menuconfig.
+
+## Basic Constructs
+
+LunaConfig is presented as a file named `LConfig` and thus limited to one 
+LConfig per directory. This is because LunaConfig enforce user to organise each 
+directory to be a module or logical packaging of set of relavent functionalities.
+
+Each LunaConfig files is comprised by these major constructs:
+
+1. Config Component: `Terms` and `Groups`
+2. Config Import
+3. Native Python
+
+We will describe them in details in the following sections
+
+### Terms
+
+> The object that hold the value for customisation
+
+```py
+def feature1() -> bool:
+    return True
+```
+
+This defined an option named `feature1` with type of `bool` and the default
+value of `True`.
+
+### Groups
+
+> The logical object that is used to organise the `terms` or other `groups`
+
+A group is similar to term but without return type indication and return 
+statement
+
+And it is usually nested with other groups or terms.
+
+```py
+def group1():
+    def feature1() -> bool:
+        return True
+```
+
+### Import Mechanism
+
+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 LConfig using python's relative 
+import feature:
+
+```py
+from . import module
+```
+
+This import mechanism works like `#include` directive in C preprocessor,
+the `from . import` construct will be 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
+
+```py
+from .sub1.sub2 import module
+```
+
+This will be translated into `./sub1/sub2/module/LConfig`
+
+It can also be used in any valid python conditional branches to support 
+conditional import
+
+```py
+if condition_1:
+    from . import module1
+elif condition_2:
+    from . import module2
+```
+
+### Native Python
+
+Native Python code is fully supported in LunaConfig, this include everything 
+like packages import, functions, and class definitions. LunaConfig has the 
+ability to distinguish these code transparently from legitimate config code.
+
+However, there is one exception for this ability. Since LunaConfig treat 
+function definition as declaration of config component, to define a python 
+native function you will need to decorate it with `@native`. For example:
+
+```py
+@native
+def add(a, b):
+    return a + b
+
+def feature1() -> int:
+    return add(1, 2)
+```
+
+If a native function is nested in a config component, it will not be affected 
+by the scope and still avaliable globally. But this is not the case if it is 
+nested by another native function.
+
+If a config component is nested in a native function, then it is ignored by 
+LunaConfig
+
+## Term Typing
+
+A config term require it's value type to be specified explicitly. The type can 
+be a literal type, primitive type or composite type
+
+### Literal Typing
+
+A term can take a literal as it's type, doing this will ensure the value taken 
+by the term to be exactly same as the given type
+
+```py
+# OK
+def feature1() -> "value":
+    return "value"
+    # return "value2"
+
+# Error
+def feature1() -> "value":
+    return "abc"
+```
+
+### Primitive Typing
+
+A term can take any python's primitive type, the value taken by the term will 
+be type checked rather than value checked
+
+```py
+# OK
+def feature1() -> int:
+    return 123
+
+# Error
+def feature1() -> int:
+    return "abc"
+```
+
+### Composite Typing
+
+Any literal type or primitive type can be composite together via some structure 
+to form composite type. The checking on these type is depends on the composite 
+structure used. 
+
+#### Union Structure
+
+A Union structure realised through binary disjunctive connector `|`. The term 
+value must satisfy the type check against one of the composite type:
+
+```py
+def feature1() -> "a" | "b" | int:
+    # value can be either:
+    #  "a" or "b" or any integer
+    return "a"
+```
+
+## Component Attributes
+
+Each component have set of attributes to modify its behaviour and apperance, 
+these attributes are conveyed through decorators
+
+### Labels
+
+> usage: `Groups` and `Terms`
+
+Label provide a user friendly name for a component, which will be the first 
+choice of the name displayed by the interactive configuration tool
+
+```py
+@"This is feature 1"
+def feature1() -> bool:
+    return True
+```
+
+### Readonly
+
+> usage: `Terms`
+
+Marking a term to be readonly prevent explicit value update, that is, manual 
+update by user. Implicit value update initiated by `constrains` (more on this 
+later) is still allowed. 
+
+```py
+@readonly
+def feature1() -> bool:
+    return True
+```
+
+### Visibility
+
+> usage: `Groups` and `Terms`
+
+A component can be marked to be hidden thus prevent it from displayed by the 
+configuration tool, it does not affect the visibility in the code.
+
+If the decorated target is a group, then it is inherited by all it's 
+subordinates.
+
+```py
+@hidden
+def feature1() -> bool:
+    return True
+```
+
+### Flag
+
+> usage: `Terms`
+
+This is a short hand of both `@hidden` and `@readonly`
+
+```py
+@flag
+def feature1() -> bool:
+    return True
+```
+
+is same as
+
+```py
+@hidden
+@readonly
+def feature1() -> bool:
+    return True
+```
+
+### Parent
+
+> usage: `Groups` and `Terms`
+
+Any component can be defined outside of the logical hierachial structure (i.e., 
+the nested function) but still attached to it's physical hierachial structure.
+
+```py
+@parent := parent_group
+def feature1() -> bool:
+    return True
+
+def parent_group():
+    def feature2() -> bool:
+        return False
+```
+
+This will assigned `feature1` to be a subordinate of `parent_group`. Note that 
+the reference to `parent_group` does not required to be after the declaration.
+
+It is equivalent to 
+
+```py
+def parent_group():
+    def feature2() -> bool:
+        return False
+    
+    def feature1() -> bool:
+        return True
+```
+
+### Help Message
+
+> usage: `Groups` and `Terms`
+
+A help message will provide explainantion or comment of a component, to be used 
+and displayed by the configuration tool.
+
+The form of message is expressed using python's doc-string
+
+```py
+def feature1() -> bool:
+    """
+    This is help message for feature1
+    """
+
+    return True
+```
+
+## Directives
+
+There are builtin directives that is used inside the component.
+
+### Dependency
+
+> usage: `Groups` and `Terms`
+
+The dependency between components is described by various `require(...)` 
+directives. 
+
+This directive follows the python function call syntax and accept one argument 
+of a boolean expression as depedency predicate.
+
+Multiple `require` directives will be chainned together with logical conjunction 
+(i.e., `and`)
+
+```py
+def feature1() -> bool:
+    require (feature2 or feature3)
+    require (feature5)
+    
+    return True
+```
+
+This composition is equivalent to `feature5 and (feature2 or feature3)`, 
+indicate that the `feature1` require presences of both `feature5` and at least 
+one of `feature2` or `feature3`.
+
+If a dependency can not be satisfied, then the feature is disabled. This will 
+cause it neither to be shown in configuration tool nor referencable in source 
+code.
+
+Note that the dependency check only perform on the enablement of the node but 
+not visibility.
+
+### Constrains (Inverse Dependency)
+
+> usage: `Terms` with `bool` value type
+
+The `when(...)` directive allow changing the default value based on the 
+predicate evaluation on the value of other terms. Therefore, it can be only 
+used by `Terms` with `bool` as value type.
+
+Similar to `require`, it is based on the function call syntax and takes one 
+argument of conjunctive expression (i.e., boolean expression with only `and` 
+connectors).
+
+Multiple `when` will be chained together using logical disjunction (i.e., `or`)
+
+```py
+def feature1() -> bool:
+    when (feature2 is "value1" and feature5 is "value1")
+    when (feature3)
+```
+
+Which means `feature1` will takes value of `True` if one of the following is 
+satisfied:
+ + both `feature2` and `feature5` has value of `"value1"`
+ + `feature3` has value of True
+
+Notice that we do not have `return` statement at the end as the precense of 
+`when` will add a return automatically (or replace it if one exists)
+
+
+## Validation
+
+Since the language is based on Python, it's very tempting to pack in advanced 
+features or messy logic into the config file, which can make it harder to 
+follow—especially as the file grows in size.
+
+LunaConfig recognises this issue and includes a built-in linter to help users 
+identify such bad practices. It shows warnings by default but can be configured 
+to raise errors instead.
+
+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.
++ `class-def`: The presence of class definition
++ `complex-struct`: The present of complicated data structure such as `dict`.
++ `side-effect`: The presence of dynamic assignment of other config terms value.
++ `non-trivial-value`: The presence of non-trivial value being used as default 
+   value. This include every things **other than**:
+    + literal constant
+    + template literal
+
diff --git a/lunaix-os/scripts/build-tools/README.md b/lunaix-os/scripts/build-tools/README.md
new file mode 100644 (file)
index 0000000..12f49a3
--- /dev/null
@@ -0,0 +1,8 @@
+# Lunaix Build System
+
+The build system used by Lunaix is a hybird approach, with:
+
++ [LunaConfig](./README.lconfig.md) as compile-time configuration and build parameterisation
++ [LunaBuild](./README.lbuild.md) as soruce file collection and compiler/linker flags setting
++ Makefile for the file dependency check, auto-updating and other top-level managements.
+
diff --git a/lunaix-os/scripts/build-tools/integration/build_gen.py b/lunaix-os/scripts/build-tools/integration/build_gen.py
deleted file mode 100644 (file)
index 6b1cde7..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-from lbuild.api import BuildGenerator
-from lbuild.common import BuildEnvironment
-from lib.utils import join_path
-from os import getenv
-
-class MakefileBuildGen(BuildGenerator):
-    def __init__(self, out_dir, name = "lbuild.mkinc") -> None:
-        self.__path = join_path(out_dir, name)
-    
-    def emit_makearray(self, name, values):
-        r = []
-        r.append(f"{name} :=")
-        for v in values:
-            r.append(f"{v}")
-        return [" ".join(r)]
-
-    def generate(self, env: BuildEnvironment):
-        path = env.to_wspath(self.__path)
-        lines = []
-
-        opts = env.get_object("CC_OPTS", [])
-        lines.append("CFLAGS += %s"%(" ".join(opts)))
-        
-        opts = env.get_object("LD_OPTS", [])
-        lines.append("LDFLAGS += %s"%(" ".join(opts)))
-
-        arr = self.emit_makearray("_LBUILD_SRCS", env.srcs())
-        lines += arr
-
-        arr = self.emit_makearray("_LBUILD_HDRS", env.headers())
-        lines += arr
-
-        arr = self.emit_makearray("_LBUILD_INCS", env.includes())
-        lines += arr
-
-        with open(path, 'w') as f:
-            f.write("\n".join(lines))
-        
-
-def install_lbuild_functions(_env: BuildEnvironment):
-    def set_opts(env: BuildEnvironment, name, opts, override):
-        if not isinstance(opts, list):
-            opts = [opts]
-        
-        _opts = env.get_object(name, [])
-
-        if override:
-            _opts = opts
-        else:
-            _opts += opts
-
-        env.set_object(name, _opts)
-
-    def compile_opts(env: BuildEnvironment, opts, override=False):
-        set_opts(env, "CC_OPTS", opts, override)
-
-    def linking_opts(env: BuildEnvironment, opts, override=False):
-        set_opts(env, "LD_OPTS", opts, override)
-
-    def env(env, name, default=None):
-        return getenv(name, default)
-
-
-    _env.add_external_func(compile_opts)
-    _env.add_external_func(linking_opts)
-    _env.add_external_func(env)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/config_io.py b/lunaix-os/scripts/build-tools/integration/config_io.py
deleted file mode 100644 (file)
index 15e1ba8..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-from lcfg.api import ConfigIOProvider
-from lcfg.utils import is_basic
-
-import re
-
-class CHeaderConfigProvider(ConfigIOProvider):
-    def __init__(self, header_save, 
-                 header_prefix="CONFIG_") -> None:
-        self.__header_export = header_save
-        self.__prefix = header_prefix
-        self.__re = re.compile(r"^[A-Za-z0-9_]+$")
-
-    def export(self, env, config_dict):
-        lines = []
-        for k, v in config_dict.items():
-            result = [ "#define" ]
-            s = str.upper(k)
-            s = f"{self.__prefix}{s}"
-
-            if isinstance(v, str) and self.__re.match(v):
-                s = f"{s}_{str.upper(v)}"
-                v = ""
-    
-            result.append(s)
-            
-            v = self.serialize_value(v)
-            if v is None or v is False:
-                result.insert(0, "//")
-            elif isinstance(v, str):
-                result.append(v)
-
-            lines.append(" ".join(result))
-            
-        with open(self.__header_export, 'w') as f:
-            f.write("\n".join(lines))
-
-
-    def serialize_value(self, v):
-        if v is None:
-            return None
-        
-        if isinstance(v, bool):
-            return v
-        
-        if v and isinstance(v, str):
-            return f'"{v}"'
-        
-        if is_basic(v):
-            return str(v)
-        
-        raise ValueError(
-                f"serialising {type(v)}: not supported")
diff --git a/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py b/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py
deleted file mode 100644 (file)
index 2d755d7..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-from lbuild.api import ConfigProvider
-from lcfg.common import LConfigEnvironment
-
-class LConfigProvider(ConfigProvider):
-    def __init__(self, lcfg_env: LConfigEnvironment) -> None:
-        super().__init__()
-        self.__env = lcfg_env
-
-    def configured_value(self, name):
-        return self.__env.lookup_value(name)
-    
-    def has_config(self, name):
-        try:
-            v = self.__env.lookup_value(name)
-            return not not v
-        except:
-            return False
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/libmenu.py b/lunaix-os/scripts/build-tools/integration/libmenu.py
deleted file mode 100644 (file)
index 3b0492e..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-import curses
-import integration.libtui as tui
-from integration.libtui import ColorScope, TuiColor, Alignment, EventType
-
-def create_buttons(main_ctx, btn_defs, sizes = "*,*"):
-    size_defs = ",".join(['*'] * len(btn_defs))
-
-    layout = tui.FlexLinearLayout(main_ctx, "buttons", size_defs)
-    layout.orientation(tui.FlexLinearLayout.LANDSCAPE)
-    layout.set_size(*(sizes.split(',')[:2]))
-    layout.set_padding(1, 1, 1, 1)
-    layout.set_alignment(Alignment.CENTER | Alignment.BOT)
-
-    for i, btn_def in enumerate(btn_defs):
-        but1 = tui.TuiButton(main_ctx, "b1")
-        but1.set_text(btn_def["text"])
-        but1.set_click_callback(btn_def["onclick"])
-        but1.set_alignment(Alignment.CENTER)
-        
-        layout.set_cell(i, but1)
-
-    return layout
-
-def create_title(ctx, title):
-    _t = tui.TuiLabel(ctx, "label")
-    _t.set_text(title)
-    _t.set_local_pos(1, 0)
-    _t.set_alignment(Alignment.TOP | Alignment.CENTER)
-    _t.hightlight(True)
-    _t.pad_around(True)
-    return _t
-
-class ListView(tui.TuiObject):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-
-        self.__create_layout()
-
-        self.__sel_changed = None
-        self.__sel = None
-
-    def __create_layout(self):
-        hint_moveup = tui.TuiLabel(self._context, "movup")
-        hint_moveup.override_color(ColorScope.HINT)
-        hint_moveup.set_text("^^^ - MORE")
-        hint_moveup.set_visbility(False)
-        hint_moveup.set_alignment(Alignment.TOP)
-
-        hint_movedown = tui.TuiLabel(self._context, "movdown")
-        hint_movedown.override_color(ColorScope.HINT)
-        hint_movedown.set_text("vvv - MORE")
-        hint_movedown.set_visbility(False)
-        hint_movedown.set_alignment(Alignment.BOT)
-
-        list_ = tui.SimpleList(self._context, "list")
-        list_.set_size("*", "*")
-        list_.set_alignment(Alignment.CENTER | Alignment.TOP)
-
-        list_.set_onselected_cb(self._on_selected)
-        list_.set_onselection_change_cb(self._on_sel_changed)
-
-        scroll = tui.TuiScrollable(self._context, "scroll")
-        scroll.set_size("*", "*")
-        scroll.set_alignment(Alignment.CENTER)
-        scroll.set_content(list_)
-
-        layout = tui.FlexLinearLayout(
-                    self._context, f"main_layout", "2,*,2")
-        layout.set_size("*", "*")
-        layout.set_alignment(Alignment.CENTER)
-        layout.orientation(tui.FlexLinearLayout.PORTRAIT)
-        layout.set_parent(self)
-
-        layout.set_cell(0, hint_moveup)
-        layout.set_cell(1, scroll)
-        layout.set_cell(2, hint_movedown)
-
-        self.__hint_up   = hint_moveup
-        self.__hint_down = hint_movedown
-        self.__list      = list_
-        self.__scroll    = scroll
-        self.__layout    = layout
-
-    def add_item(self, item):
-        self.__list.add_item(item)
-
-    def clear(self):
-        self.__list.clear()
-
-    def on_draw(self):
-        super().on_draw()
-        
-        more_above = not self.__scroll.reached_top()
-        more_below = not self.__scroll.reached_last()
-        self.__hint_up.set_visbility(more_above)
-        self.__hint_down.set_visbility(more_below)
-
-        self.__layout.on_draw()
-
-    def on_layout(self):
-        super().on_layout()
-        self.__layout.on_layout()
-
-    def _on_sel_changed(self, listv, prev, new):
-        h = self.__scroll._size.y()
-        self.__scroll.set_scrollY((new + 1) // h * h)
-
-        if self.__sel_changed:
-            self.__sel_changed(listv, prev, new)
-
-    def _on_selected(self, listv, index, item):
-        if self.__sel:
-            self.__sel(listv, index, item)
-    
-    def set_onselected_cb(self, cb):
-        self.__sel = cb
-
-    def set_onselect_changed_cb(self, cb):
-        self.__sel_changed = cb
-
-class Dialogue(tui.TuiContext):
-    Pending = 0
-    Yes = 1
-    No = 2
-    Abort = 3
-    def __init__(self, session, title = "", content = "", input=False,
-                 ok_btn = "OK", no_btn = "No", abort_btn = None):
-        super().__init__(session)
-
-        self.__btns = [
-            { "text": ok_btn, "onclick": lambda x: self._ok_onclick() }
-        ]
-
-        if no_btn:
-            self.__btns.append({ 
-                "text": no_btn, 
-                "onclick": lambda x: self._no_onclick() 
-            })
-        if abort_btn:
-            self.__btns.append({ 
-                "text": abort_btn, 
-                "onclick": lambda x: self._abort_onclick() 
-            })
-
-        self.__title_txt = title
-        self.__status = Dialogue.Pending
-        self.__content = content
-        self.__input_dialog = input
-        self._textbox = None
-
-        self.set_size("70", "0.5*")
-        self.set_alignment(Alignment.CENTER)
-
-    def set_content(self, content):
-        self.__content = content
-
-    def set_input_dialogue(self, yes):
-        self.__input_dialog = yes
-
-    def prepare(self):
-        self.__create_layout(self.__title_txt)
-
-    def _handle_key_event(self, key):
-        if key == 27:
-            self.__close()
-            return
-        super()._handle_key_event(key)
-        
-    
-    def _ok_onclick(self):
-        self.__status = Dialogue.Yes
-        self.__close()
-
-    def _no_onclick(self):
-        self.__status = Dialogue.No
-        self.__close()
-
-    def _abort_onclick(self):
-        self.__status = Dialogue.Abort
-        self.__close()
-
-    def __create_layout(self, title):
-        panel  = tui.TuiPanel(self, "panel")
-        layout = tui.FlexLinearLayout(self, "layout", "*,3")
-        btn_grp = create_buttons(self, self.__btns)
-        t = create_title(self, title)
-        content = self.__create_content()
-
-        self.__title = t
-        self.__layout = layout
-        self.__panel = panel
-
-        panel._dyn_size.set(self._dyn_size)
-        panel._local_pos.set(self._local_pos)
-        panel.set_alignment(self._align)
-        panel.drop_shadow(1, 2)
-        panel.border(True)
-
-        layout.orientation(tui.FlexLinearLayout.PORTRAIT)
-        layout.set_size("*", "*")
-        layout.set_padding(4, 1, 1, 1)
-
-        t.set_alignment(Alignment.CENTER | Alignment.TOP)
-
-        layout.set_cell(0, content)
-        layout.set_cell(1, btn_grp)
-
-        panel.add(t)
-        panel.add(layout)
-
-        self.set_root(panel)
-
-    def __create_content(self):
-        text = None
-        if isinstance(self.__content, str):
-            text = tui.TuiTextBlock(self, "tb")
-            text.set_size("0.6*", "0.5*")
-            text.set_alignment(Alignment.CENTER)
-            text.set_text(self.__content)
-        elif self.__content is not None:
-            return self.__content
-        
-        if not self.__input_dialog:
-            self.set_size(h = "20")
-            return text
-        
-        tb = tui.TuiTextBox(self, "input")
-        tb.set_size("0.5*", "3")
-        tb.set_alignment(Alignment.CENTER)
-        
-        if text:
-            layout = tui.FlexLinearLayout(self, "layout", "*,5")
-            layout.orientation(tui.FlexLinearLayout.PORTRAIT)
-            layout.set_size("*", "*")
-            layout.set_cell(0, text)
-            layout.set_cell(1, tb)
-        else:
-            layout = tb
-            self.set_size(h = "10")
-        
-        self.set_curser_mode(1)
-
-        self._textbox = tb
-
-        return layout
-        
-    def __close(self):
-        self.session().pop_context()
-
-    def status(self):
-        return self.__status
-    
-    def show(self, title=None):
-        if title:
-            self.__title.set_text(title)
-        self.session().push_context(self)
-
-
-def show_dialog(session, title, text):
-    dia = Dialogue(session, title=title, content=text, no_btn=None)
-    dia.show()
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/libtui.py b/lunaix-os/scripts/build-tools/integration/libtui.py
deleted file mode 100644 (file)
index 1441836..0000000
+++ /dev/null
@@ -1,1257 +0,0 @@
-#
-# libtui - TUI framework using ncurses
-#  (c) 2024 Lunaixsky
-# 
-# I sware, this is the last time I ever touch 
-#  any sort of the GUI messes.
-#
-
-import curses
-import re
-import curses.panel as cpanel
-import curses.textpad as textpad
-import textwrap
-
-def __invoke_fn(obj, fn, *args):
-    try:
-        fn(*args)
-    except:
-        _id = obj._id if obj else "<root>"
-        raise Exception(_id, str(fn), args)
-
-def resize_safe(obj, co, y, x):
-    __invoke_fn(obj, co.resize, y, x)
-
-def move_safe(obj, co, y, x):
-    __invoke_fn(obj, co.move, y, x)
-    
-def addstr_safe(obj, co, y, x, str, *args):
-    __invoke_fn(obj, co.addstr, y, x, str, *args)
-
-class _TuiColor:
-    def __init__(self, v) -> None:
-        self.__v = v
-    def __int__(self):
-        return self.__v
-    def bright(self):
-        return self.__v + 8
-
-class TuiColor:
-    black     = _TuiColor(curses.COLOR_BLACK)
-    red       = _TuiColor(curses.COLOR_RED)
-    green     = _TuiColor(curses.COLOR_GREEN)
-    yellow    = _TuiColor(curses.COLOR_YELLOW)
-    blue      = _TuiColor(curses.COLOR_BLUE)
-    magenta   = _TuiColor(curses.COLOR_MAGENTA)
-    cyan      = _TuiColor(curses.COLOR_CYAN)
-    white     = _TuiColor(curses.COLOR_WHITE)
-
-class Alignment:
-    LEFT    = 0b000001
-    RIGHT   = 0b000010
-    CENTER  = 0b000100
-    TOP     = 0b001000
-    BOT     = 0b010000
-    ABS     = 0b000000
-    REL     = 0b100000
-
-class ColorScope:
-    WIN       = 1
-    PANEL     = 2
-    TEXT      = 3
-    TEXT_HI   = 4
-    SHADOW    = 5
-    SELECT    = 6
-    HINT      = 7
-    BOX       = 8
-
-class EventType:
-    E_KEY = 0
-    E_REDRAW = 1
-    E_QUIT = 2
-    E_TASK = 3
-    E_CHFOCUS = 4
-    E_M_FOCUS = 0b10000000
-
-    def focused_only(t):
-        return (t & EventType.E_M_FOCUS)
-    
-    def value(t):
-        return t & ~EventType.E_M_FOCUS
-    
-    def key_press(t):
-        return (t & ~EventType.E_M_FOCUS) == EventType.E_KEY
-
-class Matchers:
-    RelSize = re.compile(r"(?P<mult>[0-9]+(?:\.[0-9]+)?)?\*(?P<add>[+-][0-9]+)?")
-
-class BoundExpression:
-    def __init__(self, expr = None):
-        self._mult = 0
-        self._add  = 0
-
-        if expr:
-            self.update(expr)
-
-    def set_pair(self, mult, add):
-        self._mult = mult
-        self._add  = add
-
-    def set(self, expr):
-        self._mult = expr._mult
-        self._add  = expr._add
-        
-    def update(self, expr):
-        if isinstance(expr, int):
-            m = None
-        else:
-            m = Matchers.RelSize.match(expr)
-
-        if m:
-            g = m.groupdict()
-            mult = 1 if not g["mult"] else float(g["mult"])
-            add = 0 if not g["add"] else int(g["add"])
-            self._mult = mult
-            self._add  = add
-        else:
-            self.set_pair(0, int(expr))
-
-    def calc(self, ref_val):
-        return int(self._mult * ref_val + self._add)
-    
-    def absolute(self):
-        return self._mult == 0
-    
-    def nullity(self):
-        return self._mult == 0 and self._add == 0
-    
-    def scale_mult(self, scalar):
-        self._mult *= scalar
-        return self
-    
-    @staticmethod
-    def normalise(*exprs):
-        v = BoundExpression()
-        for e in exprs:
-            v += e
-        return [e.scale_mult(1 / v._mult) for e in exprs]
-
-    def __add__(self, b):
-        v = BoundExpression()
-        v.set(self)
-        v._mult += b._mult
-        v._add  += b._add
-        return v
-
-    def __sub__(self, b):
-        v = BoundExpression()
-        v.set(self)
-        v._mult -= b._mult
-        v._add  -= b._add
-        return v
-    
-    def __iadd__(self, b):
-        self._mult += b._mult
-        self._add  += b._add
-        return self
-
-    def __isub__(self, b):
-        self._mult -= b._mult
-        self._add  -= b._add
-        return self
-    
-    def __rmul__(self, scalar):
-        v = BoundExpression()
-        v.set(self)
-        v._mult *= scalar
-        v._add *= scalar
-        return v
-
-    def __truediv__(self, scalar):
-        v = BoundExpression()
-        v.set(self)
-        v._mult /= float(scalar)
-        v._add /= scalar
-        return v
-    
-class DynamicBound:
-    def __init__(self):
-        self.__x = BoundExpression()
-        self.__y = BoundExpression()
-    
-    def dyn_x(self):
-        return self.__x
-
-    def dyn_y(self):
-        return self.__y
-    
-    def resolve(self, ref_w, ref_h):
-        return (self.__y.calc(ref_h), self.__x.calc(ref_w))
-    
-    def set(self, dyn_bound):
-        self.__x.set(dyn_bound.dyn_x())
-        self.__y.set(dyn_bound.dyn_y())
-
-class Bound:
-    def __init__(self) -> None:
-        self.__x = 0
-        self.__y = 0
-
-    def shrinkX(self, dx):
-        self.__x -= dx
-    def shrinkY(self, dy):
-        self.__y -= dy
-
-    def growX(self, dx):
-        self.__x += dx
-    def growY(self, dy):
-        self.__y += dy
-
-    def resetX(self, x):
-        self.__x = x
-    def resetY(self, y):
-        self.__y = y
-
-    def update(self, dynsz, ref_bound):
-        y, x = dynsz.resolve(ref_bound.x(), ref_bound.y())
-        self.__x = x
-        self.__y = y
-
-    def reset(self, x, y):
-        self.__x, self.__y = x, y
-
-    def x(self):
-        return self.__x
-    
-    def y(self):
-        return self.__y
-    
-    def yx(self, scale = 1):
-        return int(self.__y * scale), int(self.__x * scale)
-
-class TuiStackWindow:
-    def __init__(self, obj) -> None:
-        self.__obj = obj
-        self.__win = curses.newwin(0, 0)
-        self.__pan = cpanel.new_panel(self.__win)
-        self.__pan.hide()
-
-    def resize(self, h, w):
-        resize_safe(self.__obj, self.__win, h, w)
-    
-    def relocate(self, y, x):
-        move_safe(self.__obj, self.__pan, y, x)
-
-    def set_geometric(self, h, w, y, x):
-        resize_safe(self.__obj, self.__win, h, w)
-        move_safe(self.__obj, self.__pan, y, x)
-
-    def set_background(self, color_scope):
-        self.__win.bkgd(' ', curses.color_pair(color_scope))
-
-    def show(self):
-        self.__pan.show()
-
-    def hide(self):
-        self.__pan.hide()
-    
-    def send_back(self):
-        self.__pan.bottom()
-
-    def send_front(self):
-        self.__pan.top()
-
-    def window(self):
-        return self.__win
-
-class SpatialObject:
-    def __init__(self) -> None:
-        self._local_pos = DynamicBound()
-        self._pos = Bound()
-        self._dyn_size = DynamicBound()
-        self._size = Bound()
-        self._margin = (0, 0, 0, 0)
-        self._padding = (0, 0, 0, 0)
-        self._align = Alignment.TOP | Alignment.LEFT
-
-    def set_local_pos(self, x, y):
-        self._local_pos.dyn_x().update(x)
-        self._local_pos.dyn_y().update(y)
-
-    def set_alignment(self, align):
-        self._align = align
-
-    def set_size(self, w = None, h = None):
-        if w:
-            self._dyn_size.dyn_x().update(w)
-        if h:
-            self._dyn_size.dyn_y().update(h)
-
-    def set_margin(self, top, right, bottom, left):
-        self._margin = (top, right, bottom, left)
-
-    def set_padding(self, top, right, bottom, left):
-        self._padding = (top, right, bottom, left)
-
-    def reset(self):
-        self._pos.reset(0, 0)
-        self._size.reset(0, 0)
-
-    def deduce_spatial(self, constrain):
-        self.reset()
-        self.__satisfy_bound(constrain)
-        self.__satisfy_alignment(constrain)
-        self.__satisfy_margin(constrain)
-        self.__satisfy_padding(constrain)
-
-        self.__to_corner_pos(constrain)
-
-    def __satisfy_alignment(self, constrain):
-        local_pos = self._local_pos
-        cbound = constrain._size
-        size = self._size
-
-        cy, cx = cbound.yx()
-        ry, rx = local_pos.resolve(cx, cy)
-        ay, ax = size.yx(0.5)
-        
-        if self._align & Alignment.CENTER:
-            ax = cx // 2
-            ay = cy // 2
-        
-        if self._align & Alignment.BOT:
-            ay = min(cy - ay, cy - 1)
-            ry = -ry
-        elif self._align & Alignment.TOP:
-            ay = size.y() // 2
-
-        if self._align & Alignment.RIGHT:
-            ax = cx - ax
-            rx = -rx
-        elif self._align & Alignment.LEFT:
-            ax = size.x() // 2
-
-        self._pos.reset(ax + rx, ay + ry)
-
-    def __satisfy_margin(self, constrain):
-        tm, lm, bm, rm = self._margin
-        
-        self._pos.growX(rm - lm)
-        self._pos.growY(bm - tm)
-
-    def __satisfy_padding(self, constrain):
-        csize = constrain._size
-        ch, cw = csize.yx()
-        h, w = self._size.yx(0.5)
-        y, x = self._pos.yx()
-
-        tp, lp, bp, rp = self._padding
-
-        if not (tp or lp or bp or rp):
-            return
-
-        dtp = min(y - h, tp) - tp
-        dbp = min(ch - (y + h), bp) - bp
-
-        dlp = min(x - w, lp) - lp
-        drp = min(cw - (x + w), rp) - rp
-
-        self._size.growX(drp + dlp)
-        self._size.growY(dtp + dbp)
-
-    def __satisfy_bound(self, constrain):
-        self._size.update(self._dyn_size, constrain._size)
-
-    def __to_corner_pos(self, constrain):
-        h, w = self._size.yx(0.5)
-        g_pos = constrain._pos
-
-        self._pos.shrinkX(w)
-        self._pos.shrinkY(h)
-
-        self._pos.growX(g_pos.x())
-        self._pos.growY(g_pos.y())
-        
-
-class TuiObject(SpatialObject):
-    def __init__(self, context, id):
-        super().__init__()
-        self._id = id
-        self._context = context
-        self._parent = None
-        self._visible = True
-        self._focused = False
-
-    def set_parent(self, parent):
-        self._parent = parent
-
-    def canvas(self):
-        if self._parent:
-            return self._parent.canvas()
-        return (self, self._context.window())
-    
-    def context(self):
-        return self._context
-    
-    def session(self):
-        return self._context.session()
-
-    def on_create(self):
-        pass
-
-    def on_destory(self):
-        pass
-
-    def on_quit(self):
-        pass
-
-    def on_layout(self):
-        if self._parent:
-            self.deduce_spatial(self._parent)
-
-    def on_draw(self):
-        pass
-
-    def on_event(self, ev_type, ev_arg):
-        pass
-
-    def on_focused(self):
-        self._focused = True
-
-    def on_focus_lost(self):
-        self._focused = False
-
-    def set_visbility(self, visible):
-        self._visible = visible
-
-    def do_draw(self):
-        if self._visible:
-            self.on_draw()
-    
-    def do_layout(self):
-        self.on_layout()
-
-class TuiWidget(TuiObject):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-    
-    def on_layout(self):
-        super().on_layout()
-        
-        co, _ = self.canvas()
-
-        y, x = co._pos.yx()
-        self._pos.shrinkX(x)
-        self._pos.shrinkY(y)
-
-class TuiContainerObject(TuiObject):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self._children = []
-
-    def add(self, child):
-        child.set_parent(self)
-        self._children.append(child)
-
-    def children(self):
-        return self._children
-
-    def on_create(self):
-        super().on_create()
-        for child in self._children:
-            child.on_create()
-
-    def on_destory(self):
-        super().on_destory()
-        for child in self._children:
-            child.on_destory()
-    
-    def on_quit(self):
-        super().on_quit()
-        for child in self._children:
-            child.on_quit()
-
-    def on_layout(self):
-        super().on_layout()
-        for child in self._children:
-            child.do_layout()
-
-    def on_draw(self):
-        super().on_draw()
-        for child in self._children:
-            child.do_draw()
-
-    def on_event(self, ev_type, ev_arg):
-        super().on_event(ev_type, ev_arg)
-        for child in self._children:
-            child.on_event(ev_type, ev_arg)
-
-
-class TuiScrollable(TuiObject):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self.__spos = Bound()
-
-        self.__pad = curses.newpad(1, 1)
-        self.__pad.bkgd(' ', curses.color_pair(ColorScope.PANEL))
-        self.__pad_panel = cpanel.new_panel(self.__pad)
-        self.__content = None
-
-    def canvas(self):
-        return (self, self.__pad)
-
-    def set_content(self, content):
-        self.__content = content
-        self.__content.set_parent(self)
-
-    def set_scrollY(self, y):
-        self.__spos.resetY(y)
-
-    def set_scrollX(self, x):
-        self.__spos.resetX(x)
-
-    def reached_last(self):
-        off = self.__spos.y() + self._size.y()
-        return off >= self.__content._size.y()
-    
-    def reached_top(self):
-        return self.__spos.y() < self._size.y()
-
-    def on_layout(self):
-        super().on_layout()
-
-        if not self.__content:
-            return
-        
-        self.__content.on_layout()
-                
-        h, w = self._size.yx()
-        ch, cw = self.__content._size.yx()
-        sh, sw = max(ch, h), max(cw, w)
-
-        self.__spos.resetX(min(self.__spos.x(), max(cw, w) - w))
-        self.__spos.resetY(min(self.__spos.y(), max(ch, h) - h))
-
-        resize_safe(self, self.__pad, sh, sw)
-
-    def on_draw(self):
-        if not self.__content:
-            return
-        
-        self.__pad_panel.top()
-        self.__content.on_draw()
-
-        wminy, wminx = self._pos.yx()
-        wmaxy, wmaxx = self._size.yx()
-        wmaxy, wmaxx = wmaxy + wminy, wmaxx + wminx
-
-        self.__pad.refresh(*self.__spos.yx(), 
-                            wminy, wminx, wmaxy - 1, wmaxx - 1)
-
-
-class Layout(TuiContainerObject):
-    class Cell(TuiObject):
-        def __init__(self, context):
-            super().__init__(context, "cell")
-            self.__obj = None
-
-        def set_obj(self, obj):
-            self.__obj = obj
-            self.__obj.set_parent(self)
-
-        def on_create(self):
-            if self.__obj:
-                self.__obj.on_create()
-
-        def on_destory(self):
-            if self.__obj:
-                self.__obj.on_destory()
-
-        def on_quit(self):
-            if self.__obj:
-                self.__obj.on_quit()
-
-        def on_layout(self):
-            super().on_layout()
-            if self.__obj:
-                self.__obj.do_layout()
-
-        def on_draw(self):
-            if self.__obj:
-                self.__obj.do_draw()
-
-        def on_event(self, ev_type, ev_arg):
-            if self.__obj:
-                self.__obj.on_event(ev_type, ev_arg)
-    
-    def __init__(self, context, id, ratios):
-        super().__init__(context, id)
-
-        rs = [BoundExpression(r) for r in ratios.split(',')]
-        self._rs = BoundExpression.normalise(*rs)
-
-        for _ in range(len(self._rs)):
-            cell = Layout.Cell(self._context)
-            super().add(cell)
-
-        self._adjust_to_fit()
-
-    def _adjust_to_fit(self):
-        pass
-
-    def add(self, child):
-        raise RuntimeError("invalid operation")
-    
-    def set_cell(self, i, obj):
-        if i > len(self._children):
-            raise ValueError(f"cell #{i} out of bound")
-        
-        self._children[i].set_obj(obj)
-
-
-class FlexLinearLayout(Layout):
-    LANDSCAPE = 0
-    PORTRAIT = 1
-    def __init__(self, context, id, ratios):
-        self.__horizontal = False
-
-        super().__init__(context, id, ratios)
-
-    def orientation(self, orient):
-        self.__horizontal = orient == FlexLinearLayout.LANDSCAPE
-    
-    def on_layout(self):
-        self.__apply_ratio()
-        super().on_layout()
-    
-    def _adjust_to_fit(self):
-        sum_abs = BoundExpression()
-        i = 0
-        for r in self._rs:
-            if r.absolute():
-                sum_abs += r
-            else:
-                i += 1
-
-        sum_abs /= i
-        for i, r in enumerate(self._rs):
-            if not r.absolute():
-                self._rs[i] -= sum_abs
-
-    def __apply_ratio(self):
-        if self.__horizontal:
-            self.__adjust_horizontal()
-        else:
-            self.__adjust_vertical()
-        
-    def __adjust_horizontal(self):
-        acc = BoundExpression()
-        for r, cell in zip(self._rs, self.children()):
-            cell._dyn_size.dyn_y().set_pair(1, 0)
-            cell._dyn_size.dyn_x().set(r)
-
-            cell.set_alignment(Alignment.LEFT)
-            cell._local_pos.dyn_y().set_pair(0, 0)
-            cell._local_pos.dyn_x().set(acc)
-
-            acc += r
-
-    def __adjust_vertical(self):
-        acc = BoundExpression()
-        for r, cell in zip(self._rs, self.children()):
-            cell._dyn_size.dyn_x().set_pair(1, 0)
-            cell._dyn_size.dyn_y().set(r)
-
-            cell.set_alignment(Alignment.TOP | Alignment.CENTER)
-            cell._local_pos.dyn_x().set_pair(0, 0)
-            cell._local_pos.dyn_y().set(acc)
-
-            acc += r
-
-
-class TuiPanel(TuiContainerObject):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-
-        self.__use_border = False
-        self.__use_shadow = False
-        self.__shadow_param = (0, 0)
-
-        self.__swin = TuiStackWindow(self)
-        self.__shad = TuiStackWindow(self)
-
-        self.__swin.set_background(ColorScope.PANEL)
-        self.__shad.set_background(ColorScope.SHADOW)
-
-    def canvas(self):
-        return (self, self.__swin.window())
-
-    def drop_shadow(self, off_y, off_x):
-        self.__shadow_param = (off_y, off_x)
-        self.__use_shadow = not (off_y == off_x and off_y == 0)
-
-    def border(self, _b):
-        self.__use_border = _b
-
-    def bkgd_override(self, scope):
-        self.__swin.set_background(scope)
-
-    def on_layout(self):
-        super().on_layout()
-
-        self.__swin.hide()
-
-        h, w = self._size.y(), self._size.x()
-        y, x = self._pos.y(), self._pos.x()
-        self.__swin.set_geometric(h, w, y, x)
-        
-        if self.__use_shadow:
-            sy, sx = self.__shadow_param
-            self.__shad.set_geometric(h, w, y + sy, x + sx)
-
-    def on_destory(self):
-        super().on_destory()
-        self.__swin.hide()
-        self.__shad.hide()
-
-    def on_draw(self):
-        win = self.__swin.window()
-        win.noutrefresh()
-
-        if self.__use_border:
-            win.border()
-        
-        if self.__use_shadow:
-            self.__shad.show()
-        else:
-            self.__shad.hide()
-
-        self.__swin.show()
-        self.__swin.send_front()
-
-        super().on_draw()
-
-class TuiLabel(TuiWidget):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self._text = "TuiLabel"
-        self._wrapped = []
-
-        self.__auto_fit = True
-        self.__trunc = False
-        self.__dopad = False
-        self.__highlight = False
-        self.__color_scope = -1
-
-    def __try_fit_text(self, txt):
-        if self.__auto_fit:
-            self._dyn_size.dyn_x().set_pair(0, len(txt))
-            self._dyn_size.dyn_y().set_pair(0, 1)
-
-    def __pad_text(self):
-        for i, t in enumerate(self._wrapped):
-            self._wrapped[i] = str.rjust(t, self._size.x())
-    
-    def set_text(self, text):
-        self._text = text
-        self.__try_fit_text(text)
-
-    def override_color(self, color = -1):
-        self.__color_scope = color
-
-    def auto_fit(self, _b):
-        self.__auto_fit = _b
-
-    def truncate(self, _b):
-        self.__trunc = _b
-
-    def hightlight(self, _b):
-        self.__highlight = _b
-
-    def pad_around(self, _b):
-        self.__dopad = _b
-
-    def on_layout(self):
-        txt = self._text
-        if self.__dopad:
-            txt = f" {txt} "
-            self.__try_fit_text(txt)
-        
-        super().on_layout()
-
-        if len(txt) <= self._size.x():
-            self._wrapped = [txt]
-            self.__pad_text()
-            return
-
-        if not self.__trunc:
-            txt = txt[:self._size.x() - 1]
-            self._wrapped = [txt]
-            self.__pad_text()
-            return
-
-        self._wrapped = textwrap.wrap(txt, self._size.x())
-        self.__pad_text()
-
-    def on_draw(self):
-        _, win = self.canvas()
-        y, x = self._pos.yx()
-        
-        if self.__color_scope != -1:
-            color = curses.color_pair(self.__color_scope)
-        elif self.__highlight:
-            color = curses.color_pair(ColorScope.TEXT_HI)
-        else:
-            color = curses.color_pair(ColorScope.TEXT)
-
-        for i, t in enumerate(self._wrapped):
-            addstr_safe(self, win, y + i, x, t, color)
-
-
-class TuiTextBlock(TuiWidget):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self.__lines = []
-        self.__wrapped = []
-        self.__fit_to_height = False
-    
-    def set_text(self, text):
-        text = textwrap.dedent(text)
-        self.__lines = text.split('\n')
-        if self.__fit_to_height:
-            self._dyn_size.dyn_y().set_pair(0, 0)
-
-    def height_auto_fit(self, yes):
-        self.__fit_to_height = yes
-
-    def on_layout(self):
-        super().on_layout()
-
-        self.__wrapped.clear()
-        for t in self.__lines:
-            if not t:
-                self.__wrapped.append(t)
-                continue
-            wrap = textwrap.wrap(t, self._size.x())
-            self.__wrapped += wrap
-
-        if self._dyn_size.dyn_y().nullity():
-            h = len(self.__wrapped)
-            self._dyn_size.dyn_y().set_pair(0, h)
-
-            # redo layouting
-            super().on_layout()
-
-    def on_draw(self):
-        _, win = self.canvas()
-        y, x = self._pos.yx()
-        
-        color = curses.color_pair(ColorScope.TEXT)
-        for i, t in enumerate(self.__wrapped):
-            addstr_safe(self, win, y + i, x, t, color)
-
-
-class TuiTextBox(TuiWidget):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self.__box = TuiStackWindow(self)
-        self.__box.set_background(ColorScope.PANEL)
-        self.__textb = textpad.Textbox(self.__box.window(), True)
-        self.__textb.stripspaces = True
-        self.__str = ""
-        self.__scheduled_edit = False
-
-        self._context.focus_group().register(self, 0)
-
-    def __validate(self, x):
-        if x == 10 or x == 9:
-            return 7
-        return x
-    
-    def on_layout(self):
-        super().on_layout()
-
-        co, _ = self.canvas()
-        h, w = self._size.yx()
-        y, x = self._pos.yx()
-        cy, cx = co._pos.yx()
-        y, x = y + cy, x + cx
-
-        self.__box.hide()
-        self.__box.set_geometric(1, w - 1, y + h // 2, x + 1)
-
-    def on_draw(self):
-        self.__box.show()
-        self.__box.send_front()
-        
-        _, cwin = self.canvas()
-
-        h, w = self._size.yx()
-        y, x = self._pos.yx()
-        textpad.rectangle(cwin, y, x, y + h - 1, x+w)
-
-        win = self.__box.window()
-        win.touchwin()
-
-    def __edit(self):
-        self.__str = self.__textb.edit(lambda x: self.__validate(x))
-        self.session().schedule(EventType.E_CHFOCUS)
-        self.__scheduled_edit = False
-
-    def get_text(self):
-        return self.__str
-    
-    def on_focused(self):
-        self.__box.set_background(ColorScope.BOX)
-        if not self.__scheduled_edit:
-            # edit will block, defer to next update cycle
-            self.session().schedule_task(self.__edit)
-            self.__scheduled_edit = True
-
-    def on_focus_lost(self):
-        self.__box.set_background(ColorScope.PANEL)
-        
-
-class SimpleList(TuiWidget):
-    class Item:
-        def __init__(self) -> None:
-            pass
-        def get_text(self):
-            return "list_item"
-        def on_selected(self):
-            pass
-        def on_key_pressed(self, key):
-            pass
-
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self.__items = []
-        self.__selected = 0
-        self.__on_sel_confirm_cb = None
-        self.__on_sel_change_cb = None
-
-        self._context.focus_group().register(self)
-
-    def set_onselected_cb(self, cb):
-        self.__on_sel_confirm_cb = cb
-
-    def set_onselection_change_cb(self, cb):
-        self.__on_sel_change_cb = cb
-
-    def count(self):
-        return len(self.__items)
-    
-    def index(self):
-        return self.__selected
-
-    def add_item(self, item):
-        self.__items.append(item)
-
-    def clear(self):
-        self.__items.clear()
-
-    def on_layout(self):
-        super().on_layout()
-        self.__selected = min(self.__selected, len(self.__items))
-        self._size.resetY(len(self.__items) + 1)
-
-    def on_draw(self):
-        _, win = self.canvas()
-        w = self._size.x()
-
-        for i, item in enumerate(self.__items):
-            color = curses.color_pair(ColorScope.TEXT)
-            if i == self.__selected:
-                if self._focused:
-                    color = curses.color_pair(ColorScope.SELECT)
-                else:
-                    color = curses.color_pair(ColorScope.BOX)
-            
-            txt = str.ljust(item.get_text(), w)
-            txt = txt[:w]
-            addstr_safe(self, win, i, 0, txt, color)
-
-    def on_event(self, ev_type, ev_arg):
-        if not EventType.key_press(ev_type):
-            return
-        
-        if len(self.__items) == 0:
-            return
-
-        sel = self.__items[self.__selected]
-
-        if ev_arg == 10:
-            sel.on_selected()
-            
-            if self.__on_sel_confirm_cb:
-                self.__on_sel_confirm_cb(self, self.__selected, sel)
-            return
-        
-        sel.on_key_pressed(ev_arg)
-        
-        if (ev_arg != curses.KEY_DOWN and 
-            ev_arg != curses.KEY_UP):
-            return
-
-        prev = self.__selected
-        if ev_arg == curses.KEY_DOWN:
-            self.__selected += 1
-        else:
-            self.__selected -= 1
-
-        self.__selected = max(self.__selected, 0)
-        self.__selected = self.__selected % len(self.__items)
-        
-        if self.__on_sel_change_cb:
-            self.__on_sel_change_cb(self, prev, self.__selected)
-
-
-class TuiButton(TuiLabel):
-    def __init__(self, context, id):
-        super().__init__(context, id)
-        self.__onclick = None
-
-        context.focus_group().register(self)
-
-    def set_text(self, text):
-        return super().set_text(f"<{text}>")
-
-    def set_click_callback(self, cb):
-        self.__onclick = cb
-
-    def hightlight(self, _b):
-        raise NotImplemented()
-    
-    def on_draw(self):
-        _, win = self.canvas()
-        y, x = self._pos.yx()
-        
-        if self._focused:
-            color = curses.color_pair(ColorScope.SELECT)
-        else:
-            color = curses.color_pair(ColorScope.TEXT)
-
-        addstr_safe(self, win, y, x, self._wrapped[0], color)
-    
-    def on_event(self, ev_type, ev_arg):
-        if not EventType.focused_only(ev_type):
-            return
-        if not EventType.key_press(ev_type):
-            return
-        
-        if ev_arg == ord('\n') and self.__onclick:
-            self.__onclick(self)
-
-
-class TuiSession:
-    def __init__(self) -> None:
-        self.stdsc = curses.initscr()
-        curses.start_color()
-
-        curses.noecho()
-        curses.cbreak()
-
-        self.__context_stack = []
-        self.__sched_events = []
-
-        ws = self.window_size()
-        self.__win = curses.newwin(*ws)
-        self.__winbg = curses.newwin(*ws)
-        self.__panbg = cpanel.new_panel(self.__winbg)
-
-        self.__winbg.bkgd(' ', curses.color_pair(ColorScope.WIN))
-
-        self.__win.timeout(50)
-        self.__win.keypad(True)
-
-    def window_size(self):
-        return self.stdsc.getmaxyx()
-
-    def set_color(self, scope, fg, bg):
-        curses.init_pair(scope, int(fg), int(bg))
-
-    def schedule_redraw(self):
-        self.schedule(EventType.E_REDRAW)
-
-    def schedule_task(self, task):
-        self.schedule(EventType.E_REDRAW)
-        self.schedule(EventType.E_TASK, task)
-
-    def schedule(self, event, arg = None):
-        if len(self.__sched_events) > 0:
-            if self.__sched_events[-1] == event:
-                return
-    
-        self.__sched_events.append((event, arg))
-
-    def push_context(self, tuictx):
-        tuictx.prepare()
-        self.__context_stack.append(tuictx)
-        self.schedule(EventType.E_REDRAW)
-
-        curses.curs_set(self.active().curser_mode())
-
-    def pop_context(self):
-        if len(self.__context_stack) == 1:
-            return
-        
-        ctx = self.__context_stack.pop()
-        ctx.on_destory()
-        self.schedule(EventType.E_REDRAW)
-
-        curses.curs_set(self.active().curser_mode())
-
-    def active(self):
-        return self.__context_stack[-1]
-    
-    def event_loop(self):
-        if len(self.__context_stack) == 0:
-            raise RuntimeError("no tui context to display")
-        
-        while True:
-            key = self.__win.getch()
-            if key != -1:
-                self.schedule(EventType.E_KEY, key)
-            
-            if len(self.__sched_events) == 0:
-                continue
-
-            evt, arg = self.__sched_events.pop(0)
-            if evt == EventType.E_REDRAW:
-                self.__redraw()
-            elif evt == EventType.E_QUIT:
-                self.__notify_quit()
-                break
-
-            self.active().dispatch_event(evt, arg)
-        
-    def __notify_quit(self):
-        while len(self.__context_stack) == 0:
-            ctx = self.__context_stack.pop()
-            ctx.dispatch_event(EventType.E_QUIT, None)
-
-    def __redraw(self):
-        self.__win.noutrefresh()
-        
-        self.active().redraw(self.__win)
-
-        self.__panbg.bottom()
-
-        cpanel.update_panels()
-        curses.doupdate()
-
-class TuiFocusGroup:
-    def __init__(self) -> None:
-        self.__grp = []
-        self.__id = 0
-        self.__sel = 0
-        self.__focused = None
-    
-    def register(self, tui_obj, pos=-1):
-        if pos == -1:
-            self.__grp.append((self.__id, tui_obj))
-        else:
-            self.__grp.insert(pos, (self.__id, tui_obj))
-        self.__id += 1
-        return self.__id - 1
-
-    def navigate_focus(self, dir = 1):
-        self.__sel = (self.__sel + dir) % len(self.__grp)
-        f = None if not len(self.__grp) else self.__grp[self.__sel][1]
-        if f and f != self.__focused:
-            if self.__focused:
-                self.__focused.on_focus_lost()
-            f.on_focused()
-        self.__focused = f
-
-    def focused(self):
-        return self.__focused
-
-class TuiContext(TuiObject):
-    def __init__(self, session: TuiSession):
-        super().__init__(self, "context")
-        self.__root = None
-        self.__sobj = None
-        self.__session = session
-
-        self.__win = None
-
-        y, x = self.__session.window_size()
-        self._size.reset(x, y)
-        self.set_parent(None)
-
-        self.__focus_group = TuiFocusGroup()
-        self.__curser_mode = 0
-
-    def set_curser_mode(self, mode):
-        self.__curser_mode = mode
-
-    def curser_mode(self):
-        return self.__curser_mode
-
-    def set_root(self, root):
-        self.__root = root
-        self.__root.set_parent(self)
-    
-    def set_state(self, obj):
-        self.__sobj = obj
-
-    def state(self):
-        return self.__sobj
-
-    def prepare(self):
-        self.__root.on_create()
-
-    def on_destory(self):
-        self.__root.on_destory()
-
-    def canvas(self):
-        return (self, self.__win)
-    
-    def session(self):
-        return self.__session
-    
-    def dispatch_event(self, evt, arg):
-        if evt == EventType.E_REDRAW:
-            self.__focus_group.navigate_focus(0)
-        elif evt == EventType.E_CHFOCUS:
-            self.__focus_group.navigate_focus(1)
-            self.__session.schedule(EventType.E_REDRAW)
-            return
-        elif evt == EventType.E_TASK:
-            arg()
-        elif evt == EventType.E_QUIT:
-            self.__root.on_quit()
-        elif evt == EventType.E_KEY:
-            self._handle_key_event(arg)
-        else:
-            self.__root.on_event(evt, arg)
-
-        focused = self.__focus_group.focused()
-        if focused:
-            focused.on_event(evt | EventType.E_M_FOCUS, arg)
-
-    def redraw(self, win):
-        self.__win = win
-        self.on_layout()
-        self.on_draw()
-
-    def on_layout(self):
-        self.__root.on_layout()
-
-    def on_draw(self):
-        self.__root.on_draw()
-
-    def focus_group(self):
-        return self.__focus_group
-    
-    def _handle_key_event(self, key):
-        if key == ord('\t') or key == curses.KEY_RIGHT:
-            self.__focus_group.navigate_focus()
-        elif key == curses.KEY_LEFT:
-            self.__focus_group.navigate_focus(-1)
-        else:
-            self.__root.on_event(EventType.E_KEY, key)
-        
-        if self.__focus_group.focused():
-            self.__session.schedule(EventType.E_REDRAW)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/lunamenu.py b/lunaix-os/scripts/build-tools/integration/lunamenu.py
deleted file mode 100644 (file)
index 7b1f16b..0000000
+++ /dev/null
@@ -1,457 +0,0 @@
-from lcfg.api import RenderContext
-from lcfg.types import (
-    PrimitiveType,
-    MultipleChoiceType
-)
-
-import subprocess
-import curses
-import textwrap
-import integration.libtui as tui
-import integration.libmenu as menu
-
-from integration.libtui import ColorScope, TuiColor, Alignment, EventType
-from integration.libmenu import Dialogue, ListView, show_dialog
-
-__git_repo_info = None
-__tainted = False
-
-def mark_tainted():
-    global __tainted
-    __tainted = True
-
-def unmark_tainted():
-    global __tainted
-    __tainted = False
-
-def get_git_hash():
-    try:
-        hsh = subprocess.check_output([
-                    'git', 'rev-parse', '--short', 'HEAD'
-                ]).decode('ascii').strip()
-        branch = subprocess.check_output([
-                    'git', 'branch', '--show-current'
-                ]).decode('ascii').strip()
-        return f"{branch}@{hsh}"
-    except:
-        return None
-    
-def get_git_info():
-    global __git_repo_info
-    return __git_repo_info
-    
-def do_save(session):
-    show_dialog(session, "Notice", "Configuration saved")
-    unmark_tainted()
-
-def do_exit(session):
-    global __tainted
-    if not __tainted:
-        session.schedule(EventType.E_QUIT)
-        return
-    
-    quit = QuitDialogue(session)
-    quit.show()
-
-class MainMenuContext(tui.TuiContext):
-    def __init__(self, session, view_title):
-        super().__init__(session)
-
-        self.__title = view_title
-        
-        self.__prepare_layout()
-
-    def __prepare_layout(self):
-        
-        root = tui.TuiPanel(self, "main_panel")
-        root.set_size("*-10", "*-5")
-        root.set_alignment(Alignment.CENTER)
-        root.drop_shadow(1, 2)
-        root.border(True)
-
-        layout = tui.FlexLinearLayout(self, "layout", "6,*,5")
-        layout.orientation(tui.FlexLinearLayout.PORTRAIT)
-        layout.set_size("*", "*")
-        layout.set_padding(1, 1, 1, 1)
-
-        listv = ListView(self, "list_view")
-        listv.set_size("70", "*")
-        listv.set_alignment(Alignment.CENTER)
-
-        hint = tui.TuiTextBlock(self, "hint")
-        hint.set_size(w="*")
-        hint.set_local_pos("0.1*", 0)
-        hint.height_auto_fit(True)
-        hint.set_text(
-            "Use <UP>/<DOWN>/<ENTER> to select from list\n"
-            "Use <TAB>/<RIGHT>/<LEFT> to change focus\n"
-            "<H>: show help (if applicable), <BACKSPACE>: back previous level"
-        )
-        hint.set_alignment(Alignment.CENTER | Alignment.LEFT)
-
-        suffix = ""
-        btns_defs = [
-            { 
-                "text": "Save", 
-                "onclick": lambda x: do_save(self.session())
-            },
-            { 
-                "text": "Exit", 
-                "onclick": lambda x: do_exit(self.session())
-            }
-        ]
-
-        repo_info = get_git_info()
-
-        if self.__title:
-            suffix += f" - {self.__title}"
-            btns_defs.insert(1, { 
-                "text": "Back", 
-                "onclick": lambda x: self.session().pop_context() 
-            })
-
-        btns = menu.create_buttons(self, btns_defs, sizes="50,*")
-
-        layout.set_cell(0, hint)
-        layout.set_cell(1, listv)
-        layout.set_cell(2, btns)
-
-        t = menu.create_title(self, "Lunaix Kernel Configuration" + suffix)
-        t2 = menu.create_title(self, repo_info)
-        t2.set_alignment(Alignment.BOT | Alignment.LEFT)
-
-        root.add(t)
-        root.add(t2)
-        root.add(layout)
-
-        self.set_root(root)
-        self.__menu_list = listv
-
-    def menu(self):
-        return self.__menu_list
-
-    def _handle_key_event(self, key):
-        if key == curses.KEY_BACKSPACE or key == 8:
-            self.session().pop_context()
-        elif key == 27:
-            do_exit(self.session())
-            return
-        
-        super()._handle_key_event(key)
-
-class ItemType:
-    Expandable = 0
-    Switch = 1
-    Choice = 2
-    Other = 3
-    def __init__(self, node, expandable) -> None:
-        self.__node = node
-
-        if expandable:
-            self.__type = ItemType.Expandable
-            return
-        
-        self.__type = ItemType.Other
-        self.__primitive = False
-        type_provider = node.get_type()
-
-        if isinstance(type_provider, PrimitiveType):
-            self.__primitive = True
-
-            if isinstance(type_provider, MultipleChoiceType):
-                self.__type = ItemType.Choice
-            elif type_provider._type == bool:
-                self.__type = ItemType.Switch
-        
-        self.__provider = type_provider
-
-    def get_formatter(self):
-        if self.__type == ItemType.Expandable:
-            return "%s ---->"
-        
-        v = self.__node.get_value()
-
-        if self.is_switch():
-            mark = "*" if v else " "
-            return f"[{mark}] %s"
-        
-        if self.is_choice() or isinstance(v, int):
-            return f"({v}) %s"
-        
-        return "%s"
-    
-    def expandable(self):
-        return self.__type == ItemType.Expandable
-    
-    def is_switch(self):
-        return self.__type == ItemType.Switch
-    
-    def is_choice(self):
-        return self.__type == ItemType.Choice
-    
-    def read_only(self):
-        return not self.expandable() and self.__node.read_only()
-    
-    def provider(self):
-        return self.__provider
-
-class MultiChoiceItem(tui.SimpleList.Item):
-    def __init__(self, value, get_val) -> None:
-        super().__init__()
-        self.__val = value
-        self.__getval = get_val
-
-    def get_text(self):
-        marker = "*" if self.__getval() == self.__val else " "
-        return f"  ({marker}) {self.__val}"
-    
-    def value(self):
-        return self.__val
-    
-class LunaConfigItem(tui.SimpleList.Item):
-    def __init__(self, session, node, name, expand_cb = None):
-        super().__init__()
-        self.__node = node
-        self.__type = ItemType(node, expand_cb is not None)
-        self.__name = name
-        self.__expand_cb = expand_cb
-        self.__session = session
-    
-    def get_text(self):
-        fmt = self.__type.get_formatter()
-        if self.__type.read_only():
-            fmt += "*"
-        return f"   {fmt%(self.__name)}"
-    
-    def on_selected(self):
-        if self.__type.read_only():
-            show_dialog(
-                self.__session, 
-                f"Read-only: \"{self.__name}\"", 
-                f"Value defined in this field:\n\n'{self.__node.get_value()}'")
-            return
-        
-        if self.__type.expandable():
-            view = CollectionView(self.__session, self.__node, self.__name)
-            view.set_reloader(self.__expand_cb)
-            view.show()
-            return
-        
-        if self.__type.is_switch():
-            v = self.__node.get_value()
-            self.change_value(not v)
-        else:
-            dia = ValueEditDialogue(self.__session, self)
-            dia.show()
-
-        self.__session.schedule(EventType.E_REDRAW)
-
-    def name(self):
-        return self.__name
-    
-    def node(self):
-        return self.__node
-    
-    def type(self):
-        return self.__type
-    
-    def change_value(self, val):
-        try:
-            self.__node.set_value(val)
-        except:
-            show_dialog(
-                self.__session, "Invalid value",
-                f"Value: '{val}' does not match the type")
-            return False
-        
-        mark_tainted()
-        CollectionView.reload_active(self.__session)
-        return True
-    
-    def on_key_pressed(self, key):
-        if (key & ~0b100000) != ord('H'):
-            return
-        
-        h = self.__node.help_prompt()
-        if not self.__type.expandable():
-            h = "\n".join([
-                h, "", "--------",
-                "Supported Values:",
-                textwrap.indent(str(self.__type.provider()), "  ")
-            ])
-
-        dia = HelpDialogue(self.__session, f"Help: '{self.__name}'", h)
-        dia.show()
-    
-class CollectionView(RenderContext):
-    def __init__(self, session, node, label = None) -> None:
-        super().__init__()
-        
-        ctx = MainMenuContext(session, label)
-        self.__node = node
-        self.__tui_ctx = ctx
-        self.__listv = ctx.menu()
-        self.__session = session
-        self.__reloader = lambda x: node.render(x)
-
-        ctx.set_state(self)
-
-    def set_reloader(self, cb):
-        self.__reloader = cb
-
-    def add_expandable(self, label, node, on_expand_cb):
-        item = LunaConfigItem(self.__session, node, label, on_expand_cb)
-        self.__listv.add_item(item)
-
-    def add_field(self, label, node):
-        item = LunaConfigItem(self.__session, node, label)
-        self.__listv.add_item(item)
-
-    def show(self):
-        self.reload()
-        self.__session.push_context(self.__tui_ctx)
-
-    def reload(self):
-        self.__listv.clear()
-        self.__reloader(self)
-        self.__session.schedule(EventType.E_REDRAW)
-
-    @staticmethod
-    def reload_active(session):
-        state = session.active().state()
-        if isinstance(state, CollectionView):
-            state.reload()
-
-class ValueEditDialogue(menu.Dialogue):
-    def __init__(self, session, item: LunaConfigItem):
-        name = item.name()
-        title = f"Edit \"{name}\""
-        super().__init__(session, title, None, False, 
-                         "Confirm", "Cancle")
-        
-        self.__item = item
-        self.__value = item.node().get_value()
-
-        self.decide_content()
-
-    def __get_val(self):
-        return self.__value
-
-    def decide_content(self):
-        if not self.__item.type().is_choice():
-            self.set_input_dialogue(True)
-            return
-        
-        listv = ListView(self.context(), "choices")
-        listv.set_size("0.8*", "*")
-        listv.set_alignment(Alignment.CENTER)
-        listv.set_onselected_cb(self.__on_selected)
-
-        for t in self.__item.type().provider()._type:
-            listv.add_item(MultiChoiceItem(t, self.__get_val))
-        
-        self.set_content(listv)
-        self.set_size()
-    
-    def __on_selected(self, listv, index, item):
-        self.__value = item.value()
-    
-    def _ok_onclick(self):
-        if self._textbox:
-            self.__value = self._textbox.get_text()
-
-        if self.__item.change_value(self.__value):
-            super()._ok_onclick()
-
-class QuitDialogue(menu.Dialogue):
-    def __init__(self, session):
-        super().__init__(session, 
-                         "Quit ?", "Unsaved changes, sure to quit?", False, 
-                         "Quit Anyway", "No", "Save and Quit")
-        
-    def _ok_onclick(self):
-        self.session().schedule(EventType.E_QUIT)
-
-    def _abort_onclick(self):
-        unmark_tainted()
-        self._ok_onclick()
-
-
-class HelpDialogue(menu.Dialogue):
-    def __init__(self, session, title="", content=""):
-        super().__init__(session, title, None, no_btn=None)
-
-        self.__content = content
-        self.__scroll_y = 0
-        self.set_local_pos(0, -2)
-
-    def prepare(self):
-        tb = tui.TuiTextBlock(self._context, "content")
-        tb.set_size(w="70")
-        tb.set_text(self.__content)
-        tb.height_auto_fit(True)
-        self.__tb = tb
-        
-        self.__scroll = tui.TuiScrollable(self._context, "scroll")
-        self.__scroll.set_size("65", "*")
-        self.__scroll.set_alignment(Alignment.CENTER)
-        self.__scroll.set_content(tb)
-
-        self.set_size(w="75")
-        self.set_content(self.__scroll)
-        
-        super().prepare()
-
-    def _handle_key_event(self, key):
-        if key == curses.KEY_UP:
-            self.__scroll_y = max(self.__scroll_y - 1, 0)
-            self.__scroll.set_scrollY(self.__scroll_y)
-        elif key == curses.KEY_DOWN:
-            y = self.__tb._size.y()
-            self.__scroll_y = min(self.__scroll_y + 1, y)
-            self.__scroll.set_scrollY(self.__scroll_y)
-        super()._handle_key_event(key)
-
-class TerminalSizeCheckFailed(Exception):
-    def __init__(self, *args: object) -> None:
-        super().__init__(*args)
-
-def main(_, root_node):
-    global __git_repo_info
-
-    __git_repo_info = get_git_hash()
-
-    session = tui.TuiSession()
-
-    h, w = session.window_size()
-    if h < 30 or w < 85:
-        raise TerminalSizeCheckFailed((90, 40), (w, h))
-
-    base_background = TuiColor.white.bright()
-    session.set_color(ColorScope.WIN,   
-                        TuiColor.black, TuiColor.blue)
-    session.set_color(ColorScope.PANEL, 
-                        TuiColor.black, base_background)
-    session.set_color(ColorScope.TEXT,  
-                        TuiColor.black, base_background)
-    session.set_color(ColorScope.TEXT_HI,  
-                        TuiColor.magenta, base_background)
-    session.set_color(ColorScope.SHADOW, 
-                        TuiColor.black, TuiColor.black)
-    session.set_color(ColorScope.SELECT, 
-                        TuiColor.white, TuiColor.black.bright())
-    session.set_color(ColorScope.HINT, 
-                        TuiColor.cyan, base_background)
-    session.set_color(ColorScope.BOX, 
-                        TuiColor.black, TuiColor.white)
-
-    main_view = CollectionView(session, root_node)
-    main_view.show()
-
-    session.event_loop()
-
-def menuconfig(root_node):
-    global __tainted
-    curses.wrapper(main, root_node)
-
-    return not __tainted
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/render_ishell.py b/lunaix-os/scripts/build-tools/integration/render_ishell.py
deleted file mode 100644 (file)
index 4fdee85..0000000
+++ /dev/null
@@ -1,296 +0,0 @@
-from lcfg.api import (
-    RenderContext, 
-    InteractiveRenderer, 
-    Renderable,
-    ConfigTypeCheckError,
-    ConfigLoadException
-)
-from lcfg.common import LConfigEnvironment
-
-import shlex
-from lib.utils import join_path
-from pathlib import Path
-import readline
-
-class ShellException(Exception):
-    def __init__(self, *args: object) -> None:
-        super().__init__(*args)
-
-class ViewElement:
-    def __init__(self, label, node) -> None:
-        self.label = label
-        self.node = node
-
-    def expand(self, sctx):
-        return False
-
-    def read(self, sctx):
-        return None
-    
-    def write(self, sctx, val):
-        return None
-    
-    def get_help(self, sctx):
-        return self.node.help_prompt()
-    
-    def get_type(self, sctx):
-        return "N/A"
-    
-    def draw(self, sctx, canvas):
-        pass
-
-
-class SubviewLink(ViewElement):
-    def __init__(self, label, node, cb) -> None:
-        super().__init__(label, node)
-        self.__callback = cb
-
-    def expand(self, sctx):
-        sctx.clear_view()
-        self.__callback(sctx)
-        return True
-    
-    def draw(self, sctx, canvas):
-        print(f" [{self.label}]")
-
-    def get_type(self, sctx):
-        return f"Collection: {self.label}"
-
-class RootWrapper(ViewElement):
-    def __init__(self, root) -> None:
-        self.__root = root
-        super().__init__("root", None)
-
-    def expand(self, sctx):
-        sctx.clear_view()
-        self.__root.render(sctx)
-        return True
-    
-    def draw(self, sctx, canvas):
-        pass
-
-    def get_type(self, sctx):
-        return ""
-
-class FieldView(ViewElement):
-    def __init__(self, label, node) -> None:
-        super().__init__(label, node)
-
-    def read(self, sctx):
-        return self.node.get_value()
-    
-    def write(self, sctx, val):
-        if self.node.read_only():
-            return None
-        
-        self.node.set_value(val)
-        return val
-    
-    def get_type(self, sctx):
-        return f"Config term: {self.label}\n{self.node.get_type()}"
-    
-    def draw(self, sctx, canvas):
-        suffix = ""
-        if self.node.read_only():
-            suffix = " (ro)"
-        print(f" {self.label}{suffix}")
-
-class ShellContext(RenderContext):
-    def __init__(self) -> None:
-        super().__init__()
-
-        self._view = {}
-        self._subviews = []
-        self._field = []
-    
-    def add_expandable(self, label, node, on_expand_cb):
-        name = node.get_name()
-        self._view[name] = SubviewLink(name, node, on_expand_cb)
-        self._subviews.append(name)
-    
-    def add_field(self, label, node):
-        name = node.get_name()
-        self._view[name] = FieldView(name, node)
-        self._field.append(name)
-    
-    def clear_view(self):
-        self._view.clear()
-        self._subviews.clear()
-        self._field.clear()
-
-    def redraw(self):
-        for v in self._subviews + self._field:
-            self._view[v].draw(self, None)
-
-    def get_view(self, label):
-        if label in self._view:
-            return self._view[label]
-        return None
-
-class InteractiveShell(InteractiveRenderer):
-    def __init__(self, root: Renderable) -> None:
-        super().__init__()
-        self.__levels = [RootWrapper(root)]
-        self.__aborted = True
-        self.__sctx = ShellContext()
-        self.__cmd = {
-            "ls": (
-                "list all config node under current collection",
-                lambda *args: 
-                    self.__sctx.redraw()
-            ),
-            "help": (
-                "print help prompt for given name of node",
-                lambda *args: 
-                    self.print_help(*args)
-            ),
-            "type": (
-                "print the type of a config node",
-                lambda *args: 
-                    self.print_type(*args)
-            ),
-            "cd": (
-                "navigate to a collection node given a unix-like path",
-                lambda *args: 
-                    self.get_in(*args)
-            ),
-            "usage": (
-                "print out the usage",
-                lambda *args:
-                    self.print_usage()
-            )
-        }
-
-    def get_path(self):
-        l = [level.label for level in self.__levels[1:]]
-        return f"/{'/'.join(l)}"
-    
-    def resolve(self, relative):
-        ctx = ShellContext()
-        p = join_path(self.get_path(), relative)
-        ps = Path(p).resolve().parts
-        if ps[0] == '/':
-            ps = ps[1:]
-
-        node = self.__levels[0]
-        levels = [node]
-        for part in ps:
-            if not node.expand(ctx):
-                raise ShellException(f"node is not a collection: {part}")
-            
-            node = ctx.get_view(part)
-            if not node:
-                raise ShellException(f"no such node: {part}")
-
-            levels.append(node)
-        
-        return (node, levels)
-
-    def print_usage(self):
-        for cmd, (desc, _) in self.__cmd.items():
-            print("\n".join([
-                cmd,
-                f"   {desc}",
-                ""
-            ]))
-
-    def print_help(self, node_name):
-        view, _ = self.resolve(node_name)
-        
-        print(view.get_help(self.__sctx))
-
-    def print_type(self, node_name):
-        view, _ = self.resolve(node_name)
-        
-        print(view.get_type(self.__sctx))
-
-    def do_read(self, node_name):
-        view, _ = self.resolve(node_name)
-        rd_val = view.read(self.__sctx)
-        if rd_val is None:
-            raise ShellException(f"config node {view.label} is not readable")
-    
-        print(rd_val)
-
-    def do_write(self, node_name, val):
-        view, _ = self.resolve(node_name)
-        wr = view.write(self.__sctx, val)
-        if not wr:
-            raise ShellException(f"config node {view.label} is read only")
-
-        print(f"write: {val}")
-        self.do_render()
-
-    def get_in(self, node_name):
-        view, lvl = self.resolve(node_name)
-        
-        if not view.expand(self.__sctx):
-            print(f"{node_name} is not a collection")
-            return
-        
-        self.__levels = lvl
-
-    def do_render(self):
-        
-        curr = self.__levels[-1]        
-        curr.expand(self.__sctx)
-
-    def __loop(self):
-        prefix = "config: %s> "%(self.__levels[-1].label)
-
-        line = input(prefix)
-        args = shlex.split(line)
-        if not args:
-            return True
-        
-        cmd = args[0]
-        if cmd in self.__cmd:
-            self.__cmd[cmd][1](*args[1:])
-            return True
-        
-        if cmd == "exit":
-            self.__aborted = False
-            return False
-        
-        if cmd == "abort":
-            return False
-        
-        node = self.resolve(cmd)
-        if not node:
-            raise ShellException(f"unrecognised command {line}")
-        
-        if len(args) == 3 and args[1] == '=':
-            self.do_write(args[0], args[2])
-            return True
-        
-        if len(args) == 1:
-            self.do_read(args[0])
-            return True
-        
-        print(f"unrecognised command {line}")
-        return True
-
-
-    def render_loop(self):
-        self.do_render()
-        print("\n".join([
-            "Interactive LConfig Shell",
-            "   type 'usage' to find out how to use",
-            "   type 'exit' to end (with saving)",
-            "   type 'abort' to abort (without saving)",
-            "   type node name directly to read the value",
-            "   type 'node = val' to set 'val' to 'node'",
-            ""
-        ]))
-        while True:
-            try:
-                if not self.__loop():
-                    break
-            except KeyboardInterrupt:
-                break
-            except ConfigTypeCheckError as e:
-                print(e.args[0])
-            except ShellException as e:
-                print(e.args[0])
-
-        return not self.__aborted
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/api.py b/lunaix-os/scripts/build-tools/lbuild/api.py
deleted file mode 100644 (file)
index 9f6f8d4..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-class ConfigProvider:
-    def __init__(self) -> None:
-        pass
-
-    def configured_value(self, name):
-        raise ValueError(f"config '{name}' is undefined or disabled")
-    
-    def has_config(self, name):
-        return False
-    
-class BuildGenerator:
-    def __init__(self) -> None:
-        pass
-
-    def generate(self, env):
-        pass
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/build.py b/lunaix-os/scripts/build-tools/lbuild/build.py
new file mode 100644 (file)
index 0000000..93beeca
--- /dev/null
@@ -0,0 +1,108 @@
+import ast
+from pathlib    import Path
+from .common    import DirectoryTracker
+from lib.utils  import ConfigAST, Schema
+
+class LBuildImporter(ast.NodeTransformer):
+    ConfigImportFn = Schema(
+                        ast.Call, 
+                        func=Schema(ast.Name, id="import_"),
+                        args=[ast.Constant])
+    
+    ScopedAccess = Schema(
+                        ast.Attribute,
+                        value=ast.Name)
+    
+    def __init__(self, env, file):
+        super().__init__()
+        self.__parent = file.parent
+        self.__env = env
+
+        with file.open('r') as f:
+            subtree = ast.parse(f.read())
+        
+        self.__tree = [
+            DirectoryTracker.emit_enter(self.__parent),
+            *self.visit(subtree).body,
+            DirectoryTracker.emit_leave()
+        ]
+    
+    def tree(self):
+        return self.__tree
+    
+    def __gen_import_subtree(self, relpath, variants):    
+        block = []
+        for var in variants:
+            p = relpath/ var / "LBuild"
+            block += LBuildImporter(self.__env, p).tree()
+        
+        return ast.If(ast.Constant(True), block, [])
+
+    def visit_ImportFrom(self, node):
+        if ConfigAST.ConfigImport != node:
+            return node
+        
+        module = node.module
+        module = "" if not module else module
+        subpath = Path(*module.split('.'))
+        
+        p = self.__parent / subpath
+        return self.__gen_import_subtree(p, [x.name for x in node.names])
+    
+    def visit_Attribute(self, node):
+        if LBuildImporter.ScopedAccess != node:
+            return self.generic_visit(node)
+        
+        scope = node.value.id
+        subscope = node.attr
+
+        if scope not in self.__env.scopes:
+            return self.generic_visit(node)
+        
+        provider = self.__env.scopes[scope]
+        
+        return ast.Subscript(
+            value=ast.Name(provider.context_name(), ctx=node.value.ctx),
+            slice=ast.Constant(subscope),
+            ctx = node.ctx
+        )
+    
+    def visit_Expr(self, node):
+        val = node.value
+        if LBuildImporter.ConfigImportFn == val:
+            name = val.args[0].value
+            return self.__gen_import_subtree(self.__parent, [name])
+        
+        return self.generic_visit(node)
+
+class BuildEnvironment:
+    def __init__(self):
+        self.scopes = {}
+    
+    def load(self, rootfile):
+        self.__exec = self.__load(Path(rootfile))
+
+    def register_scope(self, scope):
+        if scope.name in self.scopes:
+            raise Exception(f"{scope.name} already exists")
+        self.scopes[scope.name] = scope
+
+    def __load(self, rootfile):
+        module = ast.Module(
+                    LBuildImporter(self, rootfile).tree(), 
+                    type_ignores=[])
+
+        module = ast.fix_missing_locations(module)
+        return compile(module, rootfile, 'exec')
+    
+    def update(self):
+        g = {
+            "__LBUILD__": True,
+        }
+
+        for scope in self.scopes.values():
+            scope.reset()
+            g[scope.context_name()] = scope
+
+        DirectoryTracker.bind(g)
+        exec(self.__exec, g)
\ No newline at end of file
index a973760136bacc09dca68c6e56e4f47f0348c2af..01a226cfa6f333849ca1e05eaa40b419fe420d05 100644 (file)
@@ -1,59 +1,57 @@
-from lib.utils import join_path
-import os
+import ast
+from pathlib import Path
 
 
-class BuildEnvironment:
-    def __init__(self, workspace_dir, generator) -> None:
-        self.__config_provider = None
-        self.__sources = []
-        self.__headers = []
-        self.__inc_dir = []
-        self.__ws_dir = workspace_dir
-        self.__ext_object = {}
-        self.__ext_function = {}
-        self.__generator = generator
+class DirectoryTracker:
+    def __init__(self):
+        self.__stack = []
 
 
-    def set_config_provider(self, provider):
-        self.__config_provider = provider
-    
-    def config_provider(self):
-        return self.__config_provider
-    
-    def add_sources(self, sources):
-        self.__sources += sources
+    def push(self, dir):
+        self.__stack.append(Path(dir))
 
 
-    def add_headers(self, sources):
-        for h in sources:
-            if not os.path.isdir(h):
-                self.__headers.append(h)
-            else:
-                self.__inc_dir.append(h)
-
-    def to_wspath(self, file):
-        path = join_path(self.__ws_dir, file)
-        return os.path.relpath(path, self.__ws_dir)
+    def pop(self):
+        self.__stack.pop()
     
     
-    def export(self):
-        self.__generator.generate(self)
-
-    def get_object(self, key, _default=None):
-        return _default if key not in self.__ext_object else self.__ext_object[key]
+    def active_relative(self):
+        root = self.__stack[0]
+        return self.__stack[-1].relative_to(root)
 
 
-    def set_object(self, key, object):
-        self.__ext_object[key] = object
-
-    def srcs(self):
-        return list(self.__sources)
-    
-    def headers(self):
-        return list(self.__headers)
+    @staticmethod
+    def context_name():
+        return "__FILESTACK__"
     
     
-    def includes(self):
-        return list(self.__inc_dir)
+    @staticmethod
+    def get(context):
+        return context[DirectoryTracker.context_name()]
     
     
-    def add_external_func(self, function):
-        name = function.__name__
-        invk = lambda *args, **kwargs: function(self, *args, **kwargs)
-        self.__ext_function[name] = invk
-
-    def external_func_table(self):
-        return self.__ext_function
\ No newline at end of file
+    @staticmethod
+    def bind(context):
+        name = DirectoryTracker.context_name()
+        context[name] = DirectoryTracker()
+
+    @staticmethod
+    def emit_enter(dir):
+        name = DirectoryTracker.context_name()
+        return ast.Expr(
+                    ast.Call(
+                        func=ast.Attribute(
+                            value=ast.Name(name, ctx=ast.Load()),
+                            attr="push",
+                            ctx=ast.Load()
+                        ),
+                        args=[
+                            ast.Constant(str(dir))
+                        ],
+                        keywords=[]))
+
+    @staticmethod
+    def emit_leave():
+        name = DirectoryTracker.context_name()
+        return ast.Expr(
+                    ast.Call(
+                        func=ast.Attribute(
+                            value=ast.Name(name, ctx=ast.Load()),
+                            attr="pop",
+                            ctx=ast.Load()
+                        ),
+                        args=[],
+                        keywords=[]))
diff --git a/lunaix-os/scripts/build-tools/lbuild/contract.py b/lunaix-os/scripts/build-tools/lbuild/contract.py
deleted file mode 100644 (file)
index 0484947..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-from lib.sandbox import Sandbox
-from lib.utils import join_path
-from .common import BuildEnvironment
-
-import os
-
-class LunaBuildFile(Sandbox):
-    def __init__(self, env: BuildEnvironment, path) -> None:
-        super().__init__({
-            "_script": 
-                path,
-            "sources": 
-                lambda src: self.export_sources(src),
-            "headers":
-                lambda hdr: self.export_headers(hdr),
-            "configured":
-                lambda name: self.check_config(name),
-            "config":
-                lambda name: self.read_config(name),
-            "use":
-                lambda file: self.import_buildfile(file),
-            **env.external_func_table()
-        })
-        
-        self.__srcs = []
-        self.__hdrs = []
-        self.__env  = env
-
-        self.__path = path
-        self.__dir  = os.path.dirname(path)
-        
-    def resolve(self):
-        self.execute(self.__path)
-        self.__env.add_sources(self.__srcs)
-        self.__env.add_headers(self.__hdrs)
-
-    def __do_process(self, list):
-        resolved = []
-        for entry in list:
-            if not entry:
-                continue
-            resolved.append(self.__resolve_value(entry))
-        return resolved
-
-    def expand_select(self, val):
-        tests = list(val.keys())
-        if len(tests) != 1:
-            raise TypeError(
-                "select statement must have exactly one conditional")
-        
-        test = tests[0]
-        outcomes = val[test]
-        if test not in outcomes:
-            self.__raise("unbounded select")
-        return outcomes[test]
-
-    def __resolve_value(self, source):
-        resolved = source
-        while isinstance(resolved, dict):
-            if isinstance(resolved, dict):
-                resolved = self.expand_select(resolved)
-            else:
-                self.__raise(f"entry with unknown type: {resolved}")
-        
-        if isinstance(resolved, list):
-            rs = []
-            for file in resolved:
-                file = join_path(self.__dir, file)
-                file = self.__env.to_wspath(file)
-                rs.append(file)
-        else:
-            rs = join_path(self.__dir, resolved)
-            rs = [self.__env.to_wspath(rs)]
-
-        return rs
-    
-    def import_buildfile(self, path):
-        path = self.__resolve_value(path)
-        for p in path:
-            if (os.path.isdir(p)):
-                p = os.path.join(p, "LBuild")
-            
-            if not os.path.exists(p):
-                self.__raise("Build file not exist: %s", p)
-
-            if os.path.abspath(p) == os.path.abspath(self.__path):
-                self.__raise("self dependency detected")
-
-            LunaBuildFile(self.__env, p).resolve()
-
-    def export_sources(self, src):
-        src = self.__resolve_value(src)
-        self.__srcs += src
-
-    def export_headers(self, hdr):
-        hdr = self.__resolve_value(hdr)
-        self.__hdrs += hdr
-
-    def check_config(self, name):
-        return self.__env.config_provider().has_config(name)
-    
-    def read_config(self, name):
-        return self.__env.config_provider().configured_value(name)
-    
-    def __raise(self, msg, *kargs):
-        raise Exception(msg%kargs)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/scope.py b/lunaix-os/scripts/build-tools/lbuild/scope.py
new file mode 100644 (file)
index 0000000..149aa5a
--- /dev/null
@@ -0,0 +1,96 @@
+import inspect
+from lib.utils import Schema
+from .common import DirectoryTracker
+
+class ScopeProvider:
+    Enumerables = Schema(Schema.Union(list, tuple))
+    Primitives = Schema(Schema.Union(str, int, bool))
+    
+    def __init__(self, name):
+        super().__init__()
+        self.name = name
+        self.__accessors = {}
+
+    def __add_accessor(self, accessor, type):
+        acc = accessor
+        if isinstance(accessor, str):
+            acc = type(accessor)
+        self.__accessors[acc.name] = acc
+
+    def subscope(self, accessor):
+        self.__add_accessor(accessor, ScopeAccessor)
+
+    def file_subscope(self, accessor):
+        self.__add_accessor(accessor, FileScopeAccessor)
+
+    def accessors(self):
+        return self.__accessors.values()
+    
+    def reset(self):
+        for v in self.__accessors.values():
+            v.reset()
+
+    def context_name(self):
+        return f"__SC{self.name}"
+
+    def __getitem__(self, name):
+        if name not in self.__accessors:
+            return None
+        return self.__accessors[name]
+    
+    def __setitem__(self, name, val):
+        pass
+
+class ScopeAccessor:
+    def __init__(self, name):
+        super().__init__()
+        self.name = name
+        self.__values = []
+        self._active_context = None
+
+    def values(self):
+        return self.__values
+    
+    def reset(self):
+        self.__values.clear()
+
+    def add(self, x):
+        self.__load_build_context()
+
+        if ScopeProvider.Enumerables == x:
+            for v in x:
+                self.add(v)
+            return
+        
+        if ScopeProvider.Primitives == x:
+            self.put_value(x)
+            return
+        
+        raise Exception(f"invalid value '{x}' of type ('{type(x).__name__}')")
+    
+    def put_value(self, x):
+        self.__values.append(x)
+
+    def __load_build_context(self):
+        self._active_context = None
+        for f in inspect.stack():
+            if "__LBUILD__" not in f.frame.f_globals:
+                continue
+            self._active_context = f.frame.f_globals
+        
+        if not self._active_context:
+            raise Exception(
+                f"unable to modify '{self.name}' outside build context)")
+
+    def __iadd__(self, other):
+        self.add(other)
+
+class FileScopeAccessor(ScopeAccessor):
+    def __init__(self, name):
+        super().__init__(name)
+
+    def put_value(self, x):
+        tracker = DirectoryTracker.get(self._active_context)
+        x = str(tracker.active_relative() / x)
+        return super().put_value(x)
+
diff --git a/lunaix-os/scripts/build-tools/lcfg/api.py b/lunaix-os/scripts/build-tools/lcfg/api.py
deleted file mode 100644 (file)
index 1b0b30f..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-from abc import abstractmethod
-
-class ConfigLoadBaseException(Exception):
-    def __init__(self, msg, node, inner=None) -> None:
-        super().__init__(msg)
-        
-        self.__msg = msg
-        self.__node = node
-        self.__inner = inner
-
-    def __str__(self) -> str:
-        return super().__str__()
-    
-class ConfigLoadException(ConfigLoadBaseException):
-    def __init__(self, msg, node = None, inner=None) -> None:
-        super().__init__(msg, node, inner)
-
-class ConfigTypeCheckError(ConfigLoadBaseException):
-    def __init__(self, msg, node, inner=None) -> None:
-        super().__init__(msg, node, inner)
-
-class ConfigIOProvider:
-    def __init__(self) -> None:
-        pass
-
-    @abstractmethod
-    def export(self, env, config_dict):
-        pass
-    
-    @abstractmethod
-    def save(self, env, config_dict):
-        pass
-
-    @abstractmethod
-    def load(self, env):
-        pass
-
-
-class TypeProviderBase:
-    def __init__(self, type) -> None:
-        self._type = type
-
-    @abstractmethod
-    def check(self, val):
-        return True
-    
-    @abstractmethod
-    def serialise(self, val):
-        pass
-
-    @abstractmethod
-    def deserialise(self, val):
-        pass
-
-    @abstractmethod
-    def parse_input(self, input_str):
-        pass
-
-    @abstractmethod
-    def to_input(self, val):
-        pass
-
-    def allow_none(self):
-        return False
-    
-    @staticmethod
-    def typedef_matched(typedef):
-        return False
-    
-    @abstractmethod
-    def __str__(self) -> str:
-        pass
-
-class Renderable:
-    def __init__(self) -> None:
-        pass
-    
-    @abstractmethod
-    def render(self, rctx):
-        pass
-
-class RenderContext:
-    def __init__(self) -> None:
-        pass
-
-    @abstractmethod
-    def add_expandable(self, label, node, on_expand_cb):
-        pass
-
-    @abstractmethod
-    def add_field(self, label, node):
-        pass
-
-class InteractiveRenderer(RenderContext):
-    def __init__(self) -> None:
-        super().__init__()
-
-    @abstractmethod
-    def render_loop(self):
-        pass
-
-
-class LConfigBuiltin:
-    def __init__(self, f, is_contextual, rename=None, caller_type=[]):
-        self.name = f.__name__ if not rename else rename
-        self.__f = f
-        self.__contextual = is_contextual
-        self.__caller_type = caller_type
-
-    def __call__(self, env, *args, **kwds):
-        if not self.__contextual:
-            return self.__f(env, *args, **kwds)
-        
-        caller = env.callframe_at(0)
-        f_name = self.__f.__name__
-
-        if not caller:
-            raise ConfigLoadException(
-                f"contextual function '{f_name}' can not be called contextless",
-                None
-            )
-        
-        if self.__caller_type and not any([isinstance(caller, x) for x in self.__caller_type]):
-            raise ConfigLoadException(
-                f"caller of '{f_name}' ({caller}) must have type of any {self.__caller_type}",
-                caller
-            )
-    
-        return self.__f(env, caller, *args, **kwds)
-    
-def contextual(name=None, caller_type=[]):
-    def wrapper(func):
-        return LConfigBuiltin(func, True, name, caller_type)
-    return wrapper
-
-def builtin(name=None):
-    def wrapper(func):
-        return LConfigBuiltin(func, False, name)
-    return wrapper
diff --git a/lunaix-os/scripts/build-tools/lcfg/builtins.py b/lunaix-os/scripts/build-tools/lcfg/builtins.py
deleted file mode 100644 (file)
index f072303..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-from .api import contextual, builtin
-from .lcnodes import LCFuncNode, LCTermNode, LCModuleNode
-from lib.utils import join_path
-import os
-
-@contextual()
-def v(env, caller, term):
-    node = env.lookup_node(term.__name__)
-    env.dependency().add(node, caller)
-    return env.resolve_symbol(node.get_name())
-
-@contextual(caller_type=[LCModuleNode])
-def include(env, caller, file):
-    fobj = caller.get_fo()
-    path = os.path.dirname(fobj.filename())
-    path = join_path(path, file)
-    
-    if os.path.isdir(path):
-        path = join_path(path, "LConfig")
-
-    env.resolve_module(path)
-
-@contextual("type", caller_type=[LCTermNode])
-def term_type(env, caller, type):
-    caller.set_type(type)
-
-@contextual("add_to_collection", caller_type=[LCFuncNode])
-def parent(env, caller, ref):
-    sym = env.lookup_node(ref.__name__)
-
-    caller.set_parent(sym)
-
-@contextual(caller_type=[LCTermNode])
-def default(env, caller, val):
-    caller.set_default(val)
-
-@contextual(caller_type=[LCTermNode])
-def set_value(env, caller, val):
-    caller.set_value(val)
-
-@builtin()
-def env(env, key, default=None):
-    return os.getenv(key, default)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg/common.py b/lunaix-os/scripts/build-tools/lcfg/common.py
deleted file mode 100644 (file)
index 383d1ec..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-import os.path as path
-import ast, json
-
-from .lcnodes import LCModuleNode, LCTermNode
-from .api import (
-    ConfigLoadException, 
-    Renderable
-)
-
-class LConfigFile:
-    def __init__(self, 
-                 env, 
-                 file) -> None:
-        self.__env = env
-        self.__file = env.to_wspath(file)
-
-        with open(file) as f:
-            tree = ast.parse(f.read(), self.__file, mode='exec')
-            self.__module = LCModuleNode(self, tree)
-
-    def filename(self):
-        return self.__file
-    
-    def env(self):
-        return self.__env
-    
-    def root_module(self):
-        return self.__module
-    
-    def compile_astns(self, astns):
-        if not isinstance(astns, list):
-            astns = [astns]
-
-        return compile(
-            ast.Module(body=astns, type_ignores=[]), 
-            self.__file, mode='exec')
-    
-class DependencyGraph:
-    def __init__(self) -> None:
-        self._edges = {}
-
-    def add(self, dependent, dependee):
-        if dependent in self._edges:
-            if dependee in self._edges[dependent]:
-                return
-            self._edges[dependent].add(dependee)
-        else:
-            self._edges[dependent] = set([dependee])
-        
-        if self.__check_loop(dependee):
-            raise ConfigLoadException(
-                f"loop dependency detected: {dependent.get_name()}",
-                dependee)
-
-    def __check_loop(self, start):
-        visited = set()
-        q = [start]
-        while len(q) > 0:
-            current = q.pop()
-            if current in visited:
-                return True
-            
-            visited.add(current)
-            if current not in self._edges:
-                continue
-            for x in self._edges[current]:
-                q.append(x)
-        
-        return False
-    
-    def cascade(self, start):
-        q = [start]
-        while len(q) > 0:
-            current = q.pop()
-            if current in self._edges:
-                for x in self._edges[current]:
-                    q.append(x)
-            if current != start:
-                current.evaluate()
-
-class ConfigTypeFactory:
-    def __init__(self) -> None:
-        self.__providers = []
-
-    def regitser(self, provider_type):
-        self.__providers.append(provider_type)
-
-    def create(self, typedef):
-        for provider in self.__providers:
-            if not provider.typedef_matched(typedef):
-                continue
-            return provider(typedef)
-        
-        raise ConfigLoadException(
-                f"no type provider defined for type: {typedef}", None)
-
-class LConfigEvaluationWrapper:
-    def __init__(self, env, node) -> None:
-        self.__env = env
-        self.__node = node
-
-    def __enter__(self):
-        self.__env.push_executing_node(self.__node)
-        return self
-
-    def __exit__(self, type, value, tb):
-        self.__env.pop_executing_node()
-
-    def evaluate(self):
-        return self.__env.evaluate_node(self.__node)
-
-class LConfigEnvironment(Renderable):
-    def __init__(self, workspace, config_io) -> None:
-        super().__init__()
-        
-        self.__ws_path = path.abspath(workspace)
-        self.__exec_globl = {}
-        self.__eval_stack = []
-        self.__lc_modules = []
-        self.__config_val = {}
-        self.__node_table = {}
-        self.__deps_graph = DependencyGraph()
-        self.__type_fatry = ConfigTypeFactory()
-        self.__config_io  = config_io
-
-    def to_wspath(self, _path):
-        _path = path.abspath(_path)
-        return path.relpath(_path, self.__ws_path)
-
-    def register_builtin_func(self, func_obj):
-        call = (lambda *arg, **kwargs:
-                     func_obj(self, *arg, **kwargs))
-        
-        self.__exec_globl[func_obj.name] = call
-
-    def resolve_module(self, file):
-        fo = LConfigFile(self, file)
-        self.__lc_modules.insert(0, (fo.root_module()))
-
-    def evaluate_node(self, node):
-        name = node.get_name()
-        
-        return self.get_symbol(name)()
-    
-    def eval_context(self, node):
-        return LConfigEvaluationWrapper(self, node)
-    
-    def push_executing_node(self, node):
-        self.__eval_stack.append(node)
-
-    def pop_executing_node(self):
-        return self.__eval_stack.pop()
-
-    def register_node(self, node):
-        self.__node_table[node.get_name()] = node
-
-        l = {}
-        try:
-            self.push_executing_node(node)
-            exec(node.get_co(), self.__exec_globl, l)
-            self.pop_executing_node()
-        except Exception as e:
-            raise ConfigLoadException("failed to load", node, e)
-        
-        self.__exec_globl.update(l)
-
-    def lookup_node(self, name):
-        if name not in self.__node_table:
-            raise ConfigLoadException(f"node '{name}' undefined")
-        return self.__node_table[name]
-
-    def type_factory(self):
-        return self.__type_fatry
-    
-    def update_value(self, key, value):
-        self.__config_val[key] = value
-
-    def get_symbol(self, name):
-        if name not in self.__exec_globl:
-            raise ConfigLoadException(f"unknown symbol '{name}'")
-        return self.__exec_globl[name]
-
-    def callframe_at(self, traverse_level):
-        try:
-            return self.__eval_stack[traverse_level - 1]
-        except:
-            return None
-    
-    def lookup_value(self, key):
-        return self.__config_val[key]
-    
-    def resolve_symbol(self, sym):
-        term_node = self.__node_table[sym]
-        if isinstance(term_node, LCTermNode):
-            if not term_node.is_ready():
-                term_node.evaluate()
-            return term_node.get_value()
-        raise Exception(f"fail to resolve symbol: {sym}, not resolvable")
-        
-    def dependency(self):
-        return self.__deps_graph
-    
-    def update(self):
-        for mod in self.__lc_modules:
-            mod.evaluate()
-    
-    def export(self):
-        self.__config_io.export(self, self.__config_val)
-    
-    def save(self, _path = ".config.json"):
-        data = {}
-        for mod in self.__lc_modules:
-            mod.serialise(data)
-
-        with open(_path, 'w') as f:
-            json.dump(data, f)
-
-    def load(self, _path = ".config.json"):
-        if not path.exists(_path):
-            return
-        
-        data = {}
-        with open(_path, 'r') as f:
-            data.update(json.load(f))
-        
-        for mod in self.__lc_modules:
-            mod.deserialise(data)
-
-        self.update()
-
-    def render(self, rctx):
-        for mod in self.__lc_modules:
-            mod.render(rctx)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg/lcnodes.py b/lunaix-os/scripts/build-tools/lcfg/lcnodes.py
deleted file mode 100644 (file)
index 87aa20d..0000000
+++ /dev/null
@@ -1,397 +0,0 @@
-from .api import (
-    ConfigLoadException,
-    ConfigTypeCheckError,
-    Renderable
-)
-
-from .utils import (
-    extract_decorators, 
-    to_displayable
-)
-
-import ast, textwrap
-from abc import abstractmethod
-
-
-
-class LCNode(Renderable):
-    def __init__(self, fo, astn):
-        super().__init__()
-
-        self._fo = fo
-        self._env = fo.env()
-        self._astn = self.prune_astn(astn)
-        self._co = self.compile_astn()
-        self._parent = None
-
-        self._env.register_node(self)
-    
-    def prune_astn(self, astn):
-        return astn
-
-    def compile_astn(self):
-        return self._fo.compile_astns(self._astn)
-
-    def get_co(self):
-        return self._co
-    
-    def get_fo(self):
-        return self._fo
-    
-    def set_parent(self, new_parent):
-        self._parent = new_parent
-
-    def parent(self):
-        return self._parent
-    
-    def add_child(self, node):
-        pass
-
-    def remove_child(self, node):
-        pass
-    
-    def get_name(self):
-        return f"LCNode: {self.__hash__()}"
-    
-    @abstractmethod
-    def evaluate(self):
-        pass
-
-    def render(self, rctx):
-        pass
-
-    def deserialise(self, dict):
-        pass
-
-    def serialise(self, dict):
-        pass
-
-
-
-class LCModuleNode(LCNode):
-    def __init__(self, fo, astn: ast.Module):
-        self.__nodes = {}
-
-        super().__init__(fo, astn)
-
-    def get_name(self):
-        return f"file: {self._fo.filename()}"
-
-    def prune_astn(self, astn: ast.Module):
-        general_exprs = []
-
-        for b in astn.body:
-            if not isinstance(b, ast.FunctionDef):
-                general_exprs.append(b)
-                continue
-            
-            node = LCFuncNode.construct(self._fo, b)
-            node.set_parent(self)
-
-            self.add_child(node)
-
-        return general_exprs
-    
-    def evaluate(self):
-        with self._env.eval_context(self) as evalc:
-            ls = list(self.__nodes.values())          
-            for node in ls:
-                node.evaluate()
-
-    def add_child(self, node):
-        self.__nodes[node] = node
-
-    def remove_child(self, node):
-        if node in self.__nodes:
-            del self.__nodes[node]
-
-    def deserialise(self, dict):
-        for node in self.__nodes:
-            node.deserialise(dict)
-
-    def serialise(self, dict):
-        for node in self.__nodes:
-            node.serialise(dict)
-
-    def render(self, rctx):
-        for node in self.__nodes:
-            node.render(rctx)
-
-class LCFuncNode(LCNode):
-    def __init__(self, fo, astn) -> None:
-        self._decors = {}
-        self._name = None
-        self._help = ""
-        self._display_name = None
-        self._enabled = True
-
-        super().__init__(fo, astn)
-
-    def prune_astn(self, astn: ast.FunctionDef):
-        self._name = astn.name
-        self._display_name = to_displayable(self._name)
-        
-        maybe_doc = astn.body[0]
-        if isinstance(maybe_doc, ast.Expr):
-            if isinstance(maybe_doc.value, ast.Constant):
-                self._help = maybe_doc.value.value
-                self._help = textwrap.dedent(self._help)
-        
-        decors = extract_decorators(astn)
-        for name, args, kwargs in decors:
-            self._decors[name] = (args, kwargs)
-
-        (args, _) = self._decors[self.mapped_name()]
-        if args:
-            self._display_name = args[0]
-        
-        astn.decorator_list.clear()
-        return astn
-    
-    def get_name(self):
-        return self._name
-    
-    def get_display_name(self):
-        return self._display_name
-    
-    def enabled(self):
-        if isinstance(self._parent, LCFuncNode):
-            return self._enabled and self._parent.enabled()
-        return self._enabled
-
-    def help_prompt(self):
-        return self._help
-    
-    def evaluate(self):
-        with self._env.eval_context(self) as evalc:
-            result = evalc.evaluate()
-            self._enabled = True if result is None else result
-            
-    @staticmethod
-    def mapped_name(self):
-        return None
-    
-    @staticmethod
-    def construct(fo, astn: ast.FunctionDef):
-        nodes = [
-            LCCollectionNode,
-            LCGroupNode,
-            LCTermNode
-        ]
-
-        for node in nodes:
-            if extract_decorators(astn, node.mapped_name(), True):
-                return node(fo, astn)
-        
-        raise ConfigLoadException(
-                f"unknown type for astn type: {type(astn)}")
-
-    def set_parent(self, new_parent):
-        if self._parent:
-            self._parent.remove_child(self)
-        
-        new_parent.add_child(self)
-        super().set_parent(new_parent)
-
-
-class LCTermNode(LCFuncNode):
-    def __init__(self, fo, astn) -> None:
-        self._value = None
-        self._default = None
-        self._type = None
-        self._rdonly = False
-        self._ready = False
-
-        super().__init__(fo, astn)
-
-    @staticmethod
-    def mapped_name():
-        return "Term"
-    
-    def prune_astn(self, astn: ast.FunctionDef):
-        astn = super().prune_astn(astn)
-
-        self._rdonly = "ReadOnly" in self._decors
-
-        return astn
-
-    def set_type(self, type_def):
-        self._type = self._env.type_factory().create(type_def)
-
-    def get_type(self):
-        return self._type
-
-    def __assert_type(self, val):
-        if not self._type:
-            raise ConfigLoadException(
-                    f"config: {self._name} must be typed", self)
-        
-        if self._type.check(val):
-           return
-
-        raise ConfigTypeCheckError(
-                f"value: {val} does not match type {self._type}", self)
-
-    def set_value(self, val):
-        if self._rdonly:
-            return
-        
-        if isinstance(val, str):
-            val = self._type.parse_input(val)
-        
-        self.__assert_type(val)
-        self._value = val
-        
-        self._ready = True
-        self.__update_value()
-        self._env.dependency().cascade(self)
-        
-
-    def set_default(self, val):
-        self.__assert_type(val)
-        self._default = val
-
-        if self._rdonly:
-            self._value = val
-
-    def get_value(self):
-        return self._value
-    
-    def is_ready(self):
-        return self._ready
-    
-    def evaluate(self):
-        super().evaluate()
-        self.__update_value()
-
-    def read_only(self):
-        return self._rdonly
-
-    def render(self, rctx):
-        if self.enabled():
-            rctx.add_field(self._display_name, self)
-
-    def serialise(self, dict):
-        s_val = self._type.serialise(self._value)
-        dict[self._name] = s_val
-
-    def deserialise(self, dict):
-        if self._name not in dict:
-            return
-        
-        s_val = dict[self._name]
-        v = self._type.deserialise(s_val)
-        self.__assert_type(v)
-        self._value = v
-
-
-    def __update_value(self):
-        v = self._value
-
-        if not self.enabled():
-            self._env.update_value(self._name, None)
-            return
-
-        if v is None:
-            v = self._default
-
-        if v is None and not self._type.allow_none():
-            raise ConfigLoadException(
-                    f"config: {self._name} must have a value", self)
-        
-        self._value = v
-        self._env.update_value(self._name, v)
-    
-
-
-
-class LCGroupNode(LCFuncNode):
-    def __init__(self, fo, astn) -> None:
-        self._children = {}
-
-        super().__init__(fo, astn)
-
-    @staticmethod
-    def mapped_name():
-        return "Group"
-
-    def prune_astn(self, astn: ast.FunctionDef):
-        astn = super().prune_astn(astn)
-
-        other_exprs = []
-        for expr in astn.body:
-            if not isinstance(expr, ast.FunctionDef):
-                other_exprs.append(expr)
-                continue
-
-            node = LCFuncNode.construct(self._fo, expr)
-            node.set_parent(self)
-            self._children[node] = node
-
-        if not other_exprs:
-            other_exprs.append(
-                ast.Pass(
-                    lineno=astn.lineno + 1, 
-                    col_offset=astn.col_offset
-                )
-            )
-
-        astn.body = other_exprs
-        return astn
-
-    def evaluate(self):
-        old_enable = super().enabled()
-        super().evaluate()
-
-        new_enabled = super().enabled()
-        if not new_enabled and old_enable == new_enabled:
-            return
-
-        with self._env.eval_context(self) as evalc:
-            children = list(self._children.values())
-            for child in children:
-                child.evaluate()
-        
-    def render(self, rctx):
-        for child in self._children.values():
-            child.render(rctx)
-
-    def serialise(self, dict):
-        sub_dict = {}
-        for child in self._children.values():
-            child.serialise(sub_dict)
-        
-        dict[self._name] = sub_dict
-
-    def deserialise(self, dict):
-        if self._name not in dict:
-            return
-        
-        sub_dict = dict[self._name]
-        for child in self._children.values():
-            child.deserialise(sub_dict)
-    
-    def add_child(self, node):
-        self._children[node] = node
-
-    def remove_child(self, node):
-        if node in self._children:
-            del self._children[node]
-
-
-class LCCollectionNode(LCGroupNode):
-    def __init__(self, fo, astn) -> None:
-        super().__init__(fo, astn)
-
-    @staticmethod
-    def mapped_name():
-        return "Collection"
-    
-    def render(self, rctx):
-        _super = super()
-        rctx.add_expandable(
-            self._display_name,
-            self, 
-            lambda _ctx: 
-                _super.render(_ctx)
-        )
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg/types.py b/lunaix-os/scripts/build-tools/lcfg/types.py
deleted file mode 100644 (file)
index 8fc373d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-from .api import TypeProviderBase
-from .utils import is_primitive, is_basic
-
-class PrimitiveType(TypeProviderBase):
-    def __init__(self, type) -> None:
-        super().__init__(type)
-
-    @staticmethod
-    def typedef_matched(typedef):
-        return is_primitive(typedef)
-    
-    def check(self, val):
-        return isinstance(val, self._type)
-    
-    def serialise(self, val):
-        return str(val)
-    
-    def deserialise(self, val):
-        if val.lower() == "true":
-            return True
-        elif val.lower() == "false":
-            return False
-        
-        return self._type(val)
-    
-    def parse_input(self, input_str):
-        return self.deserialise(input_str)
-    
-    def to_input(self, val):
-        return self.serialise(val)
-    
-    def __str__(self) -> str:
-        if isinstance(self._type, type):
-            return f"any with type of {self._type}"
-        return f"exact of value '{self._type}'"
-
-
-class MultipleChoiceType(PrimitiveType):
-    def __init__(self, type) -> None:
-        super().__init__(type)
-
-    @staticmethod
-    def typedef_matched(typedef):
-        if not isinstance(typedef, list):
-            return False
-        return all([is_basic(x) for x in typedef])
-
-    def check(self, val):
-        if not is_basic(val):
-            return False
-        return val in self._type
-    
-    def parse_input(self, input_str):
-        return super().parse_input(input_str)
-
-    def deserialise(self, val):
-        if val.lower() == "true":
-            return True
-        elif val.lower() == "false":
-            return False
-        
-        for sv in self._type:
-            if val != str(sv):
-                continue
-            return type(sv)(val)
-
-        return None
-    
-    def allow_none(self):
-        return None in self._type
-    
-    def __str__(self) -> str:
-        accepted = [f"  * {t}" for t in self._type]
-        return "\n".join([
-            "choose one:",
-            *accepted
-        ])
diff --git a/lunaix-os/scripts/build-tools/lcfg/utils.py b/lunaix-os/scripts/build-tools/lcfg/utils.py
deleted file mode 100644 (file)
index 96e9e2b..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-import ast
-
-def extract_decorators(fn_ast: ast.FunctionDef, fname = None, only_name=False):
-    decors = fn_ast.decorator_list
-    results = []
-    for decor in decors:
-        _args = []
-        _kwargs = []
-        if isinstance(decor, ast.Name):
-            name = decor.id
-        elif isinstance(decor, ast.Call):
-            if not isinstance(decor.func, ast.Name):
-                continue
-            name = decor.func.id
-            _args = decor.args
-            _kwargs = decor.keywords
-        else:
-            continue
-
-        if fname and name != fname:
-            continue
-
-        if only_name:
-            results.append(name)
-            continue
-        
-        unpacked = []
-        kwargs = {}
-        for arg in _args:
-            if isinstance(arg, ast.Constant):
-                unpacked.append(arg.value)
-        
-        for kwarg in _kwargs:
-            if isinstance(kwarg.value, ast.Constant):
-                kwargs[kwarg.arg] = kwarg.value.value
-        
-        results.append((name, unpacked, kwargs))
-
-    return results
-
-def to_displayable(name):
-    l = name.strip().split('_')
-    for i, s in enumerate(l):
-        l[i] = str.upper(s[0]) + s[1:]
-    return " ".join(l)
-
-def is_primitive(val):
-    return val in [int, str, bool]
-
-def is_basic(val):
-    basic = [int, str, bool]
-    return (
-        val in basic or
-        any([isinstance(val, x) for x in basic])
-    )
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/ast_validator.py b/lunaix-os/scripts/build-tools/lcfg2/ast_validator.py
new file mode 100644 (file)
index 0000000..2b63e31
--- /dev/null
@@ -0,0 +1,71 @@
+import ast
+import inspect
+import textwrap
+
+from typing import Callable
+from lib.utils import Schema, ConfigASTVisitor, SourceLogger
+from .common import Schema, ConfigNodeError
+
+class Rule:
+    def __init__(self, t, v, name, fn):
+        self.type = t
+        self.__name = name
+        self.__var = v
+        self.__fn = fn
+        self.__help_msg = inspect.getdoc(fn)
+        self.__help_msg = textwrap.dedent(self.__help_msg.strip())
+
+    def match_variant(self, astn):
+        if not self.__var:
+            return True
+        return self.__var.match(astn)
+    
+    def invoke(self, reducer, node):
+        if self.__fn(reducer._rules, reducer, node):
+           return
+
+        SourceLogger.warn(reducer._cfgn, node, 
+                          f"rule violation: {self.__name}: {self.__help_msg}")
+        # raise ConfigNodeError(reducer._cfgn, 
+        #         f"rule failed: {self.__name}: {self.__help_msg}")
+
+def rule(ast_type: type, variant: Schema, name: str):
+    def __rule(fn: Callable):
+        return Rule(ast_type, variant, name, fn)
+    return __rule
+
+class RuleCollection:
+    def __init__(self):
+        self.__rules = {}
+
+        members = inspect.getmembers(self, lambda p: isinstance(p, Rule))
+        for _, rule in members:
+            t = rule.type
+            if rule.type not in self.__rules:
+                self.__rules[t] = [rule]
+            else:
+                self.__rules[t].append(rule)
+    
+    def execute(self, reducer, node):
+        rules = self.__rules.get(type(node))
+        if not rules:
+            return
+        
+        for rule in rules:
+            if not rule.match_variant(node):
+                continue
+            rule.invoke(reducer, node)
+
+class NodeValidator(ast.NodeTransformer):
+    def __init__(self, all_rules):
+        super().__init__()
+        self._rules = all_rules
+
+    def validate(self, cfgn, astn):
+        self._cfgn = cfgn
+        self.visit(astn)
+
+    def visit(self, node):
+        self._rules.execute(self, node)
+        return super().visit(node)
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
diff --git a/lunaix-os/scripts/build-tools/lcfg2/common.py b/lunaix-os/scripts/build-tools/lcfg2/common.py
new file mode 100644 (file)
index 0000000..2b0a468
--- /dev/null
@@ -0,0 +1,152 @@
+import ast
+
+from lib.utils  import SourceLogger, Schema
+
+class NodeProperty:
+    class PropertyAccessor:
+        def __init__(self, key):
+            self.__key = key
+        def __getitem__(self, node):
+            return node.get_property(self.__key)
+        def __setitem__(self, node, value):
+            node.set_property(self.__key, value)
+        def __delitem__(self, node):
+            node.set_property(self.__key, None)
+
+    Token       = PropertyAccessor("$token")
+    Value       = PropertyAccessor("$value")
+    Type        = PropertyAccessor("$type")
+    Enabled     = PropertyAccessor("$enabled")
+    Status      = PropertyAccessor("$status")
+    Dependency  = PropertyAccessor("$depends")
+    Linkage     = PropertyAccessor("$linkage")
+    Hidden      = PropertyAccessor("hidden")
+    Parent      = PropertyAccessor("parent")
+    Label       = PropertyAccessor("label")
+    Readonly    = PropertyAccessor("readonly")
+    HelpText    = PropertyAccessor("help")
+
+class ConfigNodeError(Exception):
+    def __init__(self, node, *args):
+        super().__init__(*args)
+
+        self.__node = node
+        self.__msg = " ".join([str(x) for x in args])
+
+    def __str__(self):
+        node = self.__node
+        tok: ast.stmt = NodeProperty.Token[node]
+        return (
+            f"{node._filename}:{tok.lineno}:{tok.col_offset}:" +
+            f" fatal error: {node._name}: {self.__msg}"
+        )
+
+
+class ValueTypeConstrain:
+    TypeMap = {
+        "str": str,
+        "int": int,
+        "bool": bool,
+    }
+
+    BinOpOr = Schema(ast.BinOp, op=ast.BitOr)
+
+    def __init__(self, node, rettype):
+        self.__node = node
+        self.__raw = rettype
+        
+        if isinstance(rettype, ast.Expr):
+            value = rettype.value
+        else:
+            value = rettype
+        
+        if isinstance(value, ast.Constant):
+            self.schema = Schema(value.value)
+        
+        elif isinstance(value, ast.Name):
+            self.schema = Schema(self.__parse_type(value.id))
+        
+        elif isinstance(value, ast.BinOp):
+            unions = self.__parse_binop(value)
+            self.schema = Schema(Schema.Union(*unions))
+        
+        else:
+            raise Exception(
+                f"unsupported type definition: {ast.unparse(rettype)}")
+
+    def __parse_type(self, type):
+        if type not in ValueTypeConstrain.TypeMap:
+            SourceLogger.warn(self.__node, self.__raw, 
+                              f"unknwon type: '{type}'. Fallback to 'str'")
+            return str
+        
+        return ValueTypeConstrain.TypeMap[type]
+    
+    def __parse_binop(self, oproot):
+        if isinstance(oproot, ast.Constant):
+            return [oproot.value]
+        
+        if ValueTypeConstrain.BinOpOr != oproot:
+            SourceLogger.warn(
+                self.__node, self.__raw, 
+                "only OR is allowed. Ignoring...")
+            return []
+        
+        return self.__parse_binop(oproot.left) \
+             + self.__parse_binop(oproot.right)
+    
+    def check_type(self, value):
+        return self.schema.match(value)
+    
+    def ensure_type(self, node, val):
+        if self.check_type(val):
+           return
+
+        raise node.config_error(
+                f"unmatched type:",
+                f"expect: '{self.schema}',",
+                f"got: '{type(val).__name__}' (val: {val})") 
+
+class NodeDependency:
+    class SimpleWalker(ast.NodeVisitor):
+        def __init__(self, deps):
+            super().__init__()
+            self.__deps = deps
+        
+        def visit_Name(self, node):
+            self.__deps._names.append(node.id)
+    
+    def __init__(self, nodes, expr: ast.expr):
+        self._names = []
+        self._expr  = ast.unparse(expr)
+
+        expr = ast.fix_missing_locations(expr)
+        self.__exec  = compile(ast.Expression(expr), "", mode='eval')
+
+        NodeDependency.SimpleWalker(self).visit(expr)
+
+    def evaluate(self, value_tables) -> bool:        
+        return eval(self.__exec, value_tables)
+    
+    @staticmethod
+    def try_create(node):
+        expr = NodeProperty.Dependency[node]
+        if not isinstance(expr, ast.expr):
+            return
+        
+        dep = NodeDependency(node, expr)
+        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
diff --git a/lunaix-os/scripts/build-tools/lcfg2/config.py b/lunaix-os/scripts/build-tools/lcfg2/config.py
new file mode 100644 (file)
index 0000000..7ca1942
--- /dev/null
@@ -0,0 +1,118 @@
+import ast, os
+
+from lib.utils import ConfigAST
+from .common import NodeProperty, NodeDependency
+from .nodes import GroupNode, TermNode
+
+
+class ConfigEnvironment:
+    def __init__(self):
+        self.__node_table = {}
+        self.__exec = None
+        self.__globals = {}
+        self.__custom_fn = {
+            "env": lambda x: os.environ.get(x)
+        }
+    
+    def check_dependency(self, node) -> bool:
+        dep = NodeProperty.Dependency[node]
+        
+        if not isinstance(dep, NodeDependency):
+            return node.enabled()
+        
+        value_map = {}
+        for name in dep._names:
+            if name not in self.__node_table:
+                raise node.config_error(f"config: '{name}' does not exists")
+            
+            n = self.__node_table[name]
+            value_map[name] = self.check_dependency(n)
+
+        return node.enabled() and dep.evaluate(value_map)
+    
+    def register_node(self, cfg_node):
+        name = cfg_node._name
+        if name in self.__node_table:
+            raise cfg_node.config_error(f"redefinition of '{name}'")
+        
+        self.__node_table[name] = cfg_node
+    
+    def get_node(self, name):
+        return self.__node_table.get(name)
+    
+    def relocate_children(self):
+        for node in self.__node_table.values():
+            parent = NodeProperty.Parent[node]
+            if not parent:
+                continue
+
+            if not isinstance(parent, str):
+                continue
+            
+            if parent not in self.__node_table:
+                raise Exception(node, "unknow parent: %s"%(parent))
+            
+            parent_node = self.__node_table[parent]
+            if not isinstance(parent_node, GroupNode):
+                raise Exception(node, "not a valid parent: %s"%(parent))
+            
+            parent_node.add_child(node)
+
+    def set_exec_context(self, astns):
+        filtered = []
+        for x in astns:
+            if isinstance(x, ConfigAST.EnterFileMarker):
+                continue
+            if isinstance(x, ConfigAST.LeaveFileMarker):
+                continue
+            filtered.append(x)
+
+        module = ast.Module(filtered, type_ignores=[])
+        module = ast.fix_missing_locations(module)
+
+        self.__exec = compile(module, "", mode='exec')
+
+    def __update_globals(self):
+        g = {}
+
+        exec(self.__exec, g)
+        self.__globals = {
+            **g,
+            **self.__custom_fn
+        }
+
+    def context(self):
+        return self.__globals
+        
+    def refresh(self):
+        if not self.__exec:
+            return
+
+        self.__update_globals()
+
+        for name, node in self.__node_table.items():
+            node.update()
+
+        for name, node in self.__node_table.items():
+            node.sanity_check()
+            NodeProperty.Enabled[node] = self.check_dependency(node)
+
+    def reset(self):
+        self.__node_table.clear()
+
+    def nodes(self):
+        for node in self.__node_table.values():
+            yield node
+
+    def top_levels(self):
+        for node in self.__node_table.values():
+            if NodeProperty.Parent[node] is None:
+                yield node
+
+    def terms(self):
+        for node in self.__node_table.values():
+            if isinstance(node, TermNode):
+                yield node
+
+    def loaded(self):
+        return self.__exec is not None
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/lazy.py b/lunaix-os/scripts/build-tools/lcfg2/lazy.py
new file mode 100644 (file)
index 0000000..1c634fd
--- /dev/null
@@ -0,0 +1,102 @@
+import ast
+
+from .common import NodeProperty
+from lib.utils import Schema, SourceLogger
+
+class LazyLookup:
+    def __init__(self):
+        self.__tab = {}
+    
+    def put(self, lazy):
+        self.__tab[lazy.get_key()] = lazy
+
+    def get(self, key):
+        return self.__tab[key] if key in self.__tab else None
+    
+    def __setitem__(self, key, val):
+        lz = self.__tab[key]
+        lz.resolve_set(val)
+
+    def __getitem__(self, key):
+        lz = self.__tab[key]
+        return lz.resolve_get()
+
+class Lazy:
+    NodeValue     = 'val'
+
+    LazyTypes = Schema.Union(NodeValue)
+    Syntax  = Schema(ast.Attribute, attr=LazyTypes, value=ast.Name)
+
+    def __init__(self, source, type, target, env):
+        self.target = target
+        self.type   = type
+        self.env    = env
+        self.source = source
+
+    def __resolve_type(self):
+        if self.type == Lazy.NodeValue:
+            return NodeProperty.Value
+        return None
+        
+    def resolve_get(self):
+        node = self.env.get_node(self.target)
+
+        accessor = self.__resolve_type()
+        if not accessor:
+            return None
+        
+        status = NodeProperty.Status[node]
+        if status == "Updating":
+            tok = NodeProperty.Token[self.source]
+            SourceLogger.warn(self.source, tok, 
+                    f"cyclic dependency detected: {self.source._name} <-> {self.target}." + 
+                    f"Reusing cached value, maybe staled.")
+        else:
+            node.update()
+
+        return accessor[node] if accessor else None
+    
+    def resolve_set(self, val):
+        node = self.env.get_node(self.target)
+        accessor = self.__resolve_type()
+
+        if NodeProperty.Readonly[node]:
+            raise self.source.config_error(
+                f"{self.target} is readonly")
+
+        if accessor:
+            accessor[node] = val
+        else:
+            raise self.source.config_error(
+                f"invalid type {self.type} for {self.target}")
+
+    def get_key(self):
+        return Lazy.get_key_from(self.type, self.target)
+    
+    @staticmethod
+    def get_key_from(type, target):
+        return f"{type}${target}"
+    
+    @staticmethod
+    def from_astn(cfgnode, astn):
+        if Lazy.Syntax != astn:
+            return None
+        
+        type_ = astn.attr
+        target = astn.value.id
+       
+        return Lazy.from_type(cfgnode, type_, target)
+    
+    @staticmethod
+    def from_type(cfgnode, type_, target):   
+        key = Lazy.get_key_from(type_, target)
+        lz = cfgnode._lazy_table.get(key)
+        
+        if lz:
+            return key
+
+        lz = Lazy(cfgnode, type_, target, cfgnode._env)
+        cfgnode._lazy_table.put(lz)
+
+        return key
+
diff --git a/lunaix-os/scripts/build-tools/lcfg2/nodes.py b/lunaix-os/scripts/build-tools/lcfg2/nodes.py
new file mode 100644 (file)
index 0000000..3b42394
--- /dev/null
@@ -0,0 +1,188 @@
+import ast
+
+from lib.utils   import SourceLogger, Schema
+from .common     import NodeProperty, ConfigNodeError, NodeDependency
+from .lazy       import LazyLookup
+from .rewriter   import ConfigNodeASTRewriter
+
+from .ast_validator import NodeValidator
+from .rules import SyntaxRule
+
+validator = NodeValidator(SyntaxRule())
+
+class ConfigDecorator:
+    Label       = Schema(ast.Constant)
+    Readonly    = Schema(ast.Name, id="readonly")
+    Hidden      = Schema(ast.Name, id="hidden")
+    Flag        = Schema(ast.Name, id="flag")
+    SetParent   = Schema(
+                    ast.NamedExpr, 
+                        target=Schema(ast.Name, id="parent"), 
+                        value=Schema(ast.Name))
+    SetProperty = Schema(
+                    ast.NamedExpr,
+                        target=Schema(ast.Name), 
+                        value=Schema(ast.Name))
+
+
+class ConfigNode:
+    def __init__(self, env, filename, name):
+        self.__props = {}
+        self.__exec = None
+        self._env = env
+        self._filename = filename
+        self._lazy_table = LazyLookup()
+
+        self._name = name
+        
+        NodeProperty.Enabled[self]  = True
+        NodeProperty.Hidden[self]   = False
+        NodeProperty.Readonly[self] = False
+        NodeProperty.Status[self]   = "Empty"
+
+    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)
+        NodeDependency.try_create(self)
+
+        fn_name = f"__fn_{self._name}"
+        args = ast.arguments([], [], None, [], [], None, [])
+        module = ast.Module([
+            ast.FunctionDef(fn_name, args, new_ast.body, []),
+            ast.Assign(
+                [ast.Name("_result", ctx=ast.Store())],
+                ast.Call(ast.Name(fn_name,ctx=ast.Load()), [], [])
+            )
+        ], [])
+
+        module = ast.fix_missing_locations(module)
+        self.__exec = compile(module, self._filename, mode='exec')
+
+    
+    def apply_decorator(self, decor):
+        if ConfigDecorator.Label == decor:
+            NodeProperty.Label[self] = str(decor.value)
+        
+        elif ConfigDecorator.Readonly == decor:
+            NodeProperty.Readonly[self] = True
+        
+        elif ConfigDecorator.Hidden == decor:
+            NodeProperty.Hidden[self] = True  
+        
+        elif ConfigDecorator.SetParent == decor:
+            NodeProperty.Parent[self] = decor.value.id
+
+        elif ConfigDecorator.Flag == decor:
+            NodeProperty.Hidden[self] = True
+            NodeProperty.Readonly[self] = True
+
+        elif ConfigDecorator.SetProperty == decor:
+            self.set_property(decor.target.id, decor.value.id)
+
+        else:
+            fname = self._filename
+            line  = decor.lineno
+            col   = decor.col_offset
+            msg   = f"unknown decorator: {ast.unparse(decor)}"
+            msg   = SourceLogger.fmt_warning(fname, line, col, msg)
+            print(msg)
+
+    def update(self):
+        self.update_status("Updating")
+        
+        try:
+            env = self._env
+            local = {}
+            globl = {
+                **env.context(),
+                "__lzLut__": self._lazy_table
+            }
+
+            exec(self.__exec, globl, local)
+            self.update_status("Latest")
+        
+            return local["_result"]
+        
+        except ConfigNodeError as e:
+            self.update_status("Error")
+            raise e
+        
+        except Exception as e:
+            self.update_status("Error")
+            raise self.config_error(e)
+        
+    def sanity_check(self):
+        pass
+
+    def get_property(self, key):
+        return self.__props[key] if key in self.__props else None
+    
+    def set_property(self, key, value):
+        if value is None:
+            del self.__props[key]
+        else:
+            self.__props[key] = value
+
+    def enabled(self):
+        val = NodeProperty.Value[self]
+        en  = NodeProperty.Enabled[self]
+        parent = NodeProperty.Parent[self]
+
+        if isinstance(val, bool):
+            en = en and val
+        
+        if isinstance(parent, ConfigNode):
+            en = en and parent.enabled()
+        
+        return en
+    
+    def config_error(self, *args):
+        return ConfigNodeError(self, *args)
+    
+    def update_status(self, status):
+        NodeProperty.Status[self] = status
+
+
+class GroupNode(ConfigNode):
+    def __init__(self, env, filename, name):
+        super().__init__(env, filename, name)
+        
+        self._subnodes = {}
+
+    def add_child(self, node):
+        if node._name in self._subnodes:
+            return
+        
+        NodeProperty.Parent[node] = self
+        self._subnodes[node._name] = node
+
+
+class TermNode(ConfigNode):
+    def __init__(self, env, filename, name):
+        super().__init__(env, filename, name)
+
+    def update(self):
+        result = super().update()
+        
+        if NodeProperty.Readonly[self]:
+            NodeProperty.Value[self] = result
+        
+        elif NodeProperty.Value[self] is None:
+            NodeProperty.Value[self] = result
+        
+        return result
+    
+    def sanity_check(self):
+        val = NodeProperty.Value[self]
+        value_typer = NodeProperty.Type[self]
+        value_typer.ensure_type(self, val)
+        
+        super().sanity_check()
+
diff --git a/lunaix-os/scripts/build-tools/lcfg2/rewriter.py b/lunaix-os/scripts/build-tools/lcfg2/rewriter.py
new file mode 100644 (file)
index 0000000..9a42447
--- /dev/null
@@ -0,0 +1,161 @@
+import ast
+
+from lib.utils  import Schema, SourceLogger
+from .lazy       import Lazy
+from .common     import NodeProperty, NodeInverseDependency
+
+class RewriteRule:
+    MaybeBuiltin = Schema(
+                        ast.Call, 
+                            func=Schema(ast.Name),
+                            args=[ast.expr])
+
+    WhenTogglerItem = Schema(
+                            ast.Compare,
+                                left=ast.Name,
+                                ops=[Schema.Union(ast.Is, ast.IsNot)],
+                                comparators=[ast.Constant])
+    
+    WhenToggler = Schema(
+                    Schema.Union(
+                        WhenTogglerItem,
+                        Schema(ast.BoolOp, 
+                            op=ast.And, 
+                            values=Schema.List(WhenTogglerItem))))
+
+class ConfigNodeASTRewriter(ast.NodeTransformer):
+    
+
+    def __init__(self, cfg_node):
+        super().__init__()
+
+        self.__cfg_node = cfg_node
+
+        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()),
+            slice=ast.Constant(name),
+            ctx=ctx,
+        )
+
+    def __gen_accessor(self, orig):
+        key = Lazy.from_astn(self.__cfg_node, orig)
+        if not key:
+            return self.generic_visit(orig)
+        
+        return self.__subscript_accessor(key, orig.ctx, orig)
+    
+    def __gen_dependency(self, node):
+        cfgn = self.__cfg_node
+        dep_expr = NodeProperty.Dependency[cfgn]
+        if dep_expr is None:
+            NodeProperty.Dependency[cfgn] = node
+            return
+
+        if not isinstance(dep_expr, ast.expr):
+            raise cfgn.config_error(
+                f"invalid dependency state: {dep_expr}")
+        
+        dep_expr = ast.BoolOp(ast.And(), [dep_expr, node])
+        NodeProperty.Dependency[cfgn] = dep_expr
+
+    def __gen_when_expr(self, node):
+        and_list = []
+        cfgn = self.__cfg_node
+
+        if RewriteRule.WhenToggler != node:
+            raise cfgn.config_error(
+                f"when(...): invalid expression: {ast.unparse(node)}")
+
+        if RewriteRule.WhenTogglerItem == node:
+            and_list.append(node)
+        else:
+            and_list += node.values
+        
+        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:
+                operator = ast.NotEq()
+            
+            item.left = acc
+            item.ops  = [operator]
+            
+        
+        current = ast.BoolOp(
+                        op=ast.And(), 
+                        values=[ast.Constant(True), *and_list])
+        
+        expr = self.__when_epxr
+        if expr:
+            assert isinstance(expr, ast.expr)
+            current = ast.BoolOp(op=ast.Or(), values=[expr, current])
+
+        self.__when_epxr = current
+
+    def visit_Attribute(self, node):
+        return self.__gen_accessor(node)
+    
+    def visit_Expr(self, node):
+        val = node.value
+        
+        if RewriteRule.MaybeBuiltin != val:
+            return self.generic_visit(node)
+        
+        # Process marker functions
+        name = val.func.id
+        if name == "require":
+            self.__gen_dependency(val.args[0])
+        elif name == "when":
+            self.__gen_when_expr(val.args[0])
+        else:
+            return self.generic_visit(node)
+        
+        return None
+    
+    def visit_Return(self, node):
+        if self.__when_epxr:
+            SourceLogger.warn(self.__cfg_node, node, 
+                              "mixed use of `return` and `when` directive. "
+                              "`when` have higher precedence than `return`. "
+                              "consider remove `return` to avoid confusion")
+            return None
+        return self.generic_visit(node)
+    
+    def visit_Is(self, node):
+        return ast.Eq()
+    
+    def rewrite(self, node):
+        assert isinstance(node, ast.Module)
+        node = self.visit(node)
+
+        expr = self.__when_epxr
+        if not expr:
+            return node
+
+        node.body.append(ast.Return(expr, lineno=0, col_offset=0))
+        return node
diff --git a/lunaix-os/scripts/build-tools/lcfg2/rules.py b/lunaix-os/scripts/build-tools/lcfg2/rules.py
new file mode 100644 (file)
index 0000000..f77460d
--- /dev/null
@@ -0,0 +1,66 @@
+import ast
+
+from .ast_validator import RuleCollection, rule
+from lib.utils import Schema
+
+class SyntaxRule(RuleCollection):
+    NodeAssigment = Schema(ast.Subscript, 
+                            value=Schema(ast.Name, id='__lzLut__'), 
+                            ctx=ast.Store)
+
+    TrivialReturn  = Schema(Schema.Union(
+        ast.Constant,
+        ast.JoinedStr
+    ))
+
+    def __init__(self):
+        super().__init__()
+    
+    @rule(ast.If, None, "dynamic-logic")
+    def __dynamic_logic(self, reducer, node):
+        """
+        Conditional branching could interfering dependency resolving
+        """
+        return False
+    
+    @rule(ast.While, None, "while-loop")
+    def __while_loop(self, reducer, node):
+        """
+        loop construct may impact with readability.
+        """
+        return False
+    
+    @rule(ast.For, None, "for-loop")
+    def __for_loop(self, reducer, node):
+        """
+        loop construct may impact with readability.
+        """
+        return False
+    
+    @rule(ast.ClassDef, None, "class-def")
+    def __class_definition(self, reducer, node):
+        """
+        use of custom class is not recommended
+        """
+        return False
+    
+    @rule(ast.Dict, None, "complex-struct")
+    def __complex_datastruct(self, reducer, node):
+        """
+        use of complex data structure is not recommended
+        """
+        return False
+    
+    @rule(ast.Subscript, NodeAssigment, "side-effect-option")
+    def __side_effect(self, reducer, node):
+        """
+        Option modifying other options dynamically unpredictable behaviour
+        """
+        return False
+    
+    @rule(ast.Return, None, "non-trivial-value")
+    def __nontrivial_return(self, reducer, node):
+        """
+        Use of non-trivial value as default value
+        """
+        return SyntaxRule.TrivialReturn == node.value 
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py b/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py
new file mode 100644 (file)
index 0000000..1c84a77
--- /dev/null
@@ -0,0 +1,50 @@
+import ast
+
+from lib.utils import Schema, ConfigASTVisitor, SourceLogger
+
+class TreeSanitiser(ConfigASTVisitor):
+    DecoNative = Schema(ast.Name, id="native")
+    DecoName   = Schema(ast.Name)
+    DecoNameE  = Schema(ast.NamedExpr)
+    DecoCall   = Schema(ast.Call)
+    DecoConst  = Schema(ast.Constant)
+
+    def __init__(self):
+        super().__init__()
+        self.logger = SourceLogger(self)
+
+        # TODO
+        self.deco_rules = {}
+
+    def __sanitise_decorator(self, node: ast.FunctionDef):
+        deco_map = {}
+
+        for deco in node.decorator_list:
+            name = ""
+            if TreeSanitiser.DecoNative == deco:
+                setattr(node, "__builtin", True)
+                continue
+            elif TreeSanitiser.DecoCall == deco:
+                name = deco.func
+            elif TreeSanitiser.DecoConst == deco:
+                name = f"{deco.value}"
+            elif TreeSanitiser.DecoName == deco:
+                name = f"{deco.id}"
+            elif TreeSanitiser.DecoNameE == deco:
+                name = f"{ast.unparse(deco)}"
+            else:
+                self.logger.warn(deco, "invalid modifier type")
+                continue
+
+            deco_map[name] = deco
+
+        node.decorator_list = [x for x in deco_map.values()]
+        
+        for desc, rule in self.deco_rules.items():
+            if rule(deco_map):
+                continue
+            self.logger.warn(node, desc)
+
+    def _visit_fndef(self, node):
+        self.__sanitise_decorator(node)
+        super()._visit_fndef(node)
diff --git a/lunaix-os/scripts/build-tools/lib/sandbox.py b/lunaix-os/scripts/build-tools/lib/sandbox.py
deleted file mode 100644 (file)
index de28aca..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-import traceback, sys
-
-class InterpreterException(Exception):
-    pass
-
-class Sandbox:
-    def __init__(self, symbols) -> None:
-        self.__syms = globals()
-        self.__syms.update(symbols)
-
-    def execute(self, file):
-        with open(file) as f:
-            return self.executes(f.read(), file)
-    
-    def executes(self, str, file=""):
-        try:
-            local_ctx = {}
-            glb_ctx = self.__syms.copy()
-            exec(str, glb_ctx, local_ctx)
-            return local_ctx
-        except SyntaxError as err:
-            error_class = err.__class__.__name__
-            detail = err.args[0]
-            line_number = err.lineno
-        except Exception as err:
-            error_class = err.__class__.__name__
-            detail = err.args[0]
-            cl, exc, tb = sys.exc_info()
-            line_number = traceback.extract_tb(tb)[1][1]
-
-        print(f"LunaBuild failed: {error_class} at ./{file}:{line_number}, {detail}")
-        raise InterpreterException("load error")
\ No newline at end of file
index 361b78676bb092f566601207f2757c7d084951f7..6dd1ace18baf5700259c289982274b460708e57b 100644 (file)
@@ -1,6 +1,268 @@
-import os
+import os, ast
+from pathlib import Path
+from typing import Any, List
 
 def join_path(stem, path):
     if os.path.isabs(path):
         return path
 
 def join_path(stem, path):
     if os.path.isabs(path):
         return path
-    return os.path.join(stem, path)
\ No newline at end of file
+    return os.path.join(stem, path)
+
+
+class Schema:
+    class Any:
+        def __init__(self):
+            pass
+
+        def __str__(self):
+            return "Any"
+        
+        def __repr__(self):
+            return self.__str__()
+
+    class Union:
+        def __init__(self, *args):
+            self.union = [*args]
+
+        def __str__(self):
+            strs = [Schema.get_type_str(t) for t in self.union]
+            return f"{' | '.join(strs)}"
+        
+        def __repr__(self):
+            return self.__str__()
+        
+    class List:
+        def __init__(self, el_type):
+            self.el_type = el_type
+        
+        def __str__(self):
+            strs = Schema.get_type_str(self.el_type)
+            return f"*{strs}"
+        
+        def __repr__(self):
+            return self.__str__()
+
+    def __init__(self, type, **kwargs):
+        self.__type = type
+        self.__fields = kwargs
+
+    def __match_list(self, actual, expect):
+        if len(actual) != len(expect):
+            return False
+        
+        for a, b in zip(actual, expect):
+            if not self.__match(a, b):
+                return False
+            
+        return True
+    
+    def __match_list_member(self, actual, expect):
+        if not isinstance(actual, List):
+            return False
+        
+        for a in actual:
+            if not self.__match(a, expect.el_type):
+                return False
+            
+        return True
+    
+    def __match_union(self, actual, union):
+        for s in union.union:
+            if self.__match(actual, s):
+                return True
+        return False
+
+    def __match(self, val, scheme):
+        if scheme is Any:
+            return True
+        
+        if isinstance(scheme, Schema):
+            return scheme.match(val)
+        
+        if isinstance(scheme, Schema.List):
+            return self.__match_list_member(val, scheme)
+        
+        if isinstance(scheme, list) and isinstance(val, list):
+            return self.__match_list(val, scheme)
+        
+        if isinstance(scheme, Schema.Union):
+            return self.__match_union(val, scheme)
+        
+        if not isinstance(scheme, type):
+            return scheme == val
+        
+        return isinstance(val, scheme)
+    
+    def match(self, instance):
+        if not self.__match(instance, self.__type):
+            return False
+
+        for field, t in self.__fields.items():
+            if not hasattr(instance, field):
+                return False
+            
+            field_val = getattr(instance, field)
+
+            if not self.__match(field_val, t):
+                return False
+
+        return True
+
+    def __eq__(self, value):
+        if isinstance(value, Schema):
+            return super().__eq__(value)
+        return self.match(value)
+    
+    def __ne__(self, value):
+        return not self.__eq__(value)
+    
+    def __str__(self):
+        fields = ""
+        if len(self.__fields) > 0:
+            fields = ", ".join([
+                f"{name} :: {Schema.get_type_str(t)}" 
+                for name, t in self.__fields.items()])
+            fields = "{%s}"%(fields)
+        
+        return f"{Schema.get_type_str(self.__type)} {fields}"
+    
+    def __repr__(self):
+        return self.__str__()
+    
+    @staticmethod
+    def get_type_str(maybe_type):
+        if isinstance(maybe_type, type):
+            return maybe_type.__name__
+        if isinstance(maybe_type, str):
+            return f'"{maybe_type}"'
+        return str(maybe_type)
+
+class SourceLogger:
+    def __init__(self, visitor):
+        self.__visitor = visitor
+
+    def warn(self, node, fmt, *args):
+        fname = self.__visitor.current_file()
+        line  = node.lineno
+        coln  = node.col_offset
+
+        print(SourceLogger.fmt_warning(fname, line, coln, fmt%args))
+
+    @staticmethod
+    def fmt_message(fname, line, col, level, msg):
+        return "%s:%d:%d: %s: %s"%(fname, line, col, level, msg)
+
+    @staticmethod
+    def fmt_warning(fname, line, col, msg):
+        return SourceLogger.fmt_message(fname, line, col, "warning", msg)
+    
+    @staticmethod
+    def fmt_info(fname, line, col, msg):
+        return SourceLogger.fmt_message(fname, line, col, "info", msg)
+    
+    @staticmethod
+    def fmt_error(fname, line, col, msg):
+        return SourceLogger.fmt_message(fname, line, col, "error", msg)
+    
+    @staticmethod
+    def log(level, node, token, msg):
+        fname = node._filename
+        line  = token.lineno
+        col   = token.col_offset
+        
+        print( SourceLogger.fmt_message(fname, line, col, level, msg))
+        print(f"        at... {ast.unparse(token)}")
+    
+    @staticmethod
+    def warn(node, token, msg):
+        return SourceLogger.log("warning", node, token, msg)
+    
+class ConfigAST:
+    ConfigImport = Schema(ast.ImportFrom, level=1)
+
+    class EnterFileMarker:
+        def __init__(self, filename = None):
+            self.name = filename
+
+    class LeaveFileMarker:
+        def __init__(self):
+            pass
+
+    def __init__(self, root_file, cfg_name = "LConfig"):
+        self.__tree = ast.Module([])
+        self.__cfg_name = cfg_name
+        self.__rootfile = Path(root_file)
+
+        self.__load(self.__rootfile)
+
+    def __load(self, file: Path):
+        parent = file.parent
+        
+        with file.open('r') as f:
+            nodes = ast.parse(f.read()).body
+        
+        relfile = str(file.relative_to(self.__rootfile.parent))
+        marker  = ConfigAST.EnterFileMarker(relfile)
+        self.__append_tree(marker)
+        
+        for node in nodes:
+            if ConfigAST.ConfigImport != node:
+                self.__append_tree(node)
+                continue
+            
+            module = node.module
+            module = "" if not module else module
+            path = parent.joinpath(*module.split('.'))
+            for alia in node.names:
+                p = path / alia.name / self.__cfg_name
+                self.__load(p)
+        
+        self.__append_tree(ConfigAST.LeaveFileMarker())
+    
+    def __append_tree(self, node):
+        self.__tree.body.append(node)    
+
+    def visit(self, visitor):
+        visitor.visit(self.__tree)
+
+class ConfigASTVisitor:
+    def __init__(self):
+        self.__markers = []
+
+    def _visit_fndef(self, node : ast.FunctionDef):
+        self._visit_subtree(node)
+
+    def _visit_leaf(self, node):
+        pass
+
+    def _visit_subtree(self, node):
+        for n in node.body:
+            self.visit(n)
+
+    def _visit_expr(self, node : ast.Expr):
+        pass
+
+    def current_file(self):
+        if len(self.__markers) == 0:
+            return "<root>"
+        return self.__markers[-1].name
+
+    def visit(self, node):
+        if isinstance(node, ConfigAST.EnterFileMarker):
+            self.__markers.append(node)
+            return
+        
+        if isinstance(node, ConfigAST.LeaveFileMarker):
+            self.__markers.pop()
+            return
+
+        if isinstance(node, ast.FunctionDef):
+            self._visit_fndef(node)
+        
+        elif isinstance(node, ast.Expr):
+            self._visit_expr(node)
+        
+        elif hasattr(node, "body"):
+            self._visit_subtree(node)
+        
+        else:
+            self._visit_leaf(node)
index 6b8e8979eaad764bb8a13cc10124dc6ec57c4f4f..0f96e3e4dfd5901e99bf01e5a0d66b353140aff2 100755 (executable)
 #!/usr/bin/env python 
 #!/usr/bin/env python 
-
-from lbuild.contract import LunaBuildFile
-from lbuild.common import BuildEnvironment
-
-from lcfg.common import LConfigEnvironment
-from integration.config_io import CHeaderConfigProvider
-from integration.lbuild_bridge import LConfigProvider
-from integration.render_ishell import InteractiveShell
-from integration.build_gen import MakefileBuildGen, install_lbuild_functions
-from integration.lunamenu import menuconfig, TerminalSizeCheckFailed
-
-import lcfg.types as lcfg_type
-import lcfg.builtins as builtin
-
-from os import getcwd
-from os import mkdir
-from os.path import abspath, basename, dirname, exists
-from argparse import ArgumentParser
-from lib.utils import join_path
-
-def prepare_lconfig_env(out_dir):
-    provider = CHeaderConfigProvider(join_path(out_dir, "configs.h"))
-    env = LConfigEnvironment(getcwd(), provider)
-
-    env.register_builtin_func(builtin.v)
-    env.register_builtin_func(builtin.term_type)
-    env.register_builtin_func(builtin.parent)
-    env.register_builtin_func(builtin.default)
-    env.register_builtin_func(builtin.include)
-    env.register_builtin_func(builtin.env)
-    env.register_builtin_func(builtin.set_value)
-
-    env.type_factory().regitser(lcfg_type.PrimitiveType)
-    env.type_factory().regitser(lcfg_type.MultipleChoiceType)
-
-    return env
-
-def do_config(opt, lcfg_env):
-    redo_config = not exists(opt.config_save) or opt.force
-    if not redo_config or opt.quiet:
-        return
-
-    try:
-        clean_quit = menuconfig(lcfg_env)
-    except TerminalSizeCheckFailed as e:
-        least = e.args[0]
-        current = e.args[1]
-        print(
-            f"Your terminal size: {current} is less than minimum requirement of {least}.\n"
-            "menuconfig will not function properly, switch to prompt based.\n")
-
-        shell = InteractiveShell(lcfg_env)
-        clean_quit = shell.render_loop()
+from argparse           import ArgumentParser
+from pathlib            import Path
+
+from lbuild.build       import BuildEnvironment
+from lbuild.scope       import ScopeProvider
+from lcfg2.builder      import NodeBuilder
+from lcfg2.config       import ConfigEnvironment
+from lcfg2.common       import ConfigNodeError
+
+from shared.export      import ExportJsonFile
+from shared.export      import ExportHeaderFile
+from shared.export      import ExportMakefileRules
+from shared.export      import restore_config_value
+from shared.scopes      import ConfigScope, EnvScope
+from shared.build_gen   import BuildScriptGenerator
+from shared.shconfig    import shconfig
+
+class LunaBuild:
+    def __init__(self, options):
+        self.__lbuilder = BuildEnvironment()
+        self.__lconfig  = ConfigEnvironment()
+        self.__opt = options
+
+        scope = ConfigScope(self.__lconfig)
+        self.__lbuilder.register_scope(scope)
+
+        scope = ScopeProvider("src")
+        scope.file_subscope("c")
+        scope.file_subscope("h")
+        self.__lbuilder.register_scope(scope)
+
+        scope = ScopeProvider("flag")
+        scope.subscope("cc")
+        scope.subscope("ld")
+        self.__lbuilder.register_scope(scope)
+
+        self.__lbuilder.register_scope(EnvScope())
+
+        self.__json  = ExportJsonFile(self.__lconfig)
+        self.__make  = ExportMakefileRules(self.__lconfig)
+        self.__headr = ExportHeaderFile(self.__lconfig)
+        self.__build = BuildScriptGenerator(self.__lbuilder)
+
+    def load(self):
+        file = self.__opt.lconfig
+        NodeBuilder.build(self.__lconfig, file)
+
+        file = self.__opt.lbuild
+        self.__lbuilder.load(file)
+
+        self.__lconfig.refresh()
+        self.__lbuilder.update()
     
     
-    if not clean_quit:
-        print("Configuration aborted. Nothing has been saved.")
-        exit(-1)
-
-def do_buildfile_gen(opts, lcfg_env):
-    root_path = abspath(opts.root)
-    ws_path = dirname(root_path)
-    root_name = basename(root_path)
-
-    mkgen = MakefileBuildGen(opts.out_dir)
-    env = BuildEnvironment(ws_path, mkgen)
-
-    install_lbuild_functions(env)
-
-    cfg_provider = LConfigProvider(lcfg_env)
-    env.set_config_provider(cfg_provider)
-
-    root = LunaBuildFile(env, root_name)
-
-    try:
-        root.resolve()
-    except Exception as err:
-        print("failed to resolve root build file")
-        raise err
+    def restore(self):
+        save = self.__opt.save
+        if not Path(save).exists():
+            return False
+
+        restore_config_value(self.__lconfig, save)
+        return True
+
+    def save(self):
+        save = self.__opt.save
+        self.__json.export(save)
+        return True
     
     
-    env.export()
+    def generate(self):
+        outdir = Path(self.__opt.export_dir)
+        if not outdir.exists():
+            outdir.mkdir()
+
+        if self.__opt.gen_build:
+            self.__build.generate(outdir / "build.mkinc")
+
+        if self.__opt.gen_config:
+            self.__make.export(outdir / "config.mkinc")
+            self.__headr.export(outdir / "config.h")
+
+    def visual_config(self):
+        if not self.__lconfig.loaded():
+            print("no config file loaded, skipped interactive config")
+            return
+        
+        if not self.__opt.gen_config:
+            return
+        
+        if not shconfig(self.__lconfig):
+            print("configuration process aborted")
+            exit(1)
 
 def main():
     parser = ArgumentParser()
 
 def main():
     parser = ArgumentParser()
-    parser.add_argument("--config", action="store_true", default=False)
-    parser.add_argument("--quiet", action="store_true", default=False)
-    parser.add_argument("--lconfig-file", default="LConfig")
-    parser.add_argument("--config-save", default=".config.json")
-    parser.add_argument("--force", action="store_true", default=False)
-    parser.add_argument("root", nargs="?", default="LBuild")
-    parser.add_argument("-o", "--out-dir", required=True)
+    parser.add_argument("--lconfig", default="LConfig")
+    parser.add_argument("--lbuild", default="LBuild")
+    parser.add_argument("--save", default=".config.json")
+    parser.add_argument("--gen-build", action="store_true", default=False)
+    parser.add_argument("--gen-config", action="store_true", default=False)
+    parser.add_argument("export_dir")
 
     opts = parser.parse_args()
 
     opts = parser.parse_args()
-    out_dir = opts.out_dir
-    if not exists(out_dir):
-        mkdir(out_dir)
-    
-    lcfg_env = prepare_lconfig_env(out_dir)
-    require_config = exists(opts.lconfig_file)
+    builder = LunaBuild(opts)
+
     try:
     try:
-        if require_config:
-            lcfg_env.resolve_module(opts.lconfig_file)
-            lcfg_env.update()
-            lcfg_env.load()
-    except Exception as e:
+        builder.load()
+        builder.restore()
+    except ConfigNodeError as e:
         print(e)
         print(e)
+        exit(1)
     
     
-    if opts.config:
-        if require_config:
-            do_config(opts, lcfg_env)
-        else:
-            print("No configuration file detected, skipping...")
-        
-        lcfg_env.update()
-        lcfg_env.save(opts.config_save)
-        lcfg_env.export()
+    builder.visual_config()
+    
+    builder.save()
+    builder.generate()
 
 
-    do_buildfile_gen(opts, lcfg_env)
 
 if __name__ == "__main__":
     main()
\ No newline at end of file
 
 if __name__ == "__main__":
     main()
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/build_gen.py b/lunaix-os/scripts/build-tools/shared/build_gen.py
new file mode 100644 (file)
index 0000000..13f98c2
--- /dev/null
@@ -0,0 +1,77 @@
+from os.path      import isdir, exists
+from lbuild.build import BuildEnvironment
+from lbuild.scope import ScopeAccessor
+
+def gen_source_file(subscope : ScopeAccessor):
+    items = " ".join(subscope.values())
+    return [f"BUILD_SRCS := {items}"]
+
+def gen_header_file(subscope : ScopeAccessor):
+    inc, hdr = [], []
+    for x in subscope.values():
+        if not exists(x):
+            print(f"warning: '{x}' does not exists, skipped")
+            continue
+
+        if isdir(x):
+            inc.append(x)
+        else:
+            hdr.append(x)
+            
+    return [
+        f"BUILD_INC := {' '.join(inc)}",
+        f"BUILD_HDR := {' '.join(hdr)}",
+    ]
+
+def gen_ccflags(subscope : ScopeAccessor):
+    items = " ".join(subscope.values())
+    return [f"BUILD_CFLAGS := {items}"]
+
+def gen_ldflags(subscope : ScopeAccessor):
+    items = " ".join(subscope.values())
+    return [f"BUILD_LDFLAGS := {items}"]
+
+
+class BuildScriptGenerator:
+    def __init__(self, env: BuildEnvironment):
+        self.__gen_policy = {
+            "src": {
+                "c": gen_source_file,
+                "h": gen_header_file
+            },
+            "flag": {
+                "cc": gen_ccflags,
+                "ld": gen_ldflags
+            }
+        }
+
+        self.__env = env
+
+    def __gen_lines(self, scope, subscope):
+        
+        policy = self.__gen_policy.get(scope.name)
+        if policy is None:
+            print( "warning: no associated policy with"
+                  f"'{scope.name}'"
+                   ", skipped")
+            return []
+        
+        policy = policy.get(subscope.name)
+        if policy is None:
+            print( "warning: no associated policy with "
+                  f"'{scope.name}.{subscope.name}' "
+                   ", skipped")
+            return []
+        
+        return policy(subscope)
+    
+    def generate(self, file):
+        lines = []
+
+        for scope in self.__env.scopes.values():
+            for subscope in scope.accessors():
+                lines += self.__gen_lines(scope, subscope)
+
+        with open(file, 'w') as f:
+            f.write('\n'.join(lines))
+        
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/export.py b/lunaix-os/scripts/build-tools/shared/export.py
new file mode 100644 (file)
index 0000000..b2c7bb0
--- /dev/null
@@ -0,0 +1,116 @@
+from lcfg2.config import ConfigEnvironment
+from lcfg2.common import NodeProperty
+from lib.utils import SourceLogger
+import json
+import re
+
+Identifier = re.compile(r"^[A-Za-z_][A-Za-z0-9_]+$")
+
+class Exporter:
+    def __init__(self, env: ConfigEnvironment):
+        self._env = env
+
+    def export(self, file):
+        lines = []
+
+        for node in self._env.terms():
+            for name, val in self.variants(node):
+                line = self._format_line(node, name, val)
+                lines.append(line)
+
+        with open(file, 'w') as f:
+            f.write('\n'.join(lines))
+
+    def _format_line(self, node, name, value):
+        return ""
+
+    def variants(self, node):
+        value    = self.get_value(node)
+        basename = f"CONFIG_{str.upper(node._name)}"
+
+        return [(basename, value)]
+    
+    def get_value(self, node):
+        return ""
+
+
+class ExportMakefileRules(Exporter):
+    def __init__(self, env: ConfigEnvironment):
+        super().__init__(env)
+
+    def _format_line(self, node, name, value):
+        if not node.enabled():
+            return f"# {name} is disabled"
+        else:
+            return f"{name} := {value}"
+
+    def get_value(self, node):
+        tok = NodeProperty.Token[node]
+        val = NodeProperty.Value[node]
+
+        if type(val) is str:
+            return val
+
+        if type(val) is int:
+            return val
+        
+        if type(val) is bool:
+            return ""
+        
+        SourceLogger.warn(node, tok, 
+            f"value: '{val}' can not mapped to makefile primitives")
+        
+        return "## ERROR ##"
+    
+class ExportHeaderFile(Exporter):
+    def __init__(self, env: ConfigEnvironment):
+        super().__init__(env)
+
+    def _format_line(self, node, name, value):
+        if not node.enabled():
+            return f"// {name} is disabled"
+        else:
+            return f"#define {name} {value}"
+
+    def get_value(self, node):
+        tok = NodeProperty.Token[node]
+        val = NodeProperty.Value[node]
+
+        if type(val) is str:
+            return f'"{val}"'
+
+        if type(val) is int:
+            return val
+        
+        if type(val) is bool:
+            return ""
+        
+        SourceLogger.warn(node, tok, 
+            f"value: '{val}' can not mapped to C primitives")
+        
+        return "// Error"
+
+class ExportJsonFile(Exporter):
+    def __init__(self, env):
+        super().__init__(env)
+
+    def export(self, file):
+        obj = {}
+        for n in self._env.terms():
+            obj[n._name] = NodeProperty.Value[n]
+        
+        with open(file, 'w') as f:
+            json.dump(obj, f)
+
+def restore_config_value(env: ConfigEnvironment, json_path):
+    with open(json_path) as f:
+        j = json.load(f)
+        for k, v in j.items():
+            node = env.get_node(k)
+            if node is None:
+                print(f"warning: missing node: '{node}', skipped")
+                continue
+
+            NodeProperty.Value[node] = v
+
+        env.refresh()
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/scopes.py b/lunaix-os/scripts/build-tools/shared/scopes.py
new file mode 100644 (file)
index 0000000..507280b
--- /dev/null
@@ -0,0 +1,26 @@
+import os
+
+from lbuild.scope import ScopeProvider
+from lcfg2.common import NodeProperty
+
+class ConfigScope(ScopeProvider):
+    def __init__(self, env):
+        super().__init__("config")
+        self.__env = env
+
+    def __getitem__(self, name):
+        node = self.__env.get_node(name)
+        if node is None:
+            raise Exception(f"config '{name}' not exists")
+        
+        if not NodeProperty.Enabled[node]:
+            return None
+
+        return NodeProperty.Value[node]
+
+class EnvScope(ScopeProvider):
+    def __init__(self):
+        super().__init__("env")
+
+    def __getitem__(self, name):
+        return os.environ.get(name)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/shconfig/__init__.py b/lunaix-os/scripts/build-tools/shared/shconfig/__init__.py
new file mode 100644 (file)
index 0000000..9fe76d7
--- /dev/null
@@ -0,0 +1 @@
+from .main import shconfig
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/shconfig/commands.py b/lunaix-os/scripts/build-tools/shared/shconfig/commands.py
new file mode 100644 (file)
index 0000000..c668564
--- /dev/null
@@ -0,0 +1,215 @@
+import textwrap
+import pydoc
+import re
+
+from .common import CmdTable, ShconfigException
+from .common import select, cmd, get_config_name
+
+from lcfg2.config   import ConfigEnvironment
+from lcfg2.common   import NodeProperty, NodeDependency, ConfigNodeError
+
+class Commands(CmdTable):
+    def __init__(self, env: ConfigEnvironment):
+        super().__init__()
+
+        self.__env = env
+
+    def __get_node(self, name: str):
+        node_name = name.removeprefix("CONFIG_").lower()
+        node = self.__env.get_node(node_name)
+        if node is None:
+            raise ShconfigException(f"no such config: {name}")
+        return node
+    
+    def __get_opt_line(self, node, color_hint = False):
+        aligned = 40
+        name    = f"CONFIG_{node._name.upper()}"
+        value   = NodeProperty.Value[node]
+        enabled = NodeProperty.Enabled[node]
+        hidden  = NodeProperty.Hidden[node]
+        ro      = NodeProperty.Readonly[node]
+
+        status  = f"{select(not enabled, 'x', '.')}"          \
+                f"{select(ro, 'r', '.')}"          \
+                f"{select(hidden, 'h', '.')}"      \
+                                    
+        val_txt = f"{value if value is not None else '<?>'}"
+
+        if value is True:
+            val_txt = "y"
+        elif value is False:
+            val_txt = "n"
+        elif isinstance(value, str):
+            val_txt = f'"{val_txt}"'
+                
+        line = f"[{status}] {name}"
+        to_pad = max(aligned - len(line), 4)
+        line = f"{line} {'.' * to_pad} {val_txt}"
+
+        if color_hint and not enabled:
+            line = f"\x1b[90;49m{line}\x1b[0m"
+        return line
+    
+    def __format_config_list(self, nodes):
+        lines = []
+        disabled = []
+        
+        for node in nodes:
+            _l = disabled if not NodeProperty.Enabled[node] else lines
+            _l.append(self.__get_opt_line(node, True))
+
+        if disabled:
+            lines += [
+                "",
+                "\t---- disabled ----",
+                "",
+                *disabled
+            ]
+
+        return lines
+    
+    @cmd("help", "h")
+    def __fn_help(self):
+        """
+        Print this message
+        """
+
+        print()
+        for exe in self._cmd_map:
+            print(exe, "\n")
+
+    @cmd("show", "ls")
+    def __fn_show(self):
+        """
+        Show all configurable options
+        """
+        
+        lines = [
+            "Display format:",
+            "",
+            "        (flags) CONFIG_NAME ..... VALUE",
+            " ",
+            "   (flags)",
+            "      x    Config is disabled",
+            "      r    Read-Only config",
+            "      h    Hidden config",
+            "",
+            "   VALUE (bool)",
+            "      y    True",
+            "      n    False",
+            "",
+            "",
+            "Defined configuration terms",
+            ""
+        ]
+
+        lines += self.__format_config_list(self.__env.terms())
+
+        pydoc.pager("\n".join(lines))
+
+    @cmd("set")
+    def __fn_set(self, name: str, value):
+        """
+        Update a configurable option's value
+        """
+        
+        node = self.__get_node(name)
+        if node is None:
+            raise ShconfigException(f"no such config: {name}")
+        
+        if NodeProperty.Readonly[node]:
+            raise ShconfigException(f"node is read only")
+        
+        try:
+            NodeProperty.Value[node] = value
+            self.__env.refresh()
+        except ConfigNodeError as e:
+            print(e)
+
+    @cmd("dep")
+    def __fn_dep(self, name: str):
+        """
+        Show the dependency chain and boolean conditionals
+        """
+
+        def __print_dep_recursive(env, node, inds = 0):
+            indent = " "*inds
+            dep: NodeDependency = NodeProperty.Dependency[node]
+            
+            state = 'enabled' if NodeProperty.Value[node] else 'disabled'
+            print(f"{indent}* {node._name} (currently {state})")
+            if dep is None:
+                return
+            
+            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, inds + 6)
+
+        node = self.__get_node(name)
+        __print_dep_recursive(self.__env, node)
+
+    @cmd("opt", "val", "v")
+    def __fn_opt(self, name: str):
+        """
+        Show the current value and flags of selected options
+        """
+
+        node = self.__get_node(name)        
+        print(self.__get_opt_line(node))
+
+    @cmd("what", "help", "?")
+    def __fn_what(self, name: str):
+        """
+        Show the documentation associated with the option
+        """
+
+        node = self.__get_node(name)
+        help = NodeProperty.HelpText[node]
+        help = "<no help message>" if not help else help
+
+        print()
+        print(textwrap.indent(help.strip(), "  |\t", lambda _:True))
+        print()
+
+
+    @cmd("affects")
+    def __fn_affect(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()
+    
+    @cmd("find")
+    def __fn_search(self, fuzz: str):
+        """
+        Perform fuzzy search on configs (accept regex)
+        """
+
+        nodes = []
+        expr = re.compile(fuzz)
+        for node in self.__env.terms():
+            name = get_config_name(node._name)
+            if not expr.findall(name):
+                continue
+            nodes.append(node)
+
+        if not nodes:
+            print("no matches")
+            return
+
+        lines = self.__format_config_list(nodes)
+
+        pydoc.pager("\n".join(lines))
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/shconfig/common.py b/lunaix-os/scripts/build-tools/shared/shconfig/common.py
new file mode 100644 (file)
index 0000000..c9825e9
--- /dev/null
@@ -0,0 +1,106 @@
+import inspect
+import textwrap
+
+from typing         import Callable, Any
+from lib.utils      import Schema
+
+def select(val, _if, _else):
+    return _if if val else _else
+
+def get_config_name(name):
+    return f"CONFIG_{name.upper()}"
+
+def cmd(name, *alias):
+    def __cmd(fn: Callable):
+        fn.__annotations__["__CMD__"] = True
+        fn.__annotations__["__NAME__"] = name
+        fn.__annotations__["__ALIAS__"] = [*alias]
+        return fn
+    return __cmd
+
+def imply_schema(fn: Callable):
+    type_map = inspect.get_annotations(fn)
+    arg_list = []
+
+    for arg in inspect.getargs(fn.__code__).args:
+        if arg == "self":
+            continue
+
+        t = type_map.get(arg)
+        t = t if t else Any
+
+        arg_list.append((arg, t))
+    
+    return Schema([x[1] for x in arg_list]), arg_list
+
+class ShconfigException(Exception):
+    def __init__(self, *args):
+        super().__init__(*args)
+
+    def __str__(self):
+        return str(self.args[0])
+
+class Executor:
+    def __init__(self, body: Callable):
+        self.name = body.__annotations__["__NAME__"]
+        self.alias = body.__annotations__["__ALIAS__"]
+        self.help = inspect.getdoc(body)
+        self.help = textwrap.dedent(self.help if self.help else "")
+        
+        schema, args = imply_schema(body)
+        self.argstr  = ' '.join(
+            [f'<{n.upper()}: {t.__name__}>' for n, t in args])
+        
+        self.__fn = body
+        self.__argtype = schema
+
+    def match_name(self, name):
+        return self.name == name or name in self.alias
+
+    def try_invoke(self, *args):
+        t_args = [self.__type_mapper(x) for x in args]
+        if self.__argtype != t_args:
+            raise ShconfigException(
+                f"invalid parameter ({args}), expect: ({self.argstr})")
+    
+        self.__fn(*t_args)
+
+    def __type_mapper(self, strtype):
+        if strtype in ['True', 'False']:
+            return bool(strtype)
+        if strtype in ['y', 'n']:
+            return bool(strtype == 'y')
+        
+        try: return int(strtype)
+        except: pass
+
+        return strtype
+    
+    def __str__(self):
+        return '\n'.join([
+            *[f"{name} {self.argstr}" for name in self.alias + [self.name]],
+            textwrap.indent(self.help, '\t')
+        ])
+
+class CmdTable:     
+    def __init__(self):
+        self._cmd_map = []
+
+        fns = inspect.getmembers(self, 
+                                 lambda p: isinstance(p, Callable))
+        for _, fn in fns:
+            if not hasattr(fn, "__annotations__"):
+                continue
+            if "__CMD__" not in fn.__annotations__:
+                continue
+
+            self._cmd_map.append(Executor(fn))
+        
+    def call(self, name, *args):
+        for exe in self._cmd_map:
+            if exe.match_name(name):
+                exe.try_invoke(*args)
+                return
+
+        raise ShconfigException(
+                f"command not found {name}")
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/shconfig/main.py b/lunaix-os/scripts/build-tools/shared/shconfig/main.py
new file mode 100644 (file)
index 0000000..216e8e6
--- /dev/null
@@ -0,0 +1,80 @@
+import readline, textwrap
+
+from shlex          import split as shsplit
+from rlcompleter    import Completer
+from lcfg2.config   import ConfigEnvironment
+from .common        import ShconfigException, get_config_name
+from .commands      import Commands
+
+class ConfigNameCompleter(Completer):
+    def __init__(self, env: ConfigEnvironment):
+        super().__init__(None)
+
+        self.__options = []
+        self.__config_set= [
+            get_config_name(x._name)
+            for x in env.terms()
+        ]
+
+    def complete(self, text, state):
+        if state == 0:
+            text = text if text else ""
+            self.__options = [
+                x for x in self.__config_set if x.startswith(text)]
+        
+        return None if not self.__options else self.__options[state]
+
+
+def next_input(cmds: Commands):
+    line = input("shconfig> ")
+
+    if len(line) == 0:
+        return True
+    
+    parts = shsplit(line)
+    name, args = parts[0], parts[1:]
+
+    if name in ['q', 'exit']:
+        return False
+    
+    if name == "q!":
+        raise KeyboardInterrupt()
+    
+    if name.startswith("CONFIG_"):
+        cmds.call("opt", name)
+        return True
+
+    cmds.call(name, *args)
+    return True
+
+def shconfig(env: ConfigEnvironment):
+    print(
+        "\n",
+        textwrap.dedent(
+            
+            """
+            Lunaix Interactive Configurator (shconfig)
+            
+            Type "help" to see all commands avaliables
+            Type "q" or "exit" to confirm and exit
+            Type "q!" or use ^C to discard and abort
+
+            """
+        ).strip(), "\n")
+
+    cmds = Commands(env)
+    cmpleter = ConfigNameCompleter(env)
+    readline.parse_and_bind('tab: complete')
+    readline.set_completer(cmpleter.complete)
+
+    while True:
+        try:
+            if not next_input(cmds):
+                return True
+        except ShconfigException as e:
+            print(str(e))
+            continue
+        except KeyboardInterrupt as e:
+            return False
+        except Exception as e:
+            raise e
\ No newline at end of file
index 0561206b9c4d4987227391bd211ce641577577ef..05a12461912f94768e8e0c88e6a2e436039b32a8 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "testp",
     "ls",
     "signal_demo",
     "testp",
     "ls",
     "signal_demo",
@@ -10,36 +10,43 @@ sources([
     "mkdir",
     "rm",
     "fragfile",
     "mkdir",
     "rm",
     "fragfile",
-])
+)
 
 
-compile_opts([
+flag.cc += (
     "-ffreestanding",
     "-ffreestanding",
-    "-fno-pie"
-])
+    "-fno-pie",
+    "-Werror"
+)
 
 
-linking_opts([
+flag.ld += (
     "-nostdlib", 
     "-nolibc", 
     "-z noexecstack", 
     "-no-pie", 
     "-nostdlib", 
     "-nolibc", 
     "-z noexecstack", 
     "-no-pie", 
-])
+)
 
 
-linking_opts([
+flag.ld += (
     "-Wl,--build-id=none"
     "-Wl,--build-id=none"
-])
+)
 
 
-if config("arch") == "x86_64":
-    compile_opts([
-        "-m64", 
-        "-fno-unwind-tables", 
-        "-fno-asynchronous-unwind-tables",
-        "-mcmodel=large"
-    ])
-    linking_opts([
-        "-m64",
-    ])
-else:
-    compile_opts("-m32")
-    linking_opts("-m32")
+match env.ARCH:
+    case "x86_64":
+        flag.cc += (
+            "-m64", 
+            "-fno-unwind-tables", 
+            "-fno-asynchronous-unwind-tables",
+            "-mcmodel=large",
+            "-DCONFIG_ARCH_X86_64"
+        )
+        flag.ld += (
+            "-m64",
+        )
+    case "i386":
+        flag.cc += "-m32", "-DCONFIG_ARCH_X86_32"
+        flag.ld += "-m32"
 
 
-compile_opts("-mno-sse")
\ No newline at end of file
+flag.cc += "-mno-sse"
+
+flag.cc += (
+    "-Wno-discarded-qualifiers"
+)
\ No newline at end of file
diff --git a/lunaix-os/usr/LConfig b/lunaix-os/usr/LConfig
deleted file mode 100644 (file)
index 3f44fe2..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-@Term("Architecture")
-def arch():
-    """
-        set the ISA target
-    """
-    type(["i386", "x86_64", "aarch64", "rv64"])
-    default("i386")
-
-    env_val = env("ARCH")
-    if env_val is not None:
-        set_value(env_val)
\ No newline at end of file
index 5c77fe202462e59c7616be63aca777b3360f9b43..148835f5b5dab9ca634c2e45577f9ee113d86586 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "src/string.c",
     "src/termios.c",
     "src/itoa.c",
     "src/string.c",
     "src/termios.c",
     "src/itoa.c",
@@ -6,9 +6,9 @@ sources([
     "src/readdir.c",
     "src/pthread.c",
     "src/printf.c"
     "src/readdir.c",
     "src/pthread.c",
     "src/printf.c"
-])
+)
 
 
-sources([
+src.c += (
     "src/posix/signal.c",
     "src/posix/mount.c",
     "src/posix/errno.c",
     "src/posix/signal.c",
     "src/posix/mount.c",
     "src/posix/errno.c",
@@ -18,11 +18,10 @@ sources([
     "src/posix/unistd.c",
     "src/posix/mann.c",
     "src/posix/lunaix.c"
     "src/posix/unistd.c",
     "src/posix/mann.c",
     "src/posix/lunaix.c"
-])
+)
 
 
-use({
-    env("ARCH"): {
-        "i386": "arch/i386",
-        "x86_64": "arch/x86_64",
-    }
-})
\ No newline at end of file
+match env.ARCH:
+    case "i386":
+        from .arch import i386
+    case "x86_64":
+        from .arch import x86_64
index be547c8168596fc09f297259b5224237ef93fbbb..6dfebc1e3e1ed3d6a098639d58678dc18b4b2b44 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "crt0.S",
     "syscall.S",
     "trampoline.S",
     "crt0.S",
     "syscall.S",
     "trampoline.S",
-])
+)
 
 
-compile_opts("-m32")
-linking_opts("-m32")
\ No newline at end of file
+flag.cc += "-m32"
+flag.ld += "-m32"
\ No newline at end of file
index 8e0ed54d4a1e3b38a92323bede9429ab72d2c84b..e13a4beefa32453393d680f3fd6900dbfc10d98c 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "crt0.S",
     "syscall.S",
     "trampoline.S",
     "crt0.S",
     "syscall.S",
     "trampoline.S",
-])
+)
 
 
-compile_opts("-m64")
-linking_opts("-m64")
\ No newline at end of file
+flag.cc += "-m64"
+flag.ld += "-m64"
\ No newline at end of file
index 0c03770226d4a59d63f19b0bf67e0d6cbe7b5700..223ce4089ad89b58bc06408da3caccc263e2cb8b 100644 (file)
@@ -10,12 +10,12 @@ BUILD_NAME ?= liblunac
 src_dirs := src
 src_dirs += arch/$(ARCH)
 
 src_dirs := src
 src_dirs += arch/$(ARCH)
 
-obj_files := $(addsuffix .o, $(_LBUILD_SRCS))
+obj_files := $(addsuffix .o, $(BUILD_SRCS))
 
 build_lib := $(BUILD_DIR)/lib
 
 libc_include_opt = $(addprefix -I, $(libc_include))
 
 build_lib := $(BUILD_DIR)/lib
 
 libc_include_opt = $(addprefix -I, $(libc_include))
-global_include_opt = $(addprefix -I, $(INCLUDES) $(_LBUILD_INCS))
+global_include_opt = $(addprefix -I, $(INCLUDES) $(BUILD_INC))
 
 check_folders := $(src_dirs)
 check_folders += $(build_lib) $(LIBC_INCLUDE)
 
 check_folders := $(src_dirs)
 check_folders += $(build_lib) $(LIBC_INCLUDE)
index 49036fc5629967a2832284f464d8d4ec58912336..8fa1c316e75ba6d076e9e4ab4546f594849d4322 100644 (file)
@@ -1,8 +1,6 @@
 include utils.mkinc
 include toolchain.mkinc
 
 include utils.mkinc
 include toolchain.mkinc
 
-LCONFIG_FLAGS := --quiet
-
 include lunabuild.mkinc
 
 include $(lbuild_mkinc)
 include lunabuild.mkinc
 
 include $(lbuild_mkinc)
@@ -11,6 +9,9 @@ ifndef ARCH
 $(error ARCH is not set)
 endif
 
 $(error ARCH is not set)
 endif
 
+CFLAGS += $(BUILD_CFLAGS)
+LDFLAGS += $(BUILD_LDFLAGS)
+
 task := all
 
 sys_include := $(CURDIR)/includes
 task := all
 
 sys_include := $(CURDIR)/includes
@@ -29,7 +30,7 @@ BUILD_NAME := $(libc_name).a
 LIBC_INCLUDE := $(build_dir)/usr/includes
 
 mkapp-list := $(addprefix app-, $(shell cat apps.list))
 LIBC_INCLUDE := $(build_dir)/usr/includes
 
 mkapp-list := $(addprefix app-, $(shell cat apps.list))
-mkexec-list := $(addprefix $(build_dir)/bin/, $(_LBUILD_SRCS))
+mkexec-list := $(addprefix $(build_dir)/bin/, $(BUILD_SRCS))
 
 uexec_ld := $(CURDIR)/uexec.ld
 
 
 uexec_ld := $(CURDIR)/uexec.ld
 
@@ -51,7 +52,7 @@ $(build_dir)/$(libc_name).a: $(build_dir)/bin \
        @$(MAKE) $(MKFLAGS) -C libc/ $(task)
 
 $(uexec_ld): $(uexec_ld)x
        @$(MAKE) $(MKFLAGS) -C libc/ $(task)
 
 $(uexec_ld): $(uexec_ld)x
-       @$(CC) -include $(lbuild_config_h) -x c -P -E $< -o $@
+       @$(CC) $(CFLAGS) -x c -P -E $< -o $@
 
 # Application (with standalone makefile)
 export LD_SCRIPT := $(uexec_ld)
 
 # Application (with standalone makefile)
 export LD_SCRIPT := $(uexec_ld)