From: Thomas Walker Lynch Date: Tue, 10 Mar 2026 12:55:03 +0000 (+0000) Subject: promote diff now does diff instead of timestamps X-Git-Url: https://git.reasoningtechnology.com/%5B%5E?a=commitdiff_plain;h=0b1316f414b43a7481722ee0ee809d5fcea72998;p=Harmony promote diff now does diff instead of timestamps --- diff --git a/developer/document/C_single_file_modules_and_namespaces.html b/developer/document/C_single_file_modules_and_namespaces.html deleted file mode 100644 index 18284b2..0000000 --- a/developer/document/C_single_file_modules_and_namespaces.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - C single file modules and namespaces - - - - - - - - - - -

Single file C source

-

- The RT C-culture abandons the traditional separation of declarations into .h header files and definitions into .c source files. Instead, a developer integrates the interface and the implementation into a single file. -

-

- When a person includes the file using an #include directive, the file exposes only its interface. When the build system compiles the file into an object, a preprocessor macro acts as an implementation gate to compile the definitions. This keeps the source tree clean and ensures the interface and implementation never fall out of synchronization. -

- -

Ad hoc namespaces

-

- The C language lacks native namespaces. To prevent symbol collisions in large projects, RT code format uses a center dot (·) to denote ad hoc namespaces within identifiers. -

-

- A programmer maps the directory structure directly to these namespaces. For example, a file located at authored/ExampleGreet/Math.lib.c belongs to the ExampleGreet namespace and defines the Math module. -

-

- Types and modules use PascalCase, while functions and variables use snake_case. An exported function from this module carries the full namespace and module prefix, such as ExampleGreet·Math·add. -

- -

The implementation gate

-

- To achieve the single-file source pattern, the code relies on two preprocessor constructs. The entire file is wrapped in an include guard using the ·ONCE suffix. This prevents redeclaration errors if the file is included multiple times. -

-

- The definitions are wrapped in a single #ifdef block using the exact namespace and module name. The build system dynamically injects this macro via a -D compiler flag when building the module's specific object file. -

- -

Example: Math.lib.c

-

- The following example demonstrates the complete structure. -

- - - #ifndef ExampleGreet·Math·ONCE - #define ExampleGreet·Math·ONCE - - int ExampleGreet·Math·add(int a ,int b); - - #ifdef ExampleGreet·Math - - int ExampleGreet·Math·add(int a ,int b){ - return a + b; - } - - #endif // ExampleGreet·Math - #endif // ExampleGreet·Math·ONCE - - -

Build system mechanics

-

- When a consumer file, such as hello.CLI.c, contains #include "Math.lib.c", the compiler processes the file without the ExampleGreet·Math macro defined. It skips the implementation and reads only the function prototype. -

-

- When the orchestrator compiles the library object, it evaluates the target name and explicitly passes -DExampleGreet·Math to the compiler. This unlocks the gate, compiling the machine code for the definitions into Math.lib.o. -

- -
- - diff --git a/developer/document/File_directory_naming.html b/developer/document/File_directory_naming.html index 7569629..2d7a350 100644 --- a/developer/document/File_directory_naming.html +++ b/developer/document/File_directory_naming.html @@ -17,6 +17,8 @@ title="File and directory naming conventions"> + +

Program file system objects

diff --git a/developer/document/Single-file_C_modules_and_namespaces.html b/developer/document/Single-file_C_modules_and_namespaces.html new file mode 100644 index 0000000..c05225e --- /dev/null +++ b/developer/document/Single-file_C_modules_and_namespaces.html @@ -0,0 +1,125 @@ + + + + + Single-file C modules and namespaces + + + + + + + + + + +

Single-file C source

+

+ The RT C language culture abandons the traditional separation of declarations into .h header files and definitions into .c source files. A developer integrates the interface and the implementation into a single file. +

+

+ When a person includes the file using an #include directive, the file exposes only its interface. When the build system compiles the file into an object, a preprocessor macro acts as an implementation gate to compile the definitions. This keeps the source tree clean and ensures the interface and implementation never fall out of synchronization. +

+ +

Ad hoc namespaces

+

+ The C language lacks native namespaces. To prevent symbol collisions in large projects, RT code format uses a center dot (·) to denote ad hoc namespaces within identifiers. +

+

+ A programmer maps the directory structure directly to these namespaces. For example, a file located at authored/ExampleGreet/Math.lib.c belongs to the ExampleGreet namespace and defines the Math module. +

