Skip to content

Commit 81053e2

Browse files
committed
Playground improvements: syntax highlighting, examples, output capture
Major UX enhancements to the Null-Safe C playground: Features added: - Real-time C syntax highlighting with VS Code Dark+ theme - Dropdown with multiple example programs (null checks, arrays, functions) - Updated examples to show nullable-by-default semantics correctly - Output capture infrastructure with global flags - Auto-compile on page load - Fixed dropdown to show current selection - Improved error handling and logging Technical changes: - Dual-layer editor (highlighted div + transparent textarea) - Protected replacement algorithm for syntax highlighting - Global capture flags for stdout/stderr redirection - Module initialization with noExitRuntime flag - File I/O working (input.c written to Emscripten FS) Known issue: - Arguments not passing to clang _main (Module.arguments not used by Emscripten after init) - Need to investigate callMain export or argument passing mechanism - File writes successfully, clang runs, output captured - just need argc/argv working Next steps: - Fix argument passing to enable actual compilation - Build null-safe clang to WASM - Add split output pane for side-by-side comparison
1 parent a169bb5 commit 81053e2

File tree

1 file changed

+120
-58
lines changed

1 file changed

+120
-58
lines changed

nullsafe-playground/index.html

Lines changed: 120 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,7 @@ <h1>Null-Safe C Playground</h1>
437437
</button>
438438

439439
<select id="examplesSelect" class="examples-select">
440-
<option value="">Load Example...</option>
441-
<option value="null-check">Null Check Example</option>
440+
<option value="null-check" selected>Null Check Example</option>
442441
<option value="array">Array Safety</option>
443442
<option value="function">Function Parameters</option>
444443
</select>
@@ -573,58 +572,73 @@ <h1>Null-Safe C Playground</h1>
573572
'null-check': `#include <stdio.h>
574573
#include <stdlib.h>
575574
576-
// Null Check Example
575+
// Pointers are nullable by default
576+
// The compiler tracks null checks!
577+
577578
int main(void) {
578-
// Declaring a nullable pointer
579-
int * _Nullable ptr = NULL;
579+
int *ptr = NULL;
580580
581-
// ERROR: Dereferencing potentially null pointer
581+
// WARNING: might be null!
582582
*ptr = 42;
583583
584-
// CORRECT: Check before dereferencing
584+
// After null check, compiler knows it's safe
585585
if (ptr != NULL) {
586-
*ptr = 42; // OK
586+
*ptr = 42; // OK - compiler knows ptr is non-null here
587587
}
588588
589589
return 0;
590590
}`,
591591
'array': `#include <stdio.h>
592592
#include <stdlib.h>
593593
594-
// Array Safety Example
594+
// malloc can return NULL if allocation fails
595+
595596
int main(void) {
596-
// Nullable array pointer
597-
int * _Nullable arr = malloc(10 * sizeof(int));
597+
int *arr = malloc(10 * sizeof(int));
598598
599-
// ERROR: Using without null check
599+
// WARNING: arr might be NULL
600600
arr[0] = 42;
601601
602-
// CORRECT: Check first
602+
// SAFE: check before use
603603
if (arr != NULL) {
604-
arr[0] = 42; // OK
604+
arr[0] = 42;
605+
arr[1] = 100;
606+
printf("arr[0] = %d\\n", arr[0]);
605607
free(arr);
608+
} else {
609+
printf("Allocation failed!\\n");
606610
}
607611
608612
return 0;
609613
}`,
610614
'function': `#include <stdio.h>
611615
612-
// Function with nullable parameter
613-
void process(int * _Nullable ptr) {
614-
// ERROR: Unsafe dereference
615-
int value = *ptr;
616+
// Parameters are nullable by default
617+
void process(int *data) {
618+
// WARNING: data might be null
619+
*data = 42;
620+
}
616621
617-
// CORRECT: Check first
618-
if (ptr != NULL) {
619-
int value = *ptr; // OK
620-
printf("Value: %d\\n", value);
622+
// Safe version with null check
623+
void process_safe(int *data) {
624+
if (data != NULL) {
625+
*data = 42; // OK - compiler knows data is non-null
621626
}
622627
}
623628
629+
// Explicitly non-null parameter
630+
void guaranteed(int * _Nonnull data) {
631+
*data = 42; // Always safe - caller must pass non-null
632+
}
633+
624634
int main(void) {
625635
int x = 42;
626-
process(&x);
627-
process(NULL);
636+
int *ptr = &x;
637+
638+
process(ptr); // Works but ptr might be null
639+
process_safe(ptr); // Safe - checks for null
640+
guaranteed(ptr); // OK - ptr is non-null here
641+
628642
return 0;
629643
}`
630644
};
@@ -678,7 +692,7 @@ <h1>Null-Safe C Playground</h1>
678692
editor.value = examples[example];
679693
updateHighlight(); // Update highlighting
680694
showToast('Example loaded!');
681-
e.target.value = '';
695+
// Don't reset - keep the selection showing
682696
}
683697
});
684698

@@ -699,6 +713,10 @@ <h1>Null-Safe C Playground</h1>
699713
showToast('Output cleared');
700714
});
701715

