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
17 changes: 17 additions & 0 deletions apps/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
FROM docker.io/cloudflare/sandbox:0.6.0-python

# Install C++ compiler and tools for C++ language support
RUN apt-get update && apt-get install -y \
g++ \
build-essential \
git \
&& rm -rf /var/lib/apt/lists/*

# Install nlohmann/json library (header-only JSON library for C++)
RUN mkdir -p /usr/local/include && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nlohmann/json library is installed from the latest commit on the main branch without a version pin. This could lead to unexpected behavior if the library's API changes. Consider pinning to a specific tag/release for reproducibility.

git clone --depth 1 --branch v3.11.3 https://github.com/nlohmann/json.git
Suggested change
git clone --depth 1 https://github.com/nlohmann/json.git && \
git clone --depth 1 --branch v3.11.3 https://github.com/nlohmann/json.git && \

Copilot uses AI. Check for mistakes.
cp json/single_include/nlohmann/json.hpp /usr/local/include/ && \
Comment on lines +11 to +14
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The installation of nlohmann/json only copies the single header file to /usr/local/include/ directly, but the compile command uses -I/usr/local/include and includes <nlohmann/json.hpp> (note the nlohmann/ subdirectory). This path mismatch will cause compilation failures.

Fix this by creating the proper directory structure:

RUN mkdir -p /usr/local/include/nlohmann && \
    cd /tmp && \
    git clone --depth 1 https://github.com/nlohmann/json.git && \
    cp json/single_include/nlohmann/json.hpp /usr/local/include/nlohmann/ && \
    rm -rf /tmp/json
Suggested change
RUN mkdir -p /usr/local/include && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
cp json/single_include/nlohmann/json.hpp /usr/local/include/ && \
RUN mkdir -p /usr/local/include/nlohmann && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
cp json/single_include/nlohmann/json.hpp /usr/local/include/nlohmann/ && \

Copilot uses AI. Check for mistakes.
rm -rf /tmp/json

# Verify C++ compiler installation
RUN g++ --version

# Required during local development to access exposed ports
EXPOSE 8080
186 changes: 186 additions & 0 deletions apps/backend/src/problem-actions/src/code-generator/cpp-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import type { FunctionSignatureSchema, TypeDef } from "@repo/api-types";
import type { CodeGenerator } from "./types";

export class CppGenerator implements CodeGenerator {
private includes = new Set<string>();

typeToString(typeDef: TypeDef): string {
switch (typeDef.kind) {
case "primitive": {
const map: Record<string, string> = {
int: "long long",
float: "double",
string: "std::string",
boolean: "bool",
null: "std::nullptr_t",
};
return map[typeDef.type];
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typeToString method doesn't handle the case where typeDef.type doesn't match any key in the map. This would return undefined, which could cause issues downstream. Consider adding a default case or throwing an error for unsupported primitive types.

const mapped = map[typeDef.type];
if (!mapped) {
  throw new Error(`Unsupported primitive type: ${typeDef.type}`);
}
return mapped;
Suggested change
return map[typeDef.type];
const mapped = map[typeDef.type];
if (!mapped) {
throw new Error(`Unsupported primitive type: ${typeDef.type}`);
}
return mapped;

Copilot uses AI. Check for mistakes.
}
case "array":
this.includes.add("vector");
return `std::vector<${this.typeToString(typeDef.items)}>`;
case "object":
// Inline objects become std::map<std::string, ...>
// For heterogeneous objects, we'd need a struct, but for now use generic map
this.includes.add("map");
this.includes.add("string");
return "std::map<std::string, std::string>";
case "map":
this.includes.add("unordered_map");
return `std::unordered_map<${this.typeToString(typeDef.keyType)}, ${this.typeToString(typeDef.valueType)}>`;
case "tuple":
this.includes.add("tuple");
return `std::tuple<${typeDef.items.map((i) => this.typeToString(i)).join(", ")}>`;
case "union": {
// Use std::variant for unions
this.includes.add("variant");
return `std::variant<${typeDef.types.map((t) => this.typeToString(t)).join(", ")}>`;
}
case "reference":
// Named types (TreeNode, ListNode, etc.) - use pointer notation
return `${typeDef.name}*`;
}
}

generateTypeDefinitions(schema: FunctionSignatureSchema): string {
if (!schema.namedTypes?.length) return "";

return schema.namedTypes
.map((nt) => {
if (nt.definition.kind === "object") {
const props = Object.entries(nt.definition.properties)
.map(([k, v]) => ` ${this.typeToString(v)} ${k};`)
.join("\n");
// Generate constructor
const constructorParams = Object.entries(nt.definition.properties)
.map(([k, v]) => `${this.typeToString(v)} _${k}`)
.join(", ");
const constructorInits = Object.keys(nt.definition.properties)
.map((k) => `${k}(_${k})`)
.join(", ");

return `struct ${nt.name} {\n${props}\n ${nt.name}(${constructorParams}) : ${constructorInits} {}\n};`;
Comment on lines +50 to +62
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generateTypeDefinitions method generates constructors for struct definitions, but it doesn't handle the case where an object has no properties (empty properties object). This would result in invalid C++ syntax with trailing colons in the constructor. Consider adding a check:

if (nt.definition.kind === "object") {
  const props = Object.entries(nt.definition.properties)
    .map(([k, v]) => `    ${this.typeToString(v)} ${k};`)
    .join("\n");
  
  // Only generate constructor if there are properties
  if (Object.keys(nt.definition.properties).length === 0) {
    return `struct ${nt.name} {\n${props}\n};`;
  }
  
  const constructorParams = Object.entries(nt.definition.properties)
    .map(([k, v]) => `${this.typeToString(v)} _${k}`)
    .join(", ");
  const constructorInits = Object.keys(nt.definition.properties)
    .map((k) => `${k}(_${k})`)
    .join(", ");

  return `struct ${nt.name} {\n${props}\n    ${nt.name}(${constructorParams}) : ${constructorInits} {}\n};`;
}

Copilot uses AI. Check for mistakes.
}
return `using ${nt.name} = ${this.typeToString(nt.definition)};`;
})
.join("\n\n");
}

generateScaffold(schema: FunctionSignatureSchema): string {
const params = schema.parameters
.map((p) => {
const typeStr = this.typeToString(p.type);
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ generator doesn't handle optional parameters (unlike TypeScript and Python generators which check p.optional). If the schema includes optional parameters, they won't be properly represented in C++. Consider adding support for optional parameters using std::optional<T>:

const params = schema.parameters
  .map((p) => {
    const typeStr = this.typeToString(p.type);
    if (p.optional) {
      this.includes.add("optional");
      return `std::optional<${typeStr}> ${p.name}`;
    }
    return `${typeStr} ${p.name}`;
  })
  .join(", ");
Suggested change
const typeStr = this.typeToString(p.type);
const typeStr = this.typeToString(p.type);
if (p.optional) {
this.includes.add("optional");
return `std::optional<${typeStr}> ${p.name}`;
}

Copilot uses AI. Check for mistakes.
return `${typeStr} ${p.name}`;
})
.join(", ");
const returnType = this.typeToString(schema.returnType);
return `${returnType} runSolution(${params}) {\n // TODO: implement your solution here\n throw std::runtime_error("Not implemented");\n}`;
}

generateStarterCode(schema: FunctionSignatureSchema): string {
// Clear includes before generating to get fresh set
this.includes.clear();

// Generate type definitions first (this populates includes)
const typeDefs = this.generateTypeDefinitions(schema);

// Generate scaffold (this also populates includes)
const scaffold = this.generateScaffold(schema);

// Build include statements
const commonIncludes = ["iostream", "stdexcept"];
const allIncludes = [...new Set([...commonIncludes, ...Array.from(this.includes)])].sort();
const includeLines = allIncludes.map((inc) => `#include <${inc}>`).join("\n");

// Combine all parts
const parts = [includeLines];
if (typeDefs) {
parts.push("\n\n" + typeDefs);
}
parts.push("\n\n" + scaffold);

return parts.join("");
}

/**
* Generate the runner code that calls runSolution with deserialized parameters
*/
generateRunnerCode(schema: FunctionSignatureSchema): string {
// Generate parameter deserialization code
const paramDeserializations = schema.parameters
.map(
(p, index) =>
` auto ${p.name} = input[${index}].get<${this.typeToString(p.type)}>();`
)
.join("\n");

const paramNames = schema.parameters.map((p) => p.name).join(", ");
Comment on lines +108 to +117
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The generateRunnerCode method doesn't handle the case where a function has no parameters. When schema.parameters is empty, paramDeserializations will be an empty string and paramNames will be an empty string, but the generated code would still have comments and structure that suggests parameters. While this should still compile correctly, consider adding a comment or adjusting the code generation for clarity in the zero-parameter case.

Copilot uses AI. Check for mistakes.

return `
#include <fstream>
#include <sstream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <input.json> <output.json>" << std::endl;
return 1;
}

std::string inputPath = argv[1];
std::string outputPath = argv[2];

try {
// Read input JSON
std::ifstream inputFile(inputPath);
if (!inputFile.is_open()) {
throw std::runtime_error("Failed to open input file");
}
json input = json::parse(inputFile);
inputFile.close();

// Capture stdout
std::stringstream stdoutCapture;
std::streambuf* oldCout = std::cout.rdbuf(stdoutCapture.rdbuf());

// Deserialize parameters
${paramDeserializations}

// Call user's solution
auto result = runSolution(${paramNames});

// Restore stdout
std::cout.rdbuf(oldCout);

// Write output JSON
json output;
output["success"] = true;
output["result"] = result;
output["stdout"] = stdoutCapture.str();

std::ofstream outputFile(outputPath);
outputFile << output.dump();
outputFile.close();

} catch (const std::exception& e) {
json output;
output["success"] = false;
output["error"] = e.what();
output["trace"] = "";
output["stdout"] = "";

std::ofstream outputFile(outputPath);
outputFile << output.dump();
outputFile.close();

// Exit with code 0 so main code can read output.json and handle the error
return 0;
}

return 0;
}
`.trim();
}
}
4 changes: 4 additions & 0 deletions apps/backend/src/problem-actions/src/code-generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TypeScriptGenerator } from "./typescript-generator";
import { PythonGenerator } from "./python-generator";
import { CppGenerator } from "./cpp-generator";
import type { CodeGenerator, CodeGenLanguage } from "./types";

export function createCodeGenerator(language: CodeGenLanguage): CodeGenerator {
Expand All @@ -8,6 +9,8 @@ export function createCodeGenerator(language: CodeGenLanguage): CodeGenerator {
return new TypeScriptGenerator();
case "python":
return new PythonGenerator();
case "cpp":
return new CppGenerator();
default:
throw new Error(`Unsupported language: ${language}`);
}
Expand All @@ -18,3 +21,4 @@ export type { CodeGenerator, CodeGenLanguage } from "./types";
export { CodeGenLanguageSchema } from "./types";
export { TypeScriptGenerator } from "./typescript-generator";
export { PythonGenerator } from "./python-generator";
export { CppGenerator } from "./cpp-generator";
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export interface CodeGenerator {
}

// Languages supported by the code generator (subset of all supported languages)
export const CodeGenLanguageSchema = z.enum(["typescript", "python"]);
export const CodeGenLanguageSchema = z.enum(["typescript", "python", "cpp"]);
export type CodeGenLanguage = z.infer<typeof CodeGenLanguageSchema>;
Loading