+ +

+ An exported function from this module carries the full namespace and module prefix, such as ExampleGreet·Math·add. +

+ +

The implementation gate

+

+ To achieve the single-file source pattern, the code relies on two preprocessor constructs: +

+ + +

Build system mechanics

+

+ When a consumer file, such as hello.CLI.c, contains #include "Math.lib.c", the compiler processes the file without the ExampleGreet·Math macro defined. It skips the implementation and reads only the function prototype. +

+

+ When the orchestrator compiles the library object, it evaluates the target name and explicitly passes -DExampleGreet·Math to the compiler. This unlocks the gate, compiling the machine code for the definitions into Math.lib.o. +

+ +

Example: Math.lib.c

+

+ The following example demonstrates the complete structure. +

+ + + #ifndef ExampleGreet·Math·ONCE + #define ExampleGreet·Math·ONCE + + int ExampleGreet·Math·add(int a ,int b); + + #ifdef ExampleGreet·Math + + int ExampleGreet·Math·add(int a ,int b){ + return a + b; + } + + #endif // ExampleGreet·Math + #endif // ExampleGreet·Math·ONCE + + +

Cross-module dependencies

+

+ When one module depends on another, the developer directly includes the library source file. For example, if the Greeter module requires the Math module, the file Greeter.lib.c will contain: +

+ + #include "Math.lib.c" + +

+ Because every file is protected by a ·ONCE include guard, it is safe for multiple modules to include the same dependency. The preprocessor will only expand the interface once per translation unit. +

+ +

Information hiding

+

+ To define internal helper functions or private data that should not be exposed in the module's public interface, a programmer places them strictly inside the #ifdef implementation block. +

+

+ To prevent these internal symbols from leaking into the global namespace during linking, they must be given internal linkage. The RT skeleton provides the Local macro (defined as static in RT_global.h) for this exact purpose. +

+ + #ifdef ExampleGreet·Math + + Local int internal_helper(int val){ + return val * 2; + } + + int ExampleGreet·Math·add(int a ,int b){ + return internal_helper(a) + b; + } + + #endif // ExampleGreet·Math + + +

Executable entry points

+

+ Programs intended to be compiled into standalone executables use the .CLI.c suffix. These files consume the library modules but do not define their own namespaces or include guards, as they are never included by other files. +

+

+ A .CLI.c file includes the necessary .lib.c files, parses command-line arguments in the main function, and passes native data types to a dedicated CLI() function to orchestrate the core logic. +

+ + + + diff --git a/developer/tool/promote b/developer/tool/promote index 349bfd7..a4d8888 100755 --- a/developer/tool/promote +++ b/developer/tool/promote @@ -1,13 +1,13 @@ #!/usr/bin/env -S python3 -B # -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- -import os ,sys ,shutil ,stat ,pwd ,grp ,glob ,tempfile +import os ,sys ,shutil ,stat ,pwd ,grp ,glob ,tempfile ,filecmp HELP = """usage: promote {write|clean|ls|diff|help|dry write} write Writes promoted files from scratchpad/made into consumer/made. Only updates newer files. clean Remove all contents of the consumer/made directory. ls List consumer/made as an indented tree: PERMS OWNER NAME. - diff List files in consumer/made that are not in scratchpad/made, or are newer. + diff List missing, orphaned, or out-of-sync files between scratchpad and consumer. help Show this message. dry write Preview what write would do without modifying the filesystem. """ @@ -22,7 +22,7 @@ def exit_with_status(msg ,code=1): def assert_setup(): setup_val = os.environ.get("SETUP" ,"") - if(setup_val != SETUP_MUST_BE): + if( setup_val != SETUP_MUST_BE ): hint = ( "SETUP is not 'developer/tool/setup'.\n" "Enter the project with: . setup developer\n" @@ -81,7 +81,7 @@ def ensure_mode(path_str ,mode): except Exception: pass def ensure_dir(path_str ,mode=DEFAULT_DIR_MODE ,dry=False): - if(dry): + if( dry ): if( not os.path.isdir(path_str) ): shown = _display_dst(path_str) if path_str.startswith(consumer_root()) else ( os.path.relpath(path_str ,dev_root()) if path_str.startswith(dev_root()) else path_str @@ -109,42 +109,42 @@ def list_tree(root_dp): it = list(os.scandir(path_str)) except FileNotFoundError: return - dirs = [e for e in it if e.is_dir(follow_symlinks=False)] - files = [e for e in it if not e.is_dir(follow_symlinks=False)] - dirs.sort(key=lambda e: e.name) - files.sort(key=lambda e: e.name) - - if(is_root): - for f in ( e for e in files if e.name.startswith(".") ): - st = os.lstat(f.path) - entries.append((False ,depth ,filemode(st.st_mode) ,owner_group(st) ,f.name)) - for d in dirs: - st = os.lstat(d.path) - entries.append((True ,depth ,filemode(st.st_mode) ,owner_group(st) ,d.name + "/")) - gather(d.path ,depth + 1 ,False) - for f in ( e for e in files if not e.name.startswith(".") ): - st = os.lstat(f.path) - entries.append((False ,depth ,filemode(st.st_mode) ,owner_group(st) ,f.name)) + dirs = [TM_e for TM_e in it if TM_e.is_dir(follow_symlinks=False)] + files = [TM_e for TM_e in it if not TM_e.is_dir(follow_symlinks=False)] + dirs.sort(key=lambda TM_e: TM_e.name) + files.sort(key=lambda TM_e: TM_e.name) + + if( is_root ): + for TM_f in ( TM_e for TM_e in files if TM_e.name.startswith(".") ): + st = os.lstat(TM_f.path) + entries.append((False ,depth ,filemode(st.st_mode) ,owner_group(st) ,TM_f.name)) + for TM_d in dirs: + st = os.lstat(TM_d.path) + entries.append((True ,depth ,filemode(st.st_mode) ,owner_group(st) ,TM_d.name + "/")) + gather(TM_d.path ,depth + 1 ,False) + for TM_f in ( TM_e for TM_e in files if not TM_e.name.startswith(".") ): + st = os.lstat(TM_f.path) + entries.append((False ,depth ,filemode(st.st_mode) ,owner_group(st) ,TM_f.name)) else: - for d in dirs: - st = os.lstat(d.path) - entries.append((True ,depth ,filemode(st.st_mode) ,owner_group(st) ,d.name + "/")) - gather(d.path ,depth + 1 ,False) - for f in files: - st = os.lstat(f.path) - entries.append((False ,depth ,filemode(st.st_mode) ,owner_group(st) ,f.name)) + for TM_d in dirs: + st = os.lstat(TM_d.path) + entries.append((True ,depth ,filemode(st.st_mode) ,owner_group(st) ,TM_d.name + "/")) + gather(TM_d.path ,depth + 1 ,False) + for TM_f in files: + st = os.lstat(TM_f.path) + entries.append((False ,depth ,filemode(st.st_mode) ,owner_group(st) ,TM_f.name)) gather(root_dp ,1 ,True) ogw = 0 - for _isdir ,_depth ,_perms ,ownergrp ,_name in entries: - if( len(ownergrp) > ogw ): - ogw = len(ownergrp) + for TM_isdir ,TM_depth ,TM_perms ,TM_ownergrp ,TM_name in entries: + if( len(TM_ownergrp) > ogw ): + ogw = len(TM_ownergrp) print("consumer/made/") - for isdir ,depth ,perms ,ownergrp ,name in entries: - indent = " " * depth - print(f"{perms} {ownergrp:<{ogw}} {indent}{name}") + for TM_isdir ,TM_depth ,TM_perms ,TM_ownergrp ,TM_name in entries: + indent = " " * TM_depth + print(f"{TM_perms} {TM_ownergrp:<{ogw}} {indent}{TM_name}") def copy_one(src_abs ,dst_abs ,mode ,dry=False): src_show = _display_src(src_abs) @@ -152,7 +152,7 @@ def copy_one(src_abs ,dst_abs ,mode ,dry=False): parent = os.path.dirname(dst_abs) os.makedirs(parent ,exist_ok=True) - if(dry): + if( dry ): if( os.path.exists(dst_abs) ): print(f"(dry) unlink '{dst_show}'") print(f"(dry) install -m {oct(mode)[2:]} -D '{src_show}' '{dst_show}'") @@ -186,18 +186,17 @@ def cmd_write(dry=False): exit_with_status(f"cannot find developer scratchpad made at '{_display_src(src_root)}'") wrote = False - for root ,dirs ,files in os.walk(src_root): - dirs.sort() - files.sort() - for fn in files: - src_abs = os.path.join(root ,fn) + for TM_root ,TM_dirs ,TM_files in os.walk(src_root): + TM_dirs.sort() + TM_files.sort() + for TM_fn in TM_files: + src_abs = os.path.join(TM_root ,TM_fn) rel = os.path.relpath(src_abs ,src_root) dst_abs = os.path.join(cpath() ,rel) if( os.path.exists(dst_abs) ): - src_mtime = os.stat(src_abs).st_mtime - dst_mtime = os.stat(dst_abs).st_mtime - if(dst_mtime >= src_mtime): + # Use content comparison instead of timestamps + if( filecmp.cmp(src_abs ,dst_abs ,shallow=False) ): continue st = os.stat(src_abs) @@ -223,39 +222,59 @@ def cmd_diff(): ,"made" ) - if( not os.path.isdir(dst_root) ): - print(f"Consumer made directory {_display_dst(dst_root)} does not exist.") + src_files = set() + if( os.path.isdir(src_root) ): + for TM_root ,TM_dirs ,TM_files in os.walk(src_root): + for TM_fn in TM_files: + src_files.add(os.path.relpath(os.path.join(TM_root ,TM_fn) ,src_root)) + + dst_files = set() + if( os.path.isdir(dst_root) ): + for TM_root ,TM_dirs ,TM_files in os.walk(dst_root): + for TM_fn in TM_files: + dst_files.add(os.path.relpath(os.path.join(TM_root ,TM_fn) ,dst_root)) + + if( not src_files and not dst_files ): + print("No differences found. Both directories are empty or missing.") return + all_files = sorted(list(src_files | dst_files)) found_diff = False - for root ,dirs ,files in os.walk(dst_root): - dirs.sort() - files.sort() - for fn in files: - dst_abs = os.path.join(root ,fn) - rel = os.path.relpath(dst_abs ,dst_root) - src_abs = os.path.join(src_root ,rel) - - if( not os.path.exists(src_abs) ): - print(f"Orphaned in consumer made: {rel}") - found_diff = True - else: - dst_mtime = os.stat(dst_abs).st_mtime + + for TM_rel in all_files: + if( TM_rel not in dst_files ): + print(f"Pending promotion (missing in consumer made): {TM_rel}") + found_diff = True + elif( TM_rel not in src_files ): + print(f"Orphaned in consumer made (missing in scratchpad): {TM_rel}") + found_diff = True + else: + src_abs = os.path.join(src_root ,TM_rel) + dst_abs = os.path.join(dst_root ,TM_rel) + + # Compare contents first + if( not filecmp.cmp(src_abs ,dst_abs ,shallow=False) ): src_mtime = os.stat(src_abs).st_mtime - if(dst_mtime > src_mtime): - print(f"Newer in consumer made: {rel}") - found_diff = True + dst_mtime = os.stat(dst_abs).st_mtime + + # If they differ, check timestamps to infer direction + if( src_mtime > dst_mtime ): + print(f"Pending update (contents differ, newer in scratchpad): {TM_rel}") + else: + print(f"Contents differ (locally modified in consumer made): {TM_rel}") + found_diff = True if( not found_diff ): print("No differences found. Consumer made matches developer scratchpad made.") + def cmd_clean(): assert_setup() consumer_root_dir = cpath() if( not os.path.isdir(consumer_root_dir) ): return - for name in os.listdir(consumer_root_dir): - p = os.path.join(consumer_root_dir ,name) + for TM_name in os.listdir(consumer_root_dir): + p = os.path.join(consumer_root_dir ,TM_name) if( os.path.isdir(p) and not os.path.islink(p) ): shutil.rmtree(p ,ignore_errors=True) else: @@ -266,18 +285,21 @@ def CLI(): if( len(sys.argv) < 2 ): print(HELP) return - cmd ,*args = sys.argv[1:] - if(cmd == "write"): + + cmd = sys.argv[1] + args = sys.argv[2:] + + if( cmd == "write" ): cmd_write(dry=False) - elif(cmd == "clean"): + elif( cmd == "clean" ): cmd_clean() - elif(cmd == "ls"): + elif( cmd == "ls" ): list_tree(cpath()) - elif(cmd == "diff"): + elif( cmd == "diff" ): cmd_diff() - elif(cmd == "help"): + elif( cmd == "help" ): print(HELP) - elif(cmd == "dry"): + elif( cmd == "dry" ): if( args and args[0] == "write" ): cmd_write(dry=True) else: