From bfe2e75d7e3b372e55369677028429990c49bc54 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 20 Oct 2026 11:44:48 +0000 Subject: feat(driver): embed and automatically link the standard library Add -no-std to disable default library loading and linking. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 10 ++++++++ pseudocode-lib/std.cps | 2 ++ tests/i/no-std/case.json | 1 + tests/i/no-std/expected.txt | 2 ++ tests/i/no-std/notes.md | 3 +++ tests/i/no-std/program.cps | 4 ++++ tests/library/exp-string/case.json | 2 +- tests/run.pl | 6 ++--- tools/driver/StandardLibrary.cc | 43 ++++++++++++++++++++++++++++++++++ tools/driver/StandardLibrary.h | 12 ++++++++++ tools/driver/embed-standard-library.pl | 28 ++++++++++++++++++++++ tools/driver/main.cc | 10 ++++++++ 12 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 pseudocode-lib/std.cps create mode 100644 tests/i/no-std/case.json create mode 100644 tests/i/no-std/expected.txt create mode 100644 tests/i/no-std/notes.md create mode 100644 tests/i/no-std/program.cps create mode 100644 tools/driver/StandardLibrary.cc create mode 100644 tools/driver/StandardLibrary.h create mode 100644 tools/driver/embed-standard-library.pl diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d9f84f..365e6b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,16 @@ foreach(source IN LISTS cpsc_library_sources) endforeach() add_custom_target(pseudocode-lib ALL DEPENDS ${cpsc_library_outputs}) +set(cpsc_embedded_library "${CMAKE_BINARY_DIR}/standard-library/StandardLibrary.inc") +add_custom_command(OUTPUT "${cpsc_embedded_library}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_BINARY_DIR}/standard-library" + COMMAND "${PERL_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/tools/driver/embed-standard-library.pl" + "${cpsc_embedded_library}" ${cpsc_library_outputs} + DEPENDS ${cpsc_library_outputs} tools/driver/embed-standard-library.pl + VERBATIM) +target_sources(cpsc PRIVATE tools/driver/StandardLibrary.cc "${cpsc_embedded_library}") +target_include_directories(cpsc PRIVATE "${CMAKE_BINARY_DIR}/standard-library") + option(BUILD_TESTING "Enable compiler conformance tests" OFF) if(BUILD_TESTING) enable_testing() diff --git a/pseudocode-lib/std.cps b/pseudocode-lib/std.cps new file mode 100644 index 0000000..955e7da --- /dev/null +++ b/pseudocode-lib/std.cps @@ -0,0 +1,2 @@ +#LIB +INCLUDE "_exp_string.cps" diff --git a/tests/i/no-std/case.json b/tests/i/no-std/case.json new file mode 100644 index 0000000..85acec5 --- /dev/null +++ b/tests/i/no-std/case.json @@ -0,0 +1 @@ +{"compiler_args":["-no-std"]} diff --git a/tests/i/no-std/expected.txt b/tests/i/no-std/expected.txt new file mode 100644 index 0000000..1a2735d --- /dev/null +++ b/tests/i/no-std/expected.txt @@ -0,0 +1,2 @@ +C literal without the standard library +42 diff --git a/tests/i/no-std/notes.md b/tests/i/no-std/notes.md new file mode 100644 index 0000000..8fcea00 --- /dev/null +++ b/tests/i/no-std/notes.md @@ -0,0 +1,3 @@ +# No standard library + +Human review: pending. A suffixed literal is C_STRING without loading any pseudocode library. The C puts call writes the supplied text and a newline; integer addition produces 42. diff --git a/tests/i/no-std/program.cps b/tests/i/no-std/program.cps new file mode 100644 index 0000000..feb369f --- /dev/null +++ b/tests/i/no-std/program.cps @@ -0,0 +1,4 @@ +INCLUDE_OBJ "__libc" +EXTERN "C" EXTERN_FUNCTION puts(C_STRING) RETURNS C_INT +CALL puts("C literal without the standard library"C) +OUTPUT 20 + 22 diff --git a/tests/library/exp-string/case.json b/tests/library/exp-string/case.json index a558ad6..e1ed21a 100644 --- a/tests/library/exp-string/case.json +++ b/tests/library/exp-string/case.json @@ -1 +1 @@ -{"sources":["program.cps","../../../pseudocode-lib/_exp_string.cps"]} +{"sources":["program.cps","../../../pseudocode-lib/_exp_string.cps"],"compiler_args":["-no-std"]} diff --git a/tests/run.pl b/tests/run.pl index 8565805..86eff4f 100644 --- a/tests/run.pl +++ b/tests/run.pl @@ -443,7 +443,7 @@ sub main { write_file($target, $sentinel); } my $result = run_command($commands, - [$cpsc_abs, @sources, @flags, '-o', $target], $work, '', 120); + [$cpsc_abs, @sources, @{$config->{compiler_args} // []}, @flags, '-o', $target], $work, '', 120); my $preservation_error; if ($existing && (!-f $target || read_file($target) ne $sentinel)) { $preservation_error = 'compilation failure overwrote existing output'; @@ -541,12 +541,12 @@ sub main { my $target = File::Spec->catfile($work, $IS_WINDOWS ? 'program.exe' : 'program.bin'); my $compiled = run_command($commands, - [$cpsc_abs, @sources, '-o', $target], $work, '', 120); + [$cpsc_abs, @sources, @{$config->{compiler_args} // []}, '-o', $target], $work, '', 120); check_result($compiled, ''); $command = [$target]; } else { - $command = [$cpsc_abs, @sources, '-r']; + $command = [$cpsc_abs, @sources, @{$config->{compiler_args} // []}, '-r']; } my $exec_timeout = exists($config->{timeout}) ? $config->{timeout} + 0 : 30; my $result = run_command($commands, $command, $work, $data, $exec_timeout); diff --git a/tools/driver/StandardLibrary.cc b/tools/driver/StandardLibrary.cc new file mode 100644 index 0000000..ac88745 --- /dev/null +++ b/tools/driver/StandardLibrary.cc @@ -0,0 +1,43 @@ +#include "StandardLibrary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +namespace { +struct LibraryFile { + const char *Name; + const char *Data; + size_t Size; +}; +#include "StandardLibrary.inc" +} + +StandardLibrary::~StandardLibrary() { + if (!Directory.empty()) llvm::sys::fs::remove_directories(Directory); +} + +bool StandardLibrary::load(std::vector &Inputs) { + auto Error = llvm::sys::fs::createUniqueDirectory("cpsc-std", Directory); + if (Error) { + llvm::errs() << "cpsc: fatal error: cannot extract standard library: " << Error.message() << "\n"; + return false; + } + for (const auto &File : LibraryFiles) { + llvm::SmallString<256> Path(Directory); + llvm::sys::path::append(Path, File.Name); + llvm::raw_fd_ostream Output(Path, Error); + if (!Error) { + Output.write(File.Data, File.Size); + Output.close(); + Error = Output.error(); + } + if (Error) { + llvm::errs() << "cpsc: fatal error: cannot extract standard library file '" + << File.Name << "': " << Error.message() << "\n"; + Output.clear_error(); + return false; + } + if (llvm::sys::path::extension(Path) == ".cps") Inputs.emplace_back(Path); + } + return true; +} diff --git a/tools/driver/StandardLibrary.h b/tools/driver/StandardLibrary.h new file mode 100644 index 0000000..5aa4f8c --- /dev/null +++ b/tools/driver/StandardLibrary.h @@ -0,0 +1,12 @@ +#pragma once +#include "llvm/ADT/SmallString.h" +#include +#include + +// Extracted files live through native linking and runner execution. +class StandardLibrary { + llvm::SmallString<256> Directory; +public: + ~StandardLibrary(); + bool load(std::vector &Inputs); +}; diff --git a/tools/driver/embed-standard-library.pl b/tools/driver/embed-standard-library.pl new file mode 100644 index 0000000..68be7ef --- /dev/null +++ b/tools/driver/embed-standard-library.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use File::Basename qw(basename); + +my ($output, @inputs) = @ARGV; +open my $out, '>', $output or die "$output: $!\n"; +print {$out} "// Generated from pseudocode-lib; do not edit.\n"; +for my $index (0 .. $#inputs) { + open my $in, '<:raw', $inputs[$index] or die "$inputs[$index]: $!\n"; + local $/; + my $data = <$in>; + close $in or die "$inputs[$index]: $!\n"; + print {$out} "static const char LibraryData$index\[\] =\n"; + while (length $data) { + my $chunk = substr($data, 0, 32, ''); + print {$out} '"', join('', map { sprintf '\\%03o', $_ } unpack 'C*', $chunk), "\"\n"; + } + print {$out} ";\n"; +} +print {$out} "static const LibraryFile LibraryFiles[] = {\n"; +for my $index (0 .. $#inputs) { + my $name = basename($inputs[$index]); + die "Invalid library filename: $name\n" unless $name =~ /^[A-Za-z0-9_.-]+$/; + print {$out} " {\"$name\", LibraryData$index, sizeof(LibraryData$index) - 1},\n"; +} +print {$out} "};\n"; +close $out or die "$output: $!\n"; diff --git a/tools/driver/main.cc b/tools/driver/main.cc index 5f3aaa2..4d41e5e 100644 --- a/tools/driver/main.cc +++ b/tools/driver/main.cc @@ -3,6 +3,7 @@ #include "cps/Parser.h" #include "cps/CodeGen.h" #include "NativeCompiler.h" +#include "StandardLibrary.h" #ifdef CPSC_RUN_EMBEDDED #include "EmbeddedRunner.h" #endif @@ -91,12 +92,15 @@ int main(int argc, char **argv) { std::string OutputPath; bool Run = false; bool EmitIR = false; + bool NoStd = false; for (int I = 1; I < argc; ++I) { std::string Arg = argv[I]; if (Arg == "-r") { Run = true; } else if (Arg == "-S") { EmitIR = true; + } else if (Arg == "-no-std") { + NoStd = true; } else if (Arg == "-o") { if (!OutputPath.empty()) { llvm::errs() << "cpsc: error: -o may only be specified once\n"; @@ -137,6 +141,9 @@ int main(int argc, char **argv) { #endif } const bool Save = !Run || EmitIR || !OutputPath.empty(); + const size_t UserInputCount = InputPaths.size(); + StandardLibrary StdLibrary; + if (!NoStd && !StdLibrary.load(InputPaths)) return compilationTerminated(); std::vector>> Programs(InputPaths.size()); std::vector Units; for (size_t I = 0; I < InputPaths.size(); ++I) { @@ -163,6 +170,9 @@ int main(int argc, char **argv) { if (Lex.hadError() || Parser.hadError()) return compilationTerminated(); Units.push_back({InputPath, Programs[I], std::vector(InputPaths.size(), false), Lex.isLibrary()}); Units.back().Visible[I] = true; + if (!NoStd && I < UserInputCount) + for (size_t J = UserInputCount; J < InputPaths.size(); ++J) + Units.back().Visible[J] = true; } for (size_t I = 0; I < Units.size(); ++I) { cps::DiagnosticSource = Units.size() > 1 ? Units[I].Path : ""; -- cgit v1.3.1-4-g156e