#include "cps/CodeGen.h" #include "cps/ArrayHandler.h" #include "cps/FileAST.h" #include "cps/FileHandler.h" #include "cps/Lexer.h" #include "cps/TypeAST.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Verifier.h" #include #include using namespace llvm; using namespace cps; namespace { // Single source of truth for the built-in functions: name, arity and return // type. emitExpr checks arity against this table before dispatching, and // getExprTypeInfo reads the return type from it. struct BuiltinInfo { const char *Name; unsigned Arity; const char *ReturnTypeName; }; constexpr BuiltinInfo Builtins[] = { {"LENGTH", 1, "INTEGER"}, {"MID", 3, "STRING"}, {"RIGHT", 2, "STRING"}, {"LEFT", 2, "STRING"}, {"LCASE", 1, "STRING"}, {"UCASE", 1, "STRING"}, {"ASC", 1, "INTEGER"}, {"CHR", 1, "STRING"}, {"IS_NUM", 1, "BOOLEAN"}, {"NUM_TO_STR", 1, "STRING"}, {"STR_TO_NUM", 1, "REAL"}, {"EOF", 1, "BOOLEAN"}, }; const BuiltinInfo *findBuiltin(const std::string &Name) { for (const auto &B : Builtins) { if (Name == B.Name) return &B; } return nullptr; } } // namespace CodeGen::~CodeGen() = default; bool CodeGen::isBuiltinName(const std::string &Name) { return findBuiltin(Name) != nullptr; } void CodeGen::reportError(const char *Fmt, ...) { va_list Args; va_start(Args, Fmt); fprintf(stderr, "error: "); vfprintf(stderr, Fmt, Args); fprintf(stderr, "\n"); va_end(Args); HadError = true; } CodeGen::CodeGen() { TheContext = std::make_unique(); TheModule = std::make_unique("cps_module", *TheContext); Builder = std::make_unique>(*TheContext); Types = std::make_unique(*TheContext); RuntimeChecker = std::make_unique(*TheModule, *TheContext, *Builder); Arrays = std::make_unique(*TheContext, *Builder, *TheModule, Symbols, *Types, *RuntimeChecker); FuncGen = std::make_unique(*TheContext, *TheModule, *Builder, *Types, Symbols, HadError); ArithHandler = std::make_unique(*TheContext, *Builder, HadError, *RuntimeChecker); StrHandler = std::make_unique(*TheContext, *Builder, *TheModule); StrConvHandler = std::make_unique(*TheContext, *Builder, *TheModule); Files = std::make_unique(*TheContext, *Builder, *TheModule, *RuntimeChecker); // Array parameters are bound into ArrayHandler through this inversion so // FunctionGen never sees the array subsystem. FuncGen->setArrayParamBinder( [this](const ParamDecl &P, Value *DataPtr, const std::vector &LBs, const std::vector &UBs) { Arrays->bindArrayParameter(P.Name, P.TypeName, DataPtr, LBs, UBs, /*MakeCopy=*/P.Mode == PassMode::ByVal, *this); }); SetupExternalFunctions(); } void CodeGen::SetupExternalFunctions() { std::vector PrintfArgs; PrintfArgs.push_back(PointerType::getUnqual(*TheContext)); FunctionType *PrintfType = FunctionType::get(Type::getInt32Ty(*TheContext), PrintfArgs, true); PrintfFunc = TheModule->getOrInsertFunction("printf", PrintfType); FunctionType *ScanfType = FunctionType::get(Type::getInt32Ty(*TheContext), PrintfArgs, true); ScanfFunc = TheModule->getOrInsertFunction("scanf", ScanfType); FunctionType *MallocType = FunctionType::get(PointerType::getUnqual(*TheContext), {Type::getInt64Ty(*TheContext)}, false); MallocFunc = TheModule->getOrInsertFunction("malloc", MallocType); PrintfFormatStr = Builder->CreateGlobalStringPtr("%lld\n", "fmt_nl", 0, TheModule.get()); PrintfFloatFormatStr = Builder->CreateGlobalStringPtr("%f\n", "fmt_flt", 0, TheModule.get()); PrintfStringFormatStr = Builder->CreateGlobalStringPtr("%s\n", "fmt_str", 0, TheModule.get()); PrintfCharFormatStr = Builder->CreateGlobalStringPtr("%c\n", "fmt_chr", 0, TheModule.get()); ScanfFormatStr = Builder->CreateGlobalStringPtr("%lld", "fmt_in", 0, TheModule.get()); ScanfFloatFormatStr = Builder->CreateGlobalStringPtr("%lf", "fmt_in_flt", 0, TheModule.get()); ScanfStringFormatStr = Builder->CreateGlobalStringPtr("%s", "fmt_in_str", 0, TheModule.get()); TrueStr = Builder->CreateGlobalStringPtr("TRUE", "str_true", 0, TheModule.get()); FalseStr = Builder->CreateGlobalStringPtr("FALSE", "str_false", 0, TheModule.get()); EmptyStringStr = Builder->CreateGlobalStringPtr("", "str_empty", 0, TheModule.get()); Arrays->setupExternalFunctions(); } AllocaInst *CodeGen::CreateEntryBlockAlloca(Function *TheFunction, const std::string &VarName) { return CreateEntryBlockAlloca(TheFunction, Type::getInt64Ty(*TheContext), VarName); } AllocaInst *CodeGen::CreateEntryBlockAlloca(Function *TheFunction, Type *AllocType, const std::string &VarName) { IRBuilder<> TmpB(&TheFunction->getEntryBlock(), TheFunction->getEntryBlock().begin()); return TmpB.CreateAlloca(AllocType, nullptr, VarName); } void CodeGen::registerSymbol(const std::string &Name, Value *Storage, const std::string &TypeName, bool IsArray) { Symbols[Name] = {Storage, TypeName, IsArray}; } const SymbolInfo *CodeGen::getSymbolInfo(const std::string &Name) const { auto It = Symbols.find(Name); if (It == Symbols.end()) { return nullptr; } return &It->second; } // A whole-array variable reference evaluates to the raw buffer pointer, which // the pointer->STRING coercion would accept unchanged; file-statement operands // must reject it explicitly or garbage gets treated as a C string. bool CodeGen::isWholeArrayVar(ExprAST *Expr) const { auto *Var = dynamic_cast(Expr); if (!Var) return false; const SymbolInfo *Info = getSymbolInfo(Var->getName()); return Info && Info->IsArray; } const TypeInfo *CodeGen::resolveType(const std::string &TypeName) const { return Types->resolve(TypeName); } const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) const { if (!Expr) return nullptr; if (dynamic_cast(Expr)) return resolveType("INTEGER"); if (dynamic_cast(Expr)) return resolveType("REAL"); if (dynamic_cast(Expr)) return resolveType("BOOLEAN"); if (dynamic_cast(Expr)) return resolveType("CHAR"); if (dynamic_cast(Expr)) return resolveType("STRING"); if (auto *Var = dynamic_cast(Expr)) { const SymbolInfo *Info = getSymbolInfo(Var->getName()); if (Info) return resolveType(Info->TypeName); if (const EnumConstant *EC = Types->lookupEnumConstant(Var->getName())) { return resolveType(EC->TypeName); } return nullptr; } if (auto *Arr = dynamic_cast(Expr)) { const SymbolInfo *Info = getSymbolInfo(Arr->getName()); return Info ? resolveType(Info->TypeName) : nullptr; } if (auto *D = dynamic_cast(Expr)) { const SymbolInfo *Info = getSymbolInfo(D->getBaseName()); if (!Info) return nullptr; std::string TypeName = Info->TypeName; size_t Start = 0; if (Info->IsArray) { // SymbolInfo already holds the ELEMENT type name for arrays. if (D->getAccesses().empty() || D->getAccesses()[0].Kind != AccessKind::Index) return nullptr; Start = 1; } for (size_t I = Start; I < D->getAccesses().size(); ++I) { const TypeInfo *Cur = resolveType(TypeName); if (!Cur) return nullptr; const DesignatorAccess &A = D->getAccesses()[I]; if (A.Kind == AccessKind::Field) { const RecordPayload *RP = Cur->asRecord(); const RecordFieldInfo *FI = RP ? RP->findField(A.FieldName) : nullptr; if (!FI) return nullptr; if (!FI->IsPublic) return nullptr; TypeName = FI->TypeName; } else if (A.Kind == AccessKind::Deref) { const PointerPayload *PP = Cur->asPointer(); if (!PP) return nullptr; TypeName = PP->PointeeTypeName; } else { return nullptr; // indexing a non-array } } return resolveType(TypeName); } // ^x has no pseudocode type of its own; only the pointer admission in // emitCoercedExpr may consume it. if (dynamic_cast(Expr)) return nullptr; if (auto *Unary = dynamic_cast(Expr)) { if (Unary->getOp() == tok_not) { return resolveType("BOOLEAN"); } return getExprTypeInfo(Unary->getOperand()); } if (auto *Bin = dynamic_cast(Expr)) { switch (Bin->getOp()) { case tok_eq: case tok_ne: case '<': case '>': case tok_le: case tok_ge: case tok_and: case tok_or: return resolveType("BOOLEAN"); case '&': return resolveType("STRING"); default: break; } const TypeInfo *L = getExprTypeInfo(Bin->getLHS()); const TypeInfo *R = getExprTypeInfo(Bin->getRHS()); // Enum arithmetic: E +/- INTEGER (enum on the left) yields the enum; // every other user-kind combination has no type (emission rejects it). if ((L && L->isUserKind()) || (R && R->isUserKind())) { if (L && L->isEnum() && R && R->Kind == TypeKind::Integer && (Bin->getOp() == '+' || Bin->getOp() == '-')) { return L; } return nullptr; } if ((L && L->Kind == TypeKind::Real) || (R && R->Kind == TypeKind::Real)) { return resolveType("REAL"); } return resolveType("INTEGER"); } if (auto *Call = dynamic_cast(Expr)) { const std::string &Name = Call->getCallee(); if (const BuiltinInfo *B = findBuiltin(Name)) { return resolveType(B->ReturnTypeName); } if (const FuncSig *Sig = FuncGen->getSignature(Name)) { return resolveType(Sig->ReturnTypeName); } Function *CalleeF = TheModule->getFunction(Name); if (!CalleeF) return resolveType("INTEGER"); Type *RetTy = CalleeF->getReturnType(); if (RetTy->isVoidTy()) return resolveType("VOID"); if (RetTy->isDoubleTy()) return resolveType("REAL"); if (RetTy->isIntegerTy(1)) return resolveType("BOOLEAN"); if (RetTy->isIntegerTy(8)) return resolveType("CHAR"); if (RetTy->isPointerTy()) return resolveType("STRING"); return resolveType("INTEGER"); } return nullptr; } Value *CodeGen::coerceValueToType(Value *Val, const TypeInfo *TargetInfo) { if (!Val || !TargetInfo || !TargetInfo->LLVMType) return nullptr; if (Val->getType() == TargetInfo->LLVMType) { return Val; } switch (TargetInfo->Kind) { case TypeKind::Real: if (Val->getType()->isDoubleTy()) return Val; if (Val->getType()->isIntegerTy(1)) return Builder->CreateUIToFP(Val, Type::getDoubleTy(*TheContext), "bool_to_real"); if (Val->getType()->isIntegerTy(8)) return Builder->CreateUIToFP(Val, Type::getDoubleTy(*TheContext), "char_to_real"); if (Val->getType()->isIntegerTy()) return Builder->CreateSIToFP(Val, Type::getDoubleTy(*TheContext), "int_to_real"); break; case TypeKind::Integer: if (Val->getType()->isIntegerTy(64)) return Val; if (Val->getType()->isDoubleTy()) return Builder->CreateFPToSI(Val, Type::getInt64Ty(*TheContext), "real_to_int"); if (Val->getType()->isIntegerTy(1)) return Builder->CreateZExt(Val, Type::getInt64Ty(*TheContext), "bool_to_int"); if (Val->getType()->isIntegerTy(8)) return Builder->CreateZExt(Val, Type::getInt64Ty(*TheContext), "char_to_int"); if (Val->getType()->isIntegerTy()) return Builder->CreateSExtOrTrunc(Val, Type::getInt64Ty(*TheContext), "int_cast"); break; case TypeKind::Boolean: if (Val->getType()->isIntegerTy(1)) return Val; if (Val->getType()->isIntegerTy()) return Builder->CreateICmpNE(Val, Constant::getNullValue(Val->getType()), "to_bool"); if (Val->getType()->isDoubleTy()) return Builder->CreateFCmpONE(Val, ConstantFP::get(*TheContext, APFloat(0.0)), "to_bool"); if (Val->getType()->isPointerTy()) return Builder->CreateICmpNE(Val, ConstantPointerNull::get(cast(Val->getType())), "ptr_to_bool"); break; case TypeKind::Char: if (Val->getType()->isIntegerTy(8)) return Val; if (Val->getType()->isIntegerTy(1)) return Builder->CreateZExt(Val, Type::getInt8Ty(*TheContext), "bool_to_char"); if (Val->getType()->isDoubleTy()) return Builder->CreateFPToUI(Val, Type::getInt8Ty(*TheContext), "real_to_char"); if (Val->getType()->isIntegerTy()) return Builder->CreateTruncOrBitCast(Val, Type::getInt8Ty(*TheContext), "int_to_char"); break; case TypeKind::String: if (Val->getType()->isPointerTy()) return Val; if (Val->getType()->isIntegerTy(1)) { return Builder->CreateSelect(Val, TrueStr, FalseStr, "bool_to_str"); } if (Val->getType()->isDoubleTy()) { return StrConvHandler->emitNumToStr(Val, true); } if (Val->getType()->isIntegerTy(8)) { Value *Mem = Builder->CreateCall(MallocFunc, ConstantInt::get(*TheContext, APInt(64, 2)), "char_str"); Builder->CreateStore(Val, Mem); Value *NullPtr = Builder->CreateInBoundsGEP(Type::getInt8Ty(*TheContext), Mem, ConstantInt::get(*TheContext, APInt(64, 1))); Builder->CreateStore(ConstantInt::get(Type::getInt8Ty(*TheContext), 0), NullPtr); return Mem; } if (Val->getType()->isIntegerTy()) { Value *AsInt = coerceValueToType(Val, resolveType("INTEGER")); return AsInt ? StrConvHandler->emitNumToStr(AsInt, false) : nullptr; } break; case TypeKind::Void: return nullptr; // Enum/Record/Pointer identity lives in the type NAME; at this level // an enum is a plain i64 and a pointer a plain ptr, so nothing can be // checked here. emitCoercedExpr is the only legitimate door. case TypeKind::Enum: case TypeKind::Record: case TypeKind::Pointer: reportError("Internal: a %s value reached representation coercion without a name-level check", TargetInfo->Name.c_str()); return nullptr; } reportError("Cannot coerce value of LLVM type to target type %s", TargetInfo->Name.c_str()); return nullptr; } void CodeGen::emitDeclareStmt(DeclareStmtAST *Stmt) { const TypeInfo *Info = resolveType(Stmt->getType()); if (!Info || !Info->LLVMType || Info->isVoid()) { reportError("Unknown type %s", Stmt->getType().c_str()); return; } if (Types->lookupEnumConstant(Stmt->getName())) { reportError("'%s' is an enum value and cannot be declared as a variable", Stmt->getName().c_str()); return; } if (Types->resolve(Stmt->getName())) { reportError("'%s' is a type name and cannot be declared as a variable", Stmt->getName().c_str()); return; } Function *TheFunction = Builder->GetInsertBlock()->getParent(); AllocaInst *Alloca = CreateEntryBlockAlloca(TheFunction, Info->LLVMType, Stmt->getName()); Value *InitVal = nullptr; if (Info->isString()) { InitVal = EmptyStringStr; } else { InitVal = Types->getZeroValue(Info->Name); } if (InitVal) { Builder->CreateStore(InitVal, Alloca); } registerSymbol(Stmt->getName(), Alloca, Info->Name, false); } Value *CodeGen::emitStringNullGuard(Value *StrVal) { if (!StrVal || !StrVal->getType()->isPointerTy()) return StrVal; Value *IsNull = Builder->CreateICmpEQ(StrVal, ConstantPointerNull::get(cast(StrVal->getType())), "str_is_null"); return Builder->CreateSelect(IsNull, EmptyStringStr, StrVal, "safe_str"); } void CodeGen::emitOutputValue(Value *Val, const TypeInfo *Info, bool AppendNewline) { if (!Val || !Info) return; std::vector Args; Value *Fmt = nullptr; // Exhaustive over TypeKind (no default): the compiler warns when a new // kind is added without deciding its OUTPUT story. switch (Info->Kind) { case TypeKind::Real: Fmt = AppendNewline ? PrintfFloatFormatStr : Builder->CreateGlobalStringPtr("%f", "fmt_flt_inline", 0, TheModule.get()); Args.push_back(Fmt); Args.push_back(Val); break; case TypeKind::Boolean: { Value *BoolVal = coerceValueToType(Val, resolveType("BOOLEAN")); Fmt = AppendNewline ? PrintfStringFormatStr : Builder->CreateGlobalStringPtr("%s", "fmt_str_inline", 0, TheModule.get()); Args.push_back(Fmt); Args.push_back(Builder->CreateSelect(BoolVal, TrueStr, FalseStr)); break; } case TypeKind::String: { Value *StrVal = emitStringNullGuard(Val); Fmt = AppendNewline ? PrintfStringFormatStr : Builder->CreateGlobalStringPtr("%s", "fmt_str_inline", 0, TheModule.get()); Args.push_back(Fmt); Args.push_back(StrVal); break; } case TypeKind::Char: { Value *CharVal = coerceValueToType(Val, resolveType("CHAR")); Value *Promoted = Builder->CreateZExt(CharVal, Type::getInt32Ty(*TheContext), "char_for_printf"); Fmt = AppendNewline ? PrintfCharFormatStr : Builder->CreateGlobalStringPtr("%c", "fmt_chr_inline", 0, TheModule.get()); Args.push_back(Fmt); Args.push_back(Promoted); break; } case TypeKind::Integer: case TypeKind::Enum: { // an enum prints its ordinal (user decision) Value *IntVal = Info->isEnum() ? Val : coerceValueToType(Val, resolveType("INTEGER")); Fmt = AppendNewline ? PrintfFormatStr : Builder->CreateGlobalStringPtr("%lld", "fmt_int_inline", 0, TheModule.get()); Args.push_back(Fmt); Args.push_back(IntVal); break; } case TypeKind::Record: reportError("Cannot OUTPUT a value of record type %s; output its fields individually", Info->Name.c_str()); return; case TypeKind::Pointer: reportError("Cannot OUTPUT a value of pointer type %s", Info->Name.c_str()); return; case TypeKind::Void: reportError("Cannot OUTPUT a VOID value"); return; } Builder->CreateCall(PrintfFunc, Args); } void CodeGen::compile(const std::vector> &Statements) { FunctionType *FT = FunctionType::get(Type::getInt32Ty(*TheContext), false); Function *F = Function::Create(FT, Function::ExternalLinkage, "main", TheModule.get()); BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", F); Builder->SetInsertPoint(BB); // User types first: prototypes may name them in parameters/returns. runTypePrePass(Statements); // Pre-register every top-level FUNCTION/PROCEDURE prototype so calls may // precede definitions. for (const auto &Stmt : Statements) { if (auto *FuncDef = dynamic_cast(Stmt.get())) { FuncGen->emitPrototype(FuncDef->getProto()); } } for (const auto &Stmt : Statements) { emitStmt(Stmt.get()); } if (!Builder->GetInsertBlock()->getTerminator()) { Builder->CreateRet(ConstantInt::get(*TheContext, APInt(32, 0))); } if (verifyModule(*TheModule, &errs())) { errs() << "fatal error: LLVM module verification failed\n"; HadError = true; } } void CodeGen::print(raw_ostream &Output) { TheModule->print(Output, nullptr); } Value *CodeGen::emitExpr(ExprAST *Expr) { if (!Expr) return nullptr; if (auto *Num = dynamic_cast(Expr)) { return ConstantInt::get(*TheContext, APInt(64, Num->getVal(), true)); } if (auto *Real = dynamic_cast(Expr)) { return ConstantFP::get(*TheContext, APFloat(Real->getVal())); } if (auto *Bool = dynamic_cast(Expr)) { return ConstantInt::get(*TheContext, APInt(1, Bool->getVal() ? 1 : 0)); } if (auto *Chr = dynamic_cast(Expr)) { return ConstantInt::get(Type::getInt8Ty(*TheContext), static_cast(Chr->getVal())); } if (auto *Str = dynamic_cast(Expr)) { return StrHandler->createLiteral(Str->getVal()); } if (auto *Var = dynamic_cast(Expr)) { const SymbolInfo *Info = getSymbolInfo(Var->getName()); if (!Info) { // Enum values are bare identifiers; collisions with variables are // rejected at declaration time, so the order here cannot shadow. if (const EnumConstant *EC = Types->lookupEnumConstant(Var->getName())) { return ConstantInt::get(*TheContext, APInt(64, static_cast(EC->Ordinal), true)); } reportError("Unknown variable name %s", Var->getName().c_str()); return nullptr; } if (Info->IsArray) { return Info->Storage; } const TypeInfo *TypeInfo = resolveType(Info->TypeName); if (!TypeInfo || !TypeInfo->LLVMType) { reportError("Unknown type for variable %s", Var->getName().c_str()); return nullptr; } Value *Loaded = Builder->CreateLoad(TypeInfo->LLVMType, Info->Storage, Var->getName().c_str()); if (TypeInfo->isString()) { Loaded = emitStringNullGuard(Loaded); } return Loaded; } if (auto *ArrAcc = dynamic_cast(Expr)) { return Arrays->emitArrayAccess(ArrAcc, *this); } if (auto *Unary = dynamic_cast(Expr)) { if (Unary->getOp() == tok_not) { Value *Operand = emitCoercedExpr(Unary->getOperand(), resolveType("BOOLEAN")); if (!Operand) return nullptr; return Builder->CreateNot(Operand, "nottmp"); } return nullptr; } if (auto *Bin = dynamic_cast(Expr)) { // Whole arrays evaluate to raw data pointers and ^x to raw addresses; // neither is a value any operator may consume. if (isWholeArrayVar(Bin->getLHS()) || isWholeArrayVar(Bin->getRHS())) { reportError("A whole array cannot be used as an operand; index it"); return nullptr; } if (dynamic_cast(Bin->getLHS()) || dynamic_cast(Bin->getRHS())) { reportError("'^' (address-of) can only be used where a pointer type is expected"); return nullptr; } // Enum/Record/Pointer operands never reach ArithmeticHandler or the // '&' path: their admissible operations are decided by name here. const TypeInfo *LT = getExprTypeInfo(Bin->getLHS()); const TypeInfo *RT = getExprTypeInfo(Bin->getRHS()); if ((LT && LT->isUserKind()) || (RT && RT->isUserKind())) { return emitUserKindBinaryOp(Bin, LT, RT); } Value *L = emitExpr(Bin->getLHS()); Value *R = emitExpr(Bin->getRHS()); if (!L || !R) return nullptr; if (Bin->getOp() == '&') { L = coerceValueToType(L, resolveType("STRING")); R = coerceValueToType(R, resolveType("STRING")); if (!L || !R) return nullptr; return StrHandler->emitConcat(L, R); } // STRING and explicit pointers share LLVM's ptr representation; // string comparisons must dispatch on the pseudocode type. switch (Bin->getOp()) { case tok_eq: case tok_ne: case '<': case '>': case tok_le: case tok_ge: if ((LT && LT->isString()) || (RT && RT->isString())) { if (!(LT && LT->isString() && RT && RT->isString())) { reportError("STRING comparison requires two STRING operands (line %d)", Bin->getLine()); return nullptr; } return StrHandler->emitCompare(Bin->getOp(), L, R); } break; default: break; } return ArithHandler->emitBinaryOp(Bin->getOp(), L, R, Bin->getLine()); } if (auto *Call = dynamic_cast(Expr)) { std::string Name = Call->getCallee(); if (const BuiltinInfo *B = findBuiltin(Name)) { if (Call->getArgs().size() != B->Arity) { reportError("%s expects %u arg%s", B->Name, B->Arity, B->Arity == 1 ? "" : "s"); return nullptr; } } if (Name == "LENGTH") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); if (!Str) return nullptr; return StrHandler->emitLength(Str); } if (Name == "MID") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); Value *Start = emitCoercedExpr(Call->getArgs()[1].get(), resolveType("INTEGER")); Value *Len = emitCoercedExpr(Call->getArgs()[2].get(), resolveType("INTEGER")); if (!Str || !Start || !Len) return nullptr; return StrHandler->emitMid(Str, Start, Len); } if (Name == "RIGHT") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); Value *Len = emitCoercedExpr(Call->getArgs()[1].get(), resolveType("INTEGER")); if (!Str || !Len) return nullptr; return StrHandler->emitRight(Str, Len); } if (Name == "LEFT") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); Value *Len = emitCoercedExpr(Call->getArgs()[1].get(), resolveType("INTEGER")); if (!Str || !Len) return nullptr; return StrHandler->emitLeft(Str, Len); } if (Name == "LCASE") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); if (!Str) return nullptr; return StrHandler->emitLCase(Str); } if (Name == "UCASE") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); if (!Str) return nullptr; return StrHandler->emitUCase(Str); } if (Name == "ASC") { const TypeInfo *ArgType = getExprTypeInfo(Call->getArgs()[0].get()); if (ArgType && ArgType->isUserKind()) { reportError("ASC expects a CHAR or STRING (got %s)", ArgType->Name.c_str()); return nullptr; } Value *ArgVal = emitExpr(Call->getArgs()[0].get()); if (!ArgVal) return nullptr; Value *CharVal = nullptr; if (ArgType && ArgType->isChar()) { CharVal = coerceValueToType(ArgVal, resolveType("CHAR")); } else { CharVal = Builder->CreateLoad(Type::getInt8Ty(*TheContext), ArgVal, "char_load"); } Value *AscVal = Builder->CreateZExt(CharVal, Type::getInt32Ty(*TheContext), "asc_val"); return coerceValueToType(AscVal, resolveType("INTEGER")); } if (Name == "CHR") { Value *IntVal = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("INTEGER")); if (!IntVal) return nullptr; Value *CharVal = Builder->CreateTrunc(IntVal, Type::getInt8Ty(*TheContext), "chr_val"); return coerceValueToType(CharVal, resolveType("STRING")); } if (Name == "IS_NUM") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); if (!Str) return nullptr; return StrConvHandler->emitIsNum(Str); } if (Name == "NUM_TO_STR") { const TypeInfo *ArgType = getExprTypeInfo(Call->getArgs()[0].get()); if (ArgType && ArgType->isUserKind()) { reportError("NUM_TO_STR expects a number (got %s)", ArgType->Name.c_str()); return nullptr; } Value *NumV = emitExpr(Call->getArgs()[0].get()); if (!NumV) return nullptr; bool IsReal = NumV->getType()->isDoubleTy(); if (NumV->getType()->isIntegerTy(8)) { NumV = coerceValueToType(NumV, resolveType("INTEGER")); } return StrConvHandler->emitNumToStr(NumV, IsReal); } if (Name == "STR_TO_NUM") { Value *Str = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); if (!Str) return nullptr; return StrConvHandler->emitStrToNum(Str, true); } if (Name == "EOF") { if (isWholeArrayVar(Call->getArgs()[0].get())) { reportError("Cannot use a whole array as the EOF filename"); return nullptr; } Value *FileName = emitCoercedExpr(Call->getArgs()[0].get(), resolveType("STRING")); if (!FileName) return nullptr; return Files->emitEof(FileName, Call->getLine()); } std::vector Args; if (!marshalCallArgs(Name, Call->getArgs(), Call->getLine(), Args)) return nullptr; return FuncGen->emitCallExpr(Call, Args); } if (auto *D = dynamic_cast(Expr)) { LValueInfo LV; if (!emitLValue(D, LV)) return nullptr; return loadFromLValue(LV); } // ^x produces a raw address; it is only admissible where a pointer type // is expected, which emitCoercedExpr enforces by pointee name. if (auto *Addr = dynamic_cast(Expr)) { LValueInfo LV; if (!emitLValue(Addr->getTarget(), LV)) return nullptr; return LV.Addr; } return nullptr; } // Collects the arguments for a call to a user FUNCTION/PROCEDURE, dispatching // on the *declared* signature: BYREF parameters take the variable's alloca, // BYVAL parameters are evaluated and coerced to the declared parameter type. bool CodeGen::marshalCallArgs(const std::string &Callee, const std::vector> &ArgExprs, int Line, std::vector &Out) { const FuncSig *Sig = FuncGen->getSignature(Callee); if (!Sig) { reportError("Call to undefined function %s", Callee.c_str()); return false; } if (ArgExprs.size() != Sig->Params.size()) { reportError("Incorrect # arguments passed to %s", Callee.c_str()); return false; } for (size_t i = 0; i < ArgExprs.size(); ++i) { const ParamSig &P = Sig->Params[i]; const std::string &TypeName = P.TypeName; bool IsByRef = P.IsByRef; ExprAST *ArgExpr = ArgExprs[i].get(); if (P.IsArray) { if (!Arrays->emitArrayArgument(ArgExpr, P.TypeName, P.Rank, P.DeclaredBounds, Callee, Line, *this, Out)) { return false; } continue; } // A whole-array argument against a scalar parameter is always a user // error (and would smash the data pointer if let through). if (auto *Var = dynamic_cast(ArgExpr)) { const SymbolInfo *Info = getSymbolInfo(Var->getName()); if (Info && Info->IsArray) { reportError("Cannot pass whole array %s to %s; the parameter is not an array (declare it ARRAY OF %s)", Var->getName().c_str(), Callee.c_str(), TypeName.c_str()); return false; } } if (IsByRef) { auto *Var = dynamic_cast(ArgExpr); if (!Var) { reportError("BYREF argument to %s must be a variable", Callee.c_str()); return false; } const SymbolInfo *Info = getSymbolInfo(Var->getName()); if (!Info) { reportError("Unknown variable %s in BYREF call", Var->getName().c_str()); return false; } // BYREF aliases the storage, so the types must match exactly. if (Info->TypeName != TypeName) { reportError("BYREF argument %s to %s must be of type %s (got %s)", Var->getName().c_str(), Callee.c_str(), TypeName.c_str(), Info->TypeName.c_str()); return false; } Out.push_back(Info->Storage); } else { Value *V = emitCoercedExpr(ArgExpr, resolveType(TypeName)); if (!V) return false; Out.push_back(V); } } return true; } void CodeGen::emitIfStmt(IfStmtAST *Stmt) { Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; Function *TheFunction = Builder->GetInsertBlock()->getParent(); BasicBlock *ThenBB = BasicBlock::Create(*TheContext, "then", TheFunction); BasicBlock *ElseBB = BasicBlock::Create(*TheContext, "else", TheFunction); BasicBlock *MergeBB = BasicBlock::Create(*TheContext, "ifcont", TheFunction); Builder->CreateCondBr(CondV, ThenBB, ElseBB); // Arrays are block-scoped: metadata SSA values and the buffer setup live // inside the branch, so a declaration must not survive it. auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(ThenBB); for (const auto &S : Stmt->getThenStmts()) emitStmt(S.get()); if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(MergeBB); Arrays->exchangeTable(SavedArrays); Builder->SetInsertPoint(ElseBB); for (const auto &S : Stmt->getElseStmts()) emitStmt(S.get()); if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(MergeBB); Arrays->exchangeTable(std::move(SavedArrays)); Builder->SetInsertPoint(MergeBB); } void CodeGen::emitWhileStmt(WhileStmtAST *Stmt) { Function *TheFunction = Builder->GetInsertBlock()->getParent(); BasicBlock *CondBB = BasicBlock::Create(*TheContext, "whilecond", TheFunction); BasicBlock *LoopBB = BasicBlock::Create(*TheContext, "whileloop", TheFunction); BasicBlock *AfterBB = BasicBlock::Create(*TheContext, "whilecont", TheFunction); Builder->CreateBr(CondBB); Builder->SetInsertPoint(CondBB); Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; Builder->CreateCondBr(CondV, LoopBB, AfterBB); // Arrays declared in the body are block-scoped (see emitIfStmt). auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(LoopBB); for (const auto &S : Stmt->getBody()) emitStmt(S.get()); Arrays->exchangeTable(std::move(SavedArrays)); if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(CondBB); Builder->SetInsertPoint(AfterBB); } void CodeGen::emitRepeatStmt(RepeatStmtAST *Stmt) { Function *TheFunction = Builder->GetInsertBlock()->getParent(); BasicBlock *LoopBB = BasicBlock::Create(*TheContext, "repeatloop", TheFunction); BasicBlock *CondBB = BasicBlock::Create(*TheContext, "repeatcond", TheFunction); BasicBlock *AfterBB = BasicBlock::Create(*TheContext, "repeatcont", TheFunction); Builder->CreateBr(LoopBB); // Arrays declared in the body are block-scoped (see emitIfStmt). auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(LoopBB); for (const auto &S : Stmt->getBody()) emitStmt(S.get()); Arrays->exchangeTable(std::move(SavedArrays)); if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(CondBB); Builder->SetInsertPoint(CondBB); Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; Builder->CreateCondBr(CondV, AfterBB, LoopBB); Builder->SetInsertPoint(AfterBB); } void CodeGen::emitForStmt(ForStmtAST *Stmt) { Function *TheFunction = Builder->GetInsertBlock()->getParent(); std::string VarName = Stmt->getVarName(); const SymbolInfo *Symbol = getSymbolInfo(VarName); if (!Symbol) { reportError("Unknown variable in FOR loop %s", VarName.c_str()); return; } if (Symbol->TypeName != "INTEGER") { reportError("FOR loop variable %s must be INTEGER", VarName.c_str()); return; } Value *StartVal = emitCoercedExpr(Stmt->getStart(), resolveType("INTEGER")); if (!StartVal) return; Value *Alloca = Symbol->Storage; Builder->CreateStore(StartVal, Alloca); // STEP is evaluated exactly once, before the loop. Value *StepVal = nullptr; if (Stmt->getStep()) { StepVal = emitCoercedExpr(Stmt->getStep(), resolveType("INTEGER")); if (!StepVal) return; } else { StepVal = ConstantInt::get(*TheContext, APInt(64, 1)); } BasicBlock *CondBB = BasicBlock::Create(*TheContext, "forcond", TheFunction); BasicBlock *LoopBB = BasicBlock::Create(*TheContext, "forloop", TheFunction); BasicBlock *IncBB = BasicBlock::Create(*TheContext, "forinc", TheFunction); BasicBlock *AfterBB = BasicBlock::Create(*TheContext, "forcont", TheFunction); Builder->CreateBr(CondBB); Builder->SetInsertPoint(CondBB); Value *CurVar = Builder->CreateLoad(Type::getInt64Ty(*TheContext), Alloca, VarName.c_str()); Value *EndVal = emitCoercedExpr(Stmt->getEnd(), resolveType("INTEGER")); if (!EndVal) return; // The loop direction depends on the STEP value at run time. Value *StepNonNeg = Builder->CreateICmpSGE(StepVal, ConstantInt::get(*TheContext, APInt(64, 0)), "step_nonneg"); Value *CondUp = Builder->CreateICmpSLE(CurVar, EndVal, "forcond_le"); Value *CondDown = Builder->CreateICmpSGE(CurVar, EndVal, "forcond_ge"); Value *CondV = Builder->CreateSelect(StepNonNeg, CondUp, CondDown, "forcond"); Builder->CreateCondBr(CondV, LoopBB, AfterBB); // Arrays declared in the body are block-scoped (see emitIfStmt). auto SavedArrays = Arrays->snapshotTable(); Builder->SetInsertPoint(LoopBB); for (const auto &S : Stmt->getBody()) emitStmt(S.get()); Arrays->exchangeTable(std::move(SavedArrays)); if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(IncBB); Builder->SetInsertPoint(IncBB); Value *CurValForInc = Builder->CreateLoad(Type::getInt64Ty(*TheContext), Alloca, VarName.c_str()); Value *NextVal = Builder->CreateAdd(CurValForInc, StepVal, "nextval"); Builder->CreateStore(NextVal, Alloca); Builder->CreateBr(CondBB); Builder->SetInsertPoint(AfterBB); } void CodeGen::emitStmt(StmtAST *Stmt) { if (!Stmt) return; if (auto *ArrDecl = dynamic_cast(Stmt)) { Arrays->emitArrayDeclare(ArrDecl, *this); return; } if (auto *ArrAssign = dynamic_cast(Stmt)) { Arrays->emitArrayAssign(ArrAssign, *this); return; } if (auto *FuncDef = dynamic_cast(Stmt)) { BasicBlock *SavedBlock = Builder->GetInsertBlock(); // Function bodies get a fresh array scope (metadata SSA values belong // to the emitting function), mirroring FunctionGen's Symbols handling. auto SavedArrays = Arrays->exchangeTable({}); FuncGen->emitFunctionDef(FuncDef, [this](StmtAST *S) { this->emitStmt(S); }); Arrays->exchangeTable(std::move(SavedArrays)); if (SavedBlock) Builder->SetInsertPoint(SavedBlock); return; } if (auto *Call = dynamic_cast(Stmt)) { std::vector Args; if (!marshalCallArgs(Call->getCallee(), Call->getArgs(), Call->getLine(), Args)) return; FuncGen->emitCallStmt(Call, Args); return; } if (auto *Ret = dynamic_cast(Stmt)) { Function *F = Builder->GetInsertBlock()->getParent(); const FuncSig *Sig = FuncGen->getSignature(F->getName().str()); // Pseudocode cannot name a function "main" (reserved runtime symbol), // so no signature means we are emitting the top level. if (!Sig) { reportError("RETURN is only allowed inside a FUNCTION or PROCEDURE"); return; } if (Sig->ReturnTypeName == "VOID" && Ret->getRetVal()) { reportError("RETURN with a value is only allowed in a FUNCTION"); return; } if (Sig->ReturnTypeName != "VOID" && !Ret->getRetVal()) { reportError("RETURN in a FUNCTION requires a value"); return; } Value *RetVal = nullptr; if (Ret->getRetVal()) { RetVal = emitCoercedExpr(Ret->getRetVal(), resolveType(Sig->ReturnTypeName)); if (!RetVal) return; } FuncGen->emitReturn(Ret, RetVal); return; } if (auto *Decl = dynamic_cast(Stmt)) { emitDeclareStmt(Decl); return; } if (auto *Assign = dynamic_cast(Stmt)) { const SymbolInfo *Info = getSymbolInfo(Assign->getName()); if (!Info) { if (Types->lookupEnumConstant(Assign->getName())) { reportError("Enum value %s is not an assignable location", Assign->getName().c_str()); } else { reportError("Unknown variable name %s", Assign->getName().c_str()); } return; } if (Info->IsArray) { reportError("Cannot assign array %s without indices", Assign->getName().c_str()); return; } Value *Val = emitCoercedExpr(Assign->getExpr(), resolveType(Info->TypeName)); if (!Val) return; Builder->CreateStore(Val, Info->Storage); return; } if (auto *In = dynamic_cast(Stmt)) { const SymbolInfo *Info = getSymbolInfo(In->getName()); if (!Info) { reportError("Unknown variable name %s", In->getName().c_str()); return; } if (Info->IsArray) { reportError("INPUT for entire array %s is not supported", In->getName().c_str()); return; } const TypeInfo *TypeInfo = resolveType(Info->TypeName); if (!TypeInfo) { reportError("Unknown type for INPUT %s", In->getName().c_str()); return; } std::vector Args; if (TypeInfo->isReal()) { Args.push_back(ScanfFloatFormatStr); Args.push_back(Info->Storage); Builder->CreateCall(ScanfFunc, Args); } else if (TypeInfo->isBoolean()) { AllocaInst *TempInt = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), "tmp_bool_input"); Args.push_back(ScanfFormatStr); Args.push_back(TempInt); Builder->CreateCall(ScanfFunc, Args); Value *Val = Builder->CreateLoad(Type::getInt64Ty(*TheContext), TempInt); Value *BoolVal = Builder->CreateICmpNE(Val, ConstantInt::get(*TheContext, APInt(64, 0)), "bool_cast"); Builder->CreateStore(BoolVal, Info->Storage); } else if (TypeInfo->isString()) { Value *Mem = Builder->CreateCall(MallocFunc, ConstantInt::get(*TheContext, APInt(64, 1024)), "input_str"); Args.push_back(ScanfStringFormatStr); Args.push_back(Mem); Builder->CreateCall(ScanfFunc, Args); Builder->CreateStore(Mem, Info->Storage); } else if (TypeInfo->isChar()) { Value *CharFmt = Builder->CreateGlobalStringPtr(" %c", "fmt_in_char", 0, TheModule.get()); Args.push_back(CharFmt); Args.push_back(Info->Storage); Builder->CreateCall(ScanfFunc, Args); } else if (TypeInfo->Kind == TypeKind::Integer) { Args.push_back(ScanfFormatStr); Args.push_back(Info->Storage); Builder->CreateCall(ScanfFunc, Args); } else { // Enum/Record/Pointer and any future kind: scanf'ing raw bytes // into typed storage would corrupt it — reject explicitly. reportError("INPUT is not supported for a value of type %s", TypeInfo->Name.c_str()); } return; } if (auto *Out = dynamic_cast(Stmt)) { const auto &Exprs = Out->getExprs(); for (size_t I = 0; I < Exprs.size(); ++I) { ExprAST *Expr = Exprs[I].get(); if (Arrays->tryEmitArrayOutput(Expr, *this)) continue; Value *Val = emitExpr(Expr); const TypeInfo *TypeInfo = getExprTypeInfo(Expr); if (!TypeInfo) { reportError("Cannot infer the type of the OUTPUT expression"); continue; } if (TypeInfo->isVoid()) { reportError("Cannot OUTPUT the result of a PROCEDURE call"); continue; } emitOutputValue(Val, TypeInfo, I + 1 == Exprs.size()); } return; } if (auto *Open = dynamic_cast(Stmt)) { if (isWholeArrayVar(Open->getFileName())) { reportError("Cannot use a whole array as the OPENFILE filename"); return; } Value *Name = emitCoercedExpr(Open->getFileName(), resolveType("STRING")); if (!Name) return; Files->emitOpen(Name, Open->getMode(), Open->getLine()); return; } if (auto *Read = dynamic_cast(Stmt)) { const SymbolInfo *Info = getSymbolInfo(Read->getVarName()); if (!Info) { reportError("Unknown variable name %s", Read->getVarName().c_str()); return; } if (Info->IsArray) { reportError("READFILE into entire array %s is not supported", Read->getVarName().c_str()); return; } if (Info->TypeName != "STRING") { reportError("READFILE variable %s must be of type STRING (got %s)", Read->getVarName().c_str(), Info->TypeName.c_str()); return; } if (isWholeArrayVar(Read->getFileName())) { reportError("Cannot use a whole array as the READFILE filename"); return; } Value *Name = emitCoercedExpr(Read->getFileName(), resolveType("STRING")); if (!Name) return; Value *LineStr = Files->emitRead(Name, Read->getLine()); if (!LineStr) return; Builder->CreateStore(LineStr, Info->Storage); return; } if (auto *Write = dynamic_cast(Stmt)) { if (isWholeArrayVar(Write->getFileName()) || isWholeArrayVar(Write->getData())) { reportError("Cannot use a whole array in WRITEFILE"); return; } Value *Name = emitCoercedExpr(Write->getFileName(), resolveType("STRING")); Value *Data = emitCoercedExpr(Write->getData(), resolveType("STRING")); if (!Name || !Data) return; Files->emitWrite(Name, Data, Write->getLine()); return; } if (auto *Close = dynamic_cast(Stmt)) { if (isWholeArrayVar(Close->getFileName())) { reportError("Cannot use a whole array as the CLOSEFILE filename"); return; } Value *Name = emitCoercedExpr(Close->getFileName(), resolveType("STRING")); if (!Name) return; Files->emitClose(Name, Close->getLine()); return; } if (auto *IfStmt = dynamic_cast(Stmt)) { emitIfStmt(IfStmt); return; } if (auto *WhileStmt = dynamic_cast(Stmt)) { emitWhileStmt(WhileStmt); return; } if (auto *RepeatStmt = dynamic_cast(Stmt)) { emitRepeatStmt(RepeatStmt); return; } if (auto *ForStmt = dynamic_cast(Stmt)) { emitForStmt(ForStmt); return; } // TYPE declarations are registered by compile()'s pre-pass (the parser // guarantees they are top-level); nothing is emitted here. if (dynamic_cast(Stmt) || dynamic_cast(Stmt) || dynamic_cast(Stmt)) { return; } if (auto *DA = dynamic_cast(Stmt)) { LValueInfo LV; if (!emitLValue(DA->getTarget(), LV)) return; Value *Val = emitCoercedExpr(DA->getExpr(), resolveType(LV.TypeName)); if (!Val) return; Builder->CreateStore(Val, LV.Addr); return; } }