Step 374: Add Scheme parser and pipeline routing

This commit is contained in:
Bill
2026-02-16 11:30:33 -07:00
parent 70deab8042
commit b7918108ea
6 changed files with 729 additions and 0 deletions

View File

@@ -2245,4 +2245,13 @@ target_link_libraries(step373_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step374_test tests/step374_test.cpp)
target_include_directories(step374_test PRIVATE src)
target_link_libraries(step374_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -131,6 +131,10 @@ public:
auto pr = CommonLispParser::parseCommonLispWithDiagnostics(source);
diags = std::move(pr.diagnostics);
return std::move(pr.module);
} else if (language == "scheme" || language == "scm") {
auto pr = SchemeParser::parseSchemeWithDiagnostics(source);
diags = std::move(pr.diagnostics);
return std::move(pr.module);
}
return nullptr;
}

View File

@@ -167,3 +167,4 @@ private:
#include "ast/CParser.h"
#include "ast/WatParser.h"
#include "ast/CommonLispParser.h"
#include "ast/SchemeParser.h"

View File

@@ -0,0 +1,461 @@
#pragma once
#include "ASTNode.h"
#include "Module.h"
#include "Function.h"
#include "Variable.h"
#include "Parameter.h"
#include "Type.h"
#include "Statement.h"
#include "Expression.h"
#include "AsyncNodes.h"
#include "PreprocessorNodes.h"
#include "Annotation.h"
#include "../ast/Parser.h"
#include <cctype>
#include <string>
#include <vector>
class SchemeParser {
public:
static std::unique_ptr<Module> parseScheme(const std::string& source) {
ParseResult result = parseSchemeWithDiagnostics(source);
return std::move(result.module);
}
static ParseResult parseSchemeWithDiagnostics(const std::string& source) {
ParseResult result;
result.module = std::make_unique<Module>();
result.module->id = IdGenerator::next("mod");
result.module->name = "parsed_scheme_module";
result.module->targetLanguage = "scheme";
std::vector<SExpr> forms = parseForms(source, result.diagnostics);
for (const auto& form : forms) {
convertTopLevelForm(form, result.module.get());
}
return result;
}
private:
struct SExpr {
bool isList = false;
std::string atom;
std::vector<SExpr> list;
};
static bool isWhitespace(char c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
static bool isDelimiter(char c) {
return isWhitespace(c) || c == '(' || c == ')' || c == '\'' || c == '`' || c == ';';
}
static bool isInteger(const std::string& s) {
if (s.empty()) return false;
size_t i = (s[0] == '-' || s[0] == '+') ? 1 : 0;
if (i >= s.size()) return false;
for (; i < s.size(); ++i) {
if (!std::isdigit(static_cast<unsigned char>(s[i]))) return false;
}
return true;
}
static bool isFloat(const std::string& s) {
if (s.empty()) return false;
bool seenDot = false;
bool seenDigit = false;
size_t i = (s[0] == '-' || s[0] == '+') ? 1 : 0;
if (i >= s.size()) return false;
for (; i < s.size(); ++i) {
char c = s[i];
if (std::isdigit(static_cast<unsigned char>(c))) {
seenDigit = true;
} else if (c == '.' && !seenDot) {
seenDot = true;
} else {
return false;
}
}
return seenDigit && seenDot;
}
static bool isStringToken(const std::string& s) {
return s.size() >= 2 && s.front() == '"' && s.back() == '"';
}
static std::string canonicalHead(const std::string& symbol) {
size_t pos = symbol.rfind(':');
if (pos == std::string::npos) return symbol;
return symbol.substr(pos + 1);
}
static std::string atomText(const SExpr& expr) {
return expr.isList ? "" : expr.atom;
}
static std::string headSymbol(const SExpr& expr) {
if (!expr.isList || expr.list.empty() || expr.list[0].isList) return "";
return canonicalHead(expr.list[0].atom);
}
static void skipWhitespaceAndComments(const std::string& source, size_t& pos) {
while (pos < source.size()) {
if (isWhitespace(source[pos])) {
++pos;
continue;
}
if (source[pos] == ';') {
while (pos < source.size() && source[pos] != '\n') ++pos;
continue;
}
break;
}
}
static std::string parseStringToken(const std::string& source, size_t& pos,
std::vector<ParseDiagnostic>& diags) {
std::string out;
out.push_back('"');
++pos;
bool escaped = false;
while (pos < source.size()) {
char c = source[pos++];
out.push_back(c);
if (escaped) {
escaped = false;
continue;
}
if (c == '\\') {
escaped = true;
} else if (c == '"') {
return out;
}
}
diags.push_back({1, 1, "Unterminated string in Scheme source", "error"});
return out;
}
static std::string parseAtomToken(const std::string& source, size_t& pos) {
size_t start = pos;
while (pos < source.size() && !isDelimiter(source[pos])) ++pos;
return source.substr(start, pos - start);
}
static SExpr makeQuotedForm(const SExpr& value) {
SExpr q;
q.isList = true;
SExpr quoteSym;
quoteSym.atom = "quote";
q.list.push_back(quoteSym);
q.list.push_back(value);
return q;
}
static SExpr parseSingleForm(const std::string& source, size_t& pos,
std::vector<ParseDiagnostic>& diags) {
skipWhitespaceAndComments(source, pos);
if (pos >= source.size()) return SExpr{};
if (source[pos] == '(') {
++pos;
SExpr list;
list.isList = true;
for (;;) {
skipWhitespaceAndComments(source, pos);
if (pos >= source.size()) {
diags.push_back({1, 1, "Unbalanced parentheses in Scheme source", "error"});
return list;
}
if (source[pos] == ')') {
++pos;
return list;
}
list.list.push_back(parseSingleForm(source, pos, diags));
}
}
if (source[pos] == ')') {
diags.push_back({1, 1, "Unmatched closing parenthesis in Scheme source", "error"});
++pos;
return SExpr{};
}
if (source[pos] == '\'' || source[pos] == '`') {
++pos;
SExpr inner = parseSingleForm(source, pos, diags);
return makeQuotedForm(inner);
}
if (source[pos] == '"') {
SExpr atom;
atom.atom = parseStringToken(source, pos, diags);
return atom;
}
SExpr atom;
atom.atom = parseAtomToken(source, pos);
return atom;
}
static std::vector<SExpr> parseForms(const std::string& source,
std::vector<ParseDiagnostic>& diags) {
std::vector<SExpr> out;
size_t pos = 0;
while (pos < source.size()) {
skipWhitespaceAndComments(source, pos);
if (pos >= source.size()) break;
out.push_back(parseSingleForm(source, pos, diags));
}
return out;
}
static void appendExpressionStatement(ASTNode* owner, const std::string& role, ASTNode* expr) {
if (!expr) return;
auto* stmt = new ExpressionStatement();
stmt->id = IdGenerator::next("exprstmt");
stmt->setChild("expression", expr);
owner->addChild(role, stmt);
}
static ASTNode* convertExpression(const SExpr& expr);
static ASTNode* convertIf(const SExpr& expr) {
if (expr.list.size() < 3) return nullptr;
auto* node = new IfStatement();
node->id = IdGenerator::next("if");
node->setChild("condition", convertExpression(expr.list[1]));
appendExpressionStatement(node, "thenBranch", convertExpression(expr.list[2]));
if (expr.list.size() > 3) {
appendExpressionStatement(node, "elseBranch", convertExpression(expr.list[3]));
}
return node;
}
static ASTNode* convertCond(const SExpr& expr) {
IfStatement* root = nullptr;
IfStatement* cur = nullptr;
for (size_t i = 1; i < expr.list.size(); ++i) {
const auto& clause = expr.list[i];
if (!clause.isList || clause.list.empty()) continue;
auto* node = new IfStatement();
node->id = IdGenerator::next("if");
node->setChild("condition", convertExpression(clause.list[0]));
if (clause.list.size() > 1) {
appendExpressionStatement(node, "thenBranch", convertExpression(clause.list[1]));
}
if (!root) root = node;
if (cur) cur->addChild("elseBranch", node);
cur = node;
}
return root;
}
static ASTNode* convertLambda(const SExpr& expr) {
auto* lambda = new LambdaExpression();
lambda->id = IdGenerator::next("lambda");
if (expr.list.size() > 1 && expr.list[1].isList) {
for (const auto& p : expr.list[1].list) {
if (p.isList || p.atom.empty()) continue;
auto* param = new Parameter(IdGenerator::next("param"), p.atom);
lambda->addChild("parameters", param);
}
}
for (size_t i = 2; i < expr.list.size(); ++i) {
appendExpressionStatement(lambda, "body", convertExpression(expr.list[i]));
}
return lambda;
}
static ASTNode* convertLet(const SExpr& expr) {
auto* block = new Block();
block->id = IdGenerator::next("blk");
if (expr.list.size() > 1 && expr.list[1].isList) {
for (const auto& binding : expr.list[1].list) {
if (!binding.isList || binding.list.empty()) continue;
std::string name = atomText(binding.list[0]);
if (name.empty()) continue;
auto* v = new Variable(IdGenerator::next("var"), name);
if (binding.list.size() > 1) {
v->setChild("value", convertExpression(binding.list[1]));
}
block->addChild("statements", v);
}
}
for (size_t i = 2; i < expr.list.size(); ++i) {
appendExpressionStatement(block, "statements", convertExpression(expr.list[i]));
}
return block;
}
static ASTNode* convertDo(const SExpr& expr) {
auto* loop = new ForLoop();
loop->id = IdGenerator::next("for");
loop->iteratorName = "do";
if (expr.list.size() > 1 && expr.list[1].isList && !expr.list[1].list.empty()) {
const auto& firstBinding = expr.list[1].list[0];
if (firstBinding.isList && !firstBinding.list.empty()) {
loop->iteratorName = atomText(firstBinding.list[0]);
if (firstBinding.list.size() > 1) {
loop->setChild("iterable", convertExpression(firstBinding.list[1]));
}
}
}
size_t bodyStart = 3;
if (expr.list.size() > 2 && expr.list[2].isList && expr.list[2].list.size() > 1) {
appendExpressionStatement(loop, "body", convertExpression(expr.list[2].list[1]));
}
for (size_t i = bodyStart; i < expr.list.size(); ++i) {
appendExpressionStatement(loop, "body", convertExpression(expr.list[i]));
}
return loop;
}
static ASTNode* convertBegin(const SExpr& expr) {
auto* block = new Block();
block->id = IdGenerator::next("blk");
for (size_t i = 1; i < expr.list.size(); ++i) {
appendExpressionStatement(block, "statements", convertExpression(expr.list[i]));
}
return block;
}
static ASTNode* convertCall(const SExpr& expr) {
auto* call = new FunctionCall();
call->id = IdGenerator::next("call");
std::string head = headSymbol(expr);
if (head == "call-with-current-continuation" || head == "call/cc") {
call->functionName = head;
auto* exec = new ExecAnnotation();
exec->id = IdGenerator::next("anno");
exec->mode = "continuation";
call->addChild("annotations", exec);
} else {
call->functionName = atomText(expr.list[0]);
}
size_t start = 1;
for (size_t i = start; i < expr.list.size(); ++i) {
ASTNode* arg = convertExpression(expr.list[i]);
if (arg) call->addChild("arguments", arg);
}
return call;
}
static ASTNode* convertValues(const SExpr& expr) {
auto* call = new FunctionCall();
call->id = IdGenerator::next("call");
call->functionName = "values";
for (size_t i = 1; i < expr.list.size(); ++i) {
ASTNode* arg = convertExpression(expr.list[i]);
if (arg) call->addChild("arguments", arg);
}
auto* contract = new ContractAnnotation();
contract->id = IdGenerator::next("anno");
contract->returnShape = "multiple-values";
call->addChild("annotations", contract);
return call;
}
static ASTNode* convertQuote(const SExpr& expr) {
ASTNode* quoted = (expr.list.size() > 1) ? convertExpression(expr.list[1]) : nullptr;
if (!quoted) quoted = new VariableReference(IdGenerator::next("ref"), "nil");
auto* meta = new MetaAnnotation();
meta->id = IdGenerator::next("anno");
meta->state = "quoted";
quoted->addChild("annotations", meta);
return quoted;
}
static void convertDefineFunction(const SExpr& form, Module* module) {
if (form.list.size() < 3 || !form.list[1].isList || form.list[1].list.empty()) return;
std::string name = atomText(form.list[1].list[0]);
auto* fn = new Function(IdGenerator::next("fn"), name);
for (size_t i = 1; i < form.list[1].list.size(); ++i) {
std::string pname = atomText(form.list[1].list[i]);
if (pname.empty()) continue;
fn->addChild("parameters", new Parameter(IdGenerator::next("param"), pname));
}
for (size_t i = 2; i < form.list.size(); ++i) {
appendExpressionStatement(fn, "body", convertExpression(form.list[i]));
}
module->addChild("functions", fn);
}
static void convertDefineVariable(const SExpr& form, Module* module) {
if (form.list.size() < 2) return;
std::string name = atomText(form.list[1]);
auto* var = new Variable(IdGenerator::next("var"), name);
if (form.list.size() > 2) {
var->setChild("value", convertExpression(form.list[2]));
}
module->addChild("variables", var);
}
static void convertDefineSyntax(const SExpr& form, Module* module) {
if (form.list.size() < 2) return;
auto* macro = new MacroDefinition(IdGenerator::next("mac"), atomText(form.list[1]));
macro->isFunctionLike = true;
macro->body = "syntax-rules";
auto* meta = new MetaAnnotation();
meta->id = IdGenerator::next("anno");
meta->state = "hygienic";
macro->addChild("annotations", meta);
module->addChild("statements", macro);
}
static void convertTopLevelForm(const SExpr& form, Module* module) {
if (!form.isList || form.list.empty()) return;
std::string head = headSymbol(form);
if (head == "define") {
if (form.list.size() > 1 && form.list[1].isList) {
convertDefineFunction(form, module);
} else {
convertDefineVariable(form, module);
}
return;
}
if (head == "define-syntax") {
convertDefineSyntax(form, module);
return;
}
appendExpressionStatement(module, "statements", convertExpression(form));
}
};
inline ASTNode* SchemeParser::convertExpression(const SExpr& expr) {
if (!expr.isList) {
if (expr.atom.empty()) return nullptr;
if (isInteger(expr.atom)) return new IntegerLiteral(IdGenerator::next("int"), std::stoi(expr.atom));
if (isFloat(expr.atom)) return new FloatLiteral(IdGenerator::next("flt"), expr.atom);
if (isStringToken(expr.atom)) {
return new StringLiteral(IdGenerator::next("str"), expr.atom.substr(1, expr.atom.size() - 2));
}
if (expr.atom == "#t" || expr.atom == "t") return new BooleanLiteral(IdGenerator::next("bool"), true);
if (expr.atom == "#f" || expr.atom == "nil") return new BooleanLiteral(IdGenerator::next("bool"), false);
return new VariableReference(IdGenerator::next("ref"), expr.atom);
}
std::string head = headSymbol(expr);
if (head == "if") return convertIf(expr);
if (head == "cond") return convertCond(expr);
if (head == "lambda") return convertLambda(expr);
if (head == "let" || head == "let*" || head == "letrec") return convertLet(expr);
if (head == "do") return convertDo(expr);
if (head == "begin") return convertBegin(expr);
if (head == "values") return convertValues(expr);
if (head == "quote") return convertQuote(expr);
return convertCall(expr);
}

View File

@@ -0,0 +1,208 @@
// Step 374: Scheme Parser (12 tests)
#include <cassert>
#include <iostream>
#include <string>
#include "Pipeline.h"
#include "ast/SchemeParser.h"
static bool hasBodyNode(const Function* fn, const std::string& conceptType) {
for (auto* stmt : fn->getChildren("body")) {
if (stmt->conceptType == conceptType) return true;
if (stmt->conceptType == "ExpressionStatement") {
ASTNode* expr = stmt->getChild("expression");
if (expr && expr->conceptType == conceptType) return true;
}
}
return false;
}
int main() {
int passed = 0;
// Test 1: define function parsing
{
auto mod = SchemeParser::parseScheme("(define (add x y) (+ x y))");
assert(mod->targetLanguage == "scheme");
assert(mod->getChildren("functions").size() == 1);
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
assert(fn->name == "add");
assert(fn->getChildren("parameters").size() == 2);
std::cout << "Test 1 PASSED: define function parsing\n";
passed++;
}
// Test 2: define variable parsing
{
auto mod = SchemeParser::parseScheme("(define answer 42)");
assert(mod->getChildren("variables").size() == 1);
auto* var = static_cast<Variable*>(mod->getChildren("variables")[0]);
assert(var->name == "answer");
assert(var->getChild("value") != nullptr);
std::cout << "Test 2 PASSED: define variable parsing\n";
passed++;
}
// Test 3: lambda parsing
{
auto mod = SchemeParser::parseScheme("(define (mk) (lambda (x) (+ x 1)))");
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
assert(hasBodyNode(fn, "LambdaExpression"));
std::cout << "Test 3 PASSED: lambda parsing\n";
passed++;
}
// Test 4: let / let* / letrec parsing
{
std::string src =
"(define (a) (let ((x 1)) x))\n"
"(define (b) (let* ((x 1) (y 2)) (+ x y)))\n"
"(define (c) (letrec ((f (lambda (n) n))) (f 1)))";
auto mod = SchemeParser::parseScheme(src);
auto* fa = static_cast<Function*>(mod->getChildren("functions")[0]);
auto* fb = static_cast<Function*>(mod->getChildren("functions")[1]);
auto* fc = static_cast<Function*>(mod->getChildren("functions")[2]);
assert(hasBodyNode(fa, "Block"));
assert(hasBodyNode(fb, "Block"));
assert(hasBodyNode(fc, "Block"));
std::cout << "Test 4 PASSED: let variants parsing\n";
passed++;
}
// Test 5: if and cond parsing
{
std::string src =
"(define (s x) (if x 1 0))\n"
"(define (c x) (cond ((> x 0) 1) ((< x 0) -1) (else 0)))";
auto mod = SchemeParser::parseScheme(src);
auto* f1 = static_cast<Function*>(mod->getChildren("functions")[0]);
auto* f2 = static_cast<Function*>(mod->getChildren("functions")[1]);
assert(hasBodyNode(f1, "IfStatement"));
assert(hasBodyNode(f2, "IfStatement"));
std::cout << "Test 5 PASSED: if/cond parsing\n";
passed++;
}
// Test 6: do loop parsing
{
std::string src = "(define (loopn n) (do ((i 0 (+ i 1))) ((> i n) i) (display i)))";
auto mod = SchemeParser::parseScheme(src);
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
assert(hasBodyNode(fn, "ForLoop"));
std::cout << "Test 6 PASSED: do loop parsing\n";
passed++;
}
// Test 7: call/cc continuation annotation
{
std::string src =
"(define (cc f) (call-with-current-continuation f))\n"
"(define (cc2 f) (call/cc f))";
auto mod = SchemeParser::parseScheme(src);
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
bool hasContinuation = false;
for (auto* stmt : fn->getChildren("body")) {
ASTNode* expr = stmt->getChild("expression");
if (!expr || expr->conceptType != "FunctionCall") continue;
for (auto* anno : expr->getChildren("annotations")) {
if (anno->conceptType == "ExecAnnotation") {
auto* ex = static_cast<ExecAnnotation*>(anno);
if (ex->mode == "continuation") hasContinuation = true;
}
}
}
assert(hasContinuation);
std::cout << "Test 7 PASSED: call/cc continuation annotation\n";
passed++;
}
// Test 8: define-syntax -> macro + hygienic meta
{
std::string src = "(define-syntax when1 (syntax-rules () ((_ t b) (if t b #f))))";
auto mod = SchemeParser::parseScheme(src);
bool foundMacro = false;
bool hygienic = false;
for (auto* stmt : mod->getChildren("statements")) {
if (stmt->conceptType != "MacroDefinition") continue;
foundMacro = true;
for (auto* anno : stmt->getChildren("annotations")) {
if (anno->conceptType == "MetaAnnotation") {
auto* m = static_cast<MetaAnnotation*>(anno);
if (m->state == "hygienic") hygienic = true;
}
}
}
assert(foundMacro && hygienic);
std::cout << "Test 8 PASSED: define-syntax hygienic macro mapping\n";
passed++;
}
// Test 9: values multi-return mapping
{
std::string src = "(define (pair x) (values x (+ x 1)))";
auto mod = SchemeParser::parseScheme(src);
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
bool hasContract = false;
for (auto* stmt : fn->getChildren("body")) {
ASTNode* expr = stmt->getChild("expression");
if (!expr || expr->conceptType != "FunctionCall") continue;
for (auto* anno : expr->getChildren("annotations")) {
if (anno->conceptType == "ContractAnnotation") hasContract = true;
}
}
assert(hasContract);
std::cout << "Test 9 PASSED: values -> Contract annotation\n";
passed++;
}
// Test 10: begin form maps to sequence block
{
std::string src = "(define (f) (begin (display 1) (display 2) 3))";
auto mod = SchemeParser::parseScheme(src);
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
assert(hasBodyNode(fn, "Block"));
std::cout << "Test 10 PASSED: begin -> block sequence\n";
passed++;
}
// Test 11: deep nested s-expressions
{
std::string src = "(define (deep x) (a (b (c (d (e (f (g x))))))))";
auto mod = SchemeParser::parseScheme(src);
auto* fn = static_cast<Function*>(mod->getChildren("functions")[0]);
assert(hasBodyNode(fn, "FunctionCall"));
std::cout << "Test 11 PASSED: deep nesting parse\n";
passed++;
}
// Test 12: mixed source + pipeline route + diagnostics
{
std::string src =
"(define x 1)\n"
"(define (id y) y)\n"
"(define-syntax m (syntax-rules () ((_ z) z)))\n";
auto mod = SchemeParser::parseScheme(src);
assert(mod->getChildren("variables").size() == 1);
assert(mod->getChildren("functions").size() == 1);
bool macroFound = false;
for (auto* stmt : mod->getChildren("statements")) {
if (stmt->conceptType == "MacroDefinition") macroFound = true;
}
assert(macroFound);
Pipeline p;
std::vector<ParseDiagnostic> diags;
auto parsedScheme = p.parse("(define (f x) x)", "scheme", diags);
auto parsedScm = p.parse("(define (f x) x)", "scm", diags);
assert(parsedScheme != nullptr && parsedScm != nullptr);
auto malformed = SchemeParser::parseSchemeWithDiagnostics("(define (f x) (+ x 1)");
assert(!malformed.diagnostics.empty());
std::cout << "Test 12 PASSED: mixed forms + pipeline route + diagnostics\n";
passed++;
}
std::cout << "\nResults: " << passed << "/12\n";
assert(passed == 12);
return 0;
}

View File

@@ -2565,6 +2565,52 @@ artifact compatibility with Semanno and compact AST tooling.
- `editor/tests/step373_test.cpp` remains within project file-size guidance
for test artifacts (`147` lines)
### Step 374: Scheme Parser
**Status:** PASS (12/12 tests)
Added a dedicated Scheme parser with S-expression parsing and Scheme-specific
form mapping into the Whetstone AST, including continuation and hygienic macro
signals.
**Files created:**
- `editor/src/ast/SchemeParser.h` — Scheme parse support:
- recursive S-expression reader with Scheme comment and quote handling
- top-level mapping for `define` (function/value) and `define-syntax`
- expression mapping for `lambda`, `let`/`let*`/`letrec`, `if`, `cond`,
`do`, `begin`, `call-with-current-continuation`/`call/cc`, `quote`,
and `values`
- continuation call mapping with `Exec(mode=continuation)` annotation
- `define-syntax` mapping to `MacroDefinition` with hygienic `Meta` signal
- `editor/tests/step374_test.cpp` — 12 tests covering:
1. `define` function parsing
2. `define` variable parsing
3. `lambda` parsing
4. `let` variants mapping
5. `if`/`cond` mapping
6. `do` loop mapping
7. `call/cc` continuation annotation mapping
8. `define-syntax` hygienic macro mapping
9. `values` contract mapping
10. `begin` sequence mapping
11. deep nesting parsing
12. mixed forms + pipeline scheme/scm routing + malformed diagnostics
**Files modified:**
- `editor/src/ast/Parser.h` — include `ast/SchemeParser.h`
- `editor/src/Pipeline.h` — add parse routing for `\"scheme\"` and `\"scm\"`
- `editor/CMakeLists.txt``step374_test` target
**Verification run:**
- `step374_test` — PASS (12/12) new step coverage
- `step373_test` — PASS (8/8) regression coverage
- `step372_test` — PASS (12/12) regression coverage
- `step371_test` — PASS (12/12) regression coverage
- `step370_test` — PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/ast/SchemeParser.h` is within header size limit
(`461` lines <= `600`)
# Roadmap Planning — Sprints 12-25+
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)