716+
let capturedStdout = '';
717+
let capturedStderr = '';
718+
let captureOutput = false;
719+
702720
async function initCompiler() {
703721
status.textContent = 'Loading compiler (64MB)...';
704722
status.className = 'status';
@@ -708,24 +726,51 @@ <h1>Null-Safe C Playground</h1>
708726
// Configure Module before loading clang.js
709727
window.Module = {
710728
print: function(text) {
711-
console.log('[stdout]', text);
729+
if (captureOutput) {
730+
capturedStdout += text + '\n';
731+
console.log('[captured stdout]', text);
732+
} else {
733+
console.log('[stdout]', text);
734+
}
712735
},
713736
printErr: function(text) {
714-
console.log('[stderr]', text);
737+
if (captureOutput) {
738+
capturedStderr += text + '\n';
739+
console.log('[captured stderr]', text);
740+
} else {
741+
console.log('[stderr]', text);
742+
}
715743
},
716744
noInitialRun: true,
745+
noExitRuntime: true, // Keep runtime alive
746+
// Set up command line arguments
747+
arguments: [],
717748
onRuntimeInitialized: function() {
718749
ClangModule = window.Module;
719750
console.log('Runtime initialized!');
720751
console.log('Module has FS:', !!window.Module.FS);
721-
console.log('All Module keys:', Object.keys(window.Module));
752+
console.log('Module has callMain:', !!window.Module.callMain);
753+
754+
// Check all properties for main/call-related and exported functions
755+
const mainFuncs = Object.keys(window.Module).filter(k =>
756+
k.toLowerCase().includes('main') || k.toLowerCase().includes('call') || k.startsWith('_')
757+
);
758+
console.log('Main/call/exported functions:', mainFuncs);
759+
760+
// Check wasmExports
761+
if (window.Module.asm) {
762+
console.log('Module.asm keys:', Object.keys(window.Module.asm).slice(0, 50));
763+
}
722764

723765
status.textContent = 'Ready to compile';
724766
status.className = 'status ready';
725767
compileBtn.disabled = false;
726768
loadingBar.classList.remove('active');
727769
showToast('Compiler loaded successfully!');
728770
console.log('Clang WASM module loaded successfully');
771+
772+
// Auto-compile the initial example
773+
setTimeout(() => compile(), 500);
729774
}
730775
};
731776

@@ -813,57 +858,74 @@ <h1>Null-Safe C Playground</h1>
813858
async function compileCode(code) {
814859
return new Promise((resolve, reject) => {
815860
try {
816-
console.log('Starting compilation, Module available:', !!window.Module);
817-
console.log('Module.FS available:', !!window.Module?.FS);
818-
console.log('Global FS available:', !!window.FS);
819-
console.log('Module keys:', window.Module ? Object.keys(window.Module).slice(0, 30) : 'none');
861+
const inputFile = 'input.c'; // Try relative path
820862

821-
const inputFile = '/input.c';
822-
823-
// Try to find FS - it might be global or on Module
863+
// Get FS
824864
const FS = window.Module?.FS || window.FS;
825-
826865
if (!FS) {
827-
throw new Error('FS is not available - Module.FS: ' + !!window.Module?.FS + ', window.FS: ' + !!window.FS);
866+
throw new Error('FS is not available');
828867
}
829868

869+
// Write the file
830870
FS.writeFile(inputFile, code);
831871

832-
let stdout = '';
833-
let stderr = '';
872+
// Verify file exists
873+
try {
874+
const fileContent = FS.readFile(inputFile, { encoding: 'utf8' });
875+
console.log('File written successfully, size:', fileContent.length);
876+
console.log('File content preview:', fileContent.substring(0, 100));
877+
} catch (e) {
878+
console.error('Cannot read back file:', e);
879+
}
834880

835-
const oldPrint = Module.print;
836-
const oldPrintErr = Module.printErr;
881+
// List files in root
882+
try {
883+
const files = FS.readdir('/');
884+
console.log('Files in root:', files);
885+
} catch (e) {
886+
console.error('Cannot list files:', e);
887+
}
837888

838-
Module.print = (text) => {
839-
stdout += text + '\n';
840-
};
841-
Module.printErr = (text) => {
842-
stderr += text + '\n';
843-
};
889+
// Reset and enable capture
890+
capturedStdout = '';
891+
capturedStderr = '';
892+
captureOutput = true;
844893

845894
try {
846-
Module.callMain([
847-
'-fsyntax-only',
848-
'-Xclang', '-verify',
849-
inputFile
850-
]);
895+
// Emscripten doesn't pass Module.arguments to an already-running main
896+
// We need to set up ENV and call differently
897+
const oldArgs = Module.arguments;
898+
899+
// Try setting arguments before calling _main
900+
Module['arguments'] = ['-fsyntax-only', 'input.c'];
901+
902+
console.log('Calling clang with Module.arguments:', Module.arguments);
903+
904+
// Try calling with argc/argv = 0, Emscripten should use Module.arguments
905+
// But it seems like it doesn't work for subsequent calls
906+
// Let's try passing actual argc
907+
const argc = 2; // Program name + our args
908+
const exitCode = Module._main(argc, 0);
909+
console.log('Clang exit code:', exitCode);
910+
911+
Module.arguments = oldArgs;
851912
} catch (e) {
852-
// Clang may throw on errors, that's okay
853-
console.log('Clang execution completed (may have thrown):', e);
913+
console.log('Clang execution error:', e);
854914
}
855915

856-
Module.print = oldPrint;
857-
Module.printErr = oldPrintErr;
916+
// Disable capture
917+
captureOutput = false;
858918

859919
try {
860920
FS.unlink(inputFile);
861921
} catch (e) {
862-
// File might not exist
922+
// Ignore
863923
}
864924

865-
resolve({ stdout, stderr });
925+
console.log('Compilation result:', { stdout: capturedStdout, stderr: capturedStderr });
926+
resolve({ stdout: capturedStdout, stderr: capturedStderr });
866927
} catch (error) {
928+
captureOutput = false;
867929
console.error('compileCode error:', error);
868930
reject(error);
869931
}

0 commit comments

Comments
 (0)