Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified bin/tripc
100644 → 100755
Empty file.
229 changes: 181 additions & 48 deletions bin/tripc.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
#!/usr/bin/env -S deno run --allow-read --allow-write

/**
* TripLang Compiler CLI (tripc)
* TripLang Compiler & Linker CLI (tripc)
*
* A redistributable command-line tool for compiling TripLang modules to object files.
* A unified command-line tool for compiling TripLang modules and linking object files.
*
* Usage:
* Compilation Usage:
* tripc <input.trip> [output.tripc]
* tripc --compile <input.trip> [output.tripc]
*
* Linking Usage:
* tripc --link <input1.tripc> [input2.tripc] ...
* tripc -l <input1.tripc> [input2.tripc] ...
*
* General Usage:
* tripc --help
* tripc --version
*
* Examples:
* tripc mymodule.trip
* tripc mymodule.trip mymodule.tripc
* tripc mymodule.trip # Compile to mymodule.tripc
* tripc --link module1.tripc module2.tripc # Link modules
* tripc --help
*/

Expand All @@ -21,25 +28,41 @@ import {
compileToObjectFileString,
SingleFileCompilerError,
} from "../lib/compiler/index.ts";
import {
deserializeTripCObject,
type TripCObject,
} from "../lib/compiler/objectFile.ts";
import { linkModules } from "../lib/linker/moduleLinker.ts";
import { VERSION } from "../lib/shared/version.ts";
import { getPreludeObject } from "../lib/prelude.ts";

type Mode = "compile" | "link";

interface CLIOptions {
help: boolean;
version: boolean;
verbose: boolean;
mode: Mode;
}

function parseArgs(
args: string[],
): { options: CLIOptions; inputPath?: string; outputPath?: string } {
): {
options: CLIOptions;
inputPath?: string;
outputPath?: string;
inputFiles?: string[];
} {
const options: CLIOptions = {
help: false,
version: false,
verbose: false,
mode: "compile", // Default to compile mode
};

let inputPath: string | undefined;
let outputPath: string | undefined;
const inputFiles: string[] = [];

for (let i = 0; i < args.length; i++) {
const arg = args[i];
Expand All @@ -54,59 +77,81 @@ function parseArgs(
options.version = true;
break;
case "--verbose":
case "-V":
options.verbose = true;
break;
case "--compile":
case "-c":
options.mode = "compile";
break;
case "--link":
case "-l":
options.mode = "link";
break;
default:
if (arg.startsWith("-")) {
console.error(`Unknown option: ${arg}`);
console.error("Use --help for usage information.");
Deno.exit(1);
}

if (!inputPath) {
inputPath = arg;
} else if (!outputPath) {
outputPath = arg;
if (options.mode === "link") {
inputFiles.push(arg);
} else {
console.error(
"Too many arguments. Use --help for usage information.",
);
Deno.exit(1);
if (!inputPath) {
inputPath = arg;
} else if (!outputPath) {
outputPath = arg;
} else {
console.error(
"Too many arguments. Use --help for usage information.",
);
Deno.exit(1);
}
}
break;
}
}

return { options, inputPath, outputPath };
return { options, inputPath, outputPath, inputFiles };
}

function showHelp(): void {
console.log(`
TripLang Compiler (tripc) v${VERSION}
TripLang Compiler & Linker (tripc) v${VERSION}

USAGE:
tripc <input.trip> [output.tripc]
tripc <input.trip> [output.tripc] # Compile mode (default)
tripc --compile <input.trip> [output.tripc] # Explicit compile mode
tripc --link <input1.tripc> [input2.tripc]... # Link mode
tripc -l <input1.tripc> [input2.tripc]... # Short link mode
tripc [OPTIONS]

ARGUMENTS:
COMPILATION MODE:
<input.trip> Input TripLang source file
[output.tripc] Output object file (optional, defaults to input.tripc)

LINKING MODE:
<input1.tripc> First object file to link
[input2.tripc] Additional object files to link

OPTIONS:
-h, --help Show this help message
-v, --version Show version information
--verbose Enable verbose output
-V, --verbose Enable verbose output
-c, --compile Compile mode (default)
-l, --link Link mode

EXAMPLES:
tripc mymodule.trip
tripc mymodule.trip mymodule.tripc
tripc mymodule.trip # Compile to mymodule.tripc
tripc --link module1.tripc module2.tripc # Link modules
tripc --help
tripc --version

DESCRIPTION:
Compiles a single TripLang module (.trip) into a standardized object file (.tripc).
This is Phase 1 of the TripLang module system - single-file compilation.
Unified compiler and linker for TripLang modules.

COMPILATION: Compiles a single TripLang module (.trip) into a standardized object file (.tripc).
The object file contains:
- Module name and metadata
- Import/export declarations
Expand All @@ -118,6 +163,76 @@ function showVersion(): void {
console.log(`tripc v${VERSION}`);
}

async function validateInputFiles(inputFiles: string[]): Promise<string[]> {
const validatedFiles: string[] = [];

for (const file of inputFiles) {
// Check if file exists
try {
await Deno.stat(file);
} catch {
throw new Error(`Input file does not exist: ${file}`);
}

// Check if file has .tripc extension
if (!file.endsWith(".tripc")) {
throw new Error(`Input file must have .tripc extension: ${file}`);
}

validatedFiles.push(file);
}

return validatedFiles;
}

async function linkFiles(inputFiles: string[], verbose = false): Promise<void> {
if (verbose) {
console.log(
`Linking ${inputFiles.length} files: ${inputFiles.join(", ")}`,
);
}

// Load all .tripc files
const modules: Array<{ name: string; object: TripCObject }> = [];

// Always include prelude first (mandatory) - embedded constant
try {
if (verbose) {
console.log("Loading embedded prelude...");
}
const preludeObject = await getPreludeObject();
modules.push({ name: "Prelude", object: preludeObject });
} catch (error) {
if (error instanceof SingleFileCompilerError) {
console.error(`Error compiling embedded prelude: ${error.message}`);
Deno.exit(1);
}
throw error;
}

for (const file of inputFiles) {
if (verbose) {
console.log(`Loading ${file}...`);
}

const content = await Deno.readTextFile(file);
const object = deserializeTripCObject(content);

// Extract module name from filename
const moduleName = file.split("/").pop()?.replace(".tripc", "") ||
"Unknown";

modules.push({ name: moduleName, object });
}

if (verbose) {
console.log("Linking modules...");
}

const result = linkModules(modules, verbose);
console.log(result);
}

async function compileFile(
inputPath: string,
outputPath?: string,
Expand Down Expand Up @@ -176,7 +291,7 @@ async function compileFile(

async function main(): Promise<void> {
const args = Deno.args;
const { options, inputPath, outputPath } = parseArgs(args);
const { options, inputPath, outputPath, inputFiles } = parseArgs(args);

if (options.help) {
showHelp();
Expand All @@ -188,36 +303,54 @@ async function main(): Promise<void> {
return;
}

if (!inputPath) {
console.error("Error: No input file specified.");
console.error("Use --help for usage information.");
Deno.exit(1);
}
try {
if (options.mode === "link") {
if (!inputFiles || inputFiles.length === 0) {
console.error("Error: No input files specified for linking.");
console.error("Use --help for usage information.");
Deno.exit(1);
}

const validatedFiles = await validateInputFiles(inputFiles);
await linkFiles(validatedFiles, options.verbose);
} else {
// Compile mode
if (!inputPath) {
console.error("Error: No input file specified.");
console.error("Use --help for usage information.");
Deno.exit(1);
}

const resolvedInputPath = resolve(inputPath);

try {
const stat = await Deno.stat(resolvedInputPath);
if (!stat.isFile) {
throw new Error("Input path is not a file");
}
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.error(`Input file not found: ${inputPath}`);
} else {
console.error(`Cannot read input file '${inputPath}': ${error}`);
}
Deno.exit(1);
}

const resolvedInputPath = resolve(inputPath);
if (!inputPath.endsWith(".trip")) {
console.error(`Input file must have .trip extension: ${inputPath}`);
Deno.exit(1);
}

try {
const stat = await Deno.stat(resolvedInputPath);
if (!stat.isFile) {
throw new Error("Input path is not a file");
const resolvedOutputPath = outputPath ? resolve(outputPath) : undefined;
await compileFile(resolvedInputPath, resolvedOutputPath, options.verbose);
}
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.error(`Input file not found: ${inputPath}`);
} else {
console.error(`Cannot read input file '${inputPath}': ${error}`);
}
Deno.exit(1);
}

if (!inputPath.endsWith(".trip")) {
console.error(`Input file must have .trip extension: ${inputPath}`);
console.error(
`Error: ${error instanceof Error ? error.message : String(error)}`,
);
Deno.exit(1);
}

const resolvedOutputPath = outputPath ? resolve(outputPath) : undefined;

await compileFile(resolvedInputPath, resolvedOutputPath, options.verbose);
}

if (import.meta.main) {
Expand Down
5 changes: 3 additions & 2 deletions deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@maxdeliso/typed-ski",
"version": "0.3.1",
"version": "0.4.0",
"license": "MIT",
"exports": {
".": "./lib/index.ts",
Expand Down Expand Up @@ -51,6 +51,7 @@
]
},
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.15",
"@types/node": "npm:@types/node@^24.7.2",
"@types/chai": "npm:@types/chai@^5.2.2",
"std/path": "jsr:@std/path@1",
Expand All @@ -76,7 +77,7 @@
"bundle:tripc:node": "deno task version:gen && deno bundle --platform=browser -o dist/tripc.node.js bin/tripc.ts",
"compile:tripc": "deno task version:gen && deno compile --allow-read --allow-write --output dist/tripc bin/tripc.ts",
"compile:tripc:cross": "deno task version:gen && deno compile --allow-read --allow-write --target x86_64-pc-windows-msvc --output dist/tripc.exe bin/tripc.ts && deno compile --allow-read --allow-write --target x86_64-unknown-linux-gnu --output dist/tripc-linux bin/tripc.ts && deno compile --allow-read --allow-write --target x86_64-apple-darwin --output dist/tripc-macos bin/tripc.ts",
"dist": "deno task version:gen && mkdir -p dist && deno task bundle:tripc && deno task bundle:tripc:minified && deno task compile:tripc",
"dist": "deno task version:gen && mkdir -p dist && deno task bundle:tripc && deno task bundle:tripc:minified && deno task bundle:tripc:node && deno task compile:tripc",
"dist:all": "deno task dist && deno task dist:cross",
"dist:cross": "deno task compile:tripc:cross"
}
Expand Down
4 changes: 4 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading