aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-20 11:44:48 +0000
committer_ov4 <ov4@ov4.org>2026-10-20 11:44:48 +0000
commitbfe2e75d7e3b372e55369677028429990c49bc54 (patch)
treeb64b0e999b428f7740c0cd2c4ec05d51a213ddd9
parenta3335110dc4f1283cf1680e273cc9775b7e8c8ac (diff)
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 <noreply@anthropic.com>
-rw-r--r--CMakeLists.txt10
-rw-r--r--pseudocode-lib/std.cps2
-rw-r--r--tests/i/no-std/case.json1
-rw-r--r--tests/i/no-std/expected.txt2
-rw-r--r--tests/i/no-std/notes.md3
-rw-r--r--tests/i/no-std/program.cps4
-rw-r--r--tests/library/exp-string/case.json2
-rw-r--r--tests/run.pl6
-rw-r--r--tools/driver/StandardLibrary.cc43
-rw-r--r--tools/driver/StandardLibrary.h12
-rw-r--r--tools/driver/embed-standard-library.pl28
-rw-r--r--tools/driver/main.cc10
12 files changed, 119 insertions, 4 deletions
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<std::string> &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 <string>
+#include <vector>
+
+// Extracted files live through native linking and runner execution.
+class StandardLibrary {
+ llvm::SmallString<256> Directory;
+public:
+ ~StandardLibrary();
+ bool load(std::vector<std::string> &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<std::vector<std::unique_ptr<cps::StmtAST>>> Programs(InputPaths.size());
std::vector<cps::SourceUnit> 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<bool>(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 : "";