aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-30 14:31:07 +0000
committer_ov4 <ov4@ov4.org>2026-10-30 14:31:07 +0000
commit0cf54b4f77ccf8376341950b02ceb7c1d1268ed7 (patch)
treeeebbba01063451c28f0a2e1cc048215f50beb98f /lib
parent4cb0d9b196008bc55dbfcdac1f9211dbbc879895 (diff)
refactor(strings): construct string values implicitly from raw literals
Keep double-quoted literals and text constants as C_STRING values. Use marked STRING constructors for text and CHAR conversion, preserving string operations and I/O through the existing class machinery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/CodeGen/CallGen.cc26
-rw-r--r--lib/CodeGen/ConstantGen.cc23
-rw-r--r--lib/CodeGen/ConstructorGen.cc8
-rw-r--r--lib/CodeGen/ControlFlowGen.cc13
-rw-r--r--lib/CodeGen/ExpressionGen.cc3
-rw-r--r--lib/CodeGen/OperatorGen.cc2
-rw-r--r--lib/CodeGen/OutputGen.cc13
-rw-r--r--lib/CodeGen/StringValueGen.cc43
-rw-r--r--lib/CodeGen/TypeCheckGen.cc25
-rw-r--r--lib/CodeGen/ValueTypeGen.cc3
10 files changed, 59 insertions, 100 deletions
diff --git a/lib/CodeGen/CallGen.cc b/lib/CodeGen/CallGen.cc
index f5f1eda..6c6a1a9 100644
--- a/lib/CodeGen/CallGen.cc
+++ b/lib/CodeGen/CallGen.cc
@@ -214,7 +214,7 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
int Count = 0;
// One layer of parameter matching. Lenient admits implicit constructors;
// FilterReturn keeps only candidates whose result is the wanted type.
- auto Scan = [&](bool Lenient, bool FilterReturn) -> const FuncSig * {
+ auto Scan = [&](int Layer, bool FilterReturn) -> const FuncSig * {
const FuncSig *First = nullptr;
std::vector<const FuncSig *> Matches;
Count = 0;
@@ -228,8 +228,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
bool SameRank = Actual[I].Rank == (P.IsArray ? P.Rank : 0);
bool Same = SameRank && Actual[I].Type && Actual[I].Type->sameType(Formal);
Match = Same ||
- (Lenient && SameRank && Actual[I].Type && !P.IsByRef && !P.IsArray &&
- canImplicitlyConvert(Actual[I].Type, Formal));
+ (Layer && SameRank && Actual[I].Type && !P.IsByRef && !P.IsArray &&
+ implicitConversionRank(Actual[I].Type, Formal) <= Layer);
}
if (!Match) continue;
ParamMatched = true;
@@ -254,9 +254,9 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
Name.c_str(), Key.c_str());
};
bool Ambiguous = false;
- bool AllowConversions = false;
+ int ConversionLayer = 0;
auto Try = [&](bool FilterReturn, const FuncSig *&Selected) {
- Selected = Scan(AllowConversions, FilterReturn);
+ Selected = Scan(ConversionLayer, FilterReturn);
if (Count > 1) {
ReportAmbiguous(FilterReturn);
Ambiguous = true;
@@ -264,8 +264,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
};
// Exhaust exact ordinary/template candidates before considering conversions.
- for (bool ConversionLayer : {false, true}) {
- AllowConversions = ConversionLayer;
+ for (int Layer : {0, 1, 2}) {
+ ConversionLayer = Layer;
const FuncSig *Selected = nullptr;
if (PreferResult) {
Try(true, Selected);
@@ -312,8 +312,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
if (Binding != Bindings.end()) {
if (Binding->second.empty()) Binding->second.push_back(Type->Name);
else if (!Type->sameType(resolveType(Binding->second.front())) &&
- !(AllowConversions && P.Mode == PassMode::ByVal && !P.IsArray &&
- canImplicitlyConvert(Type, resolveType(Binding->second.front())))) {
+ !(ConversionLayer && P.Mode == PassMode::ByVal && !P.IsArray &&
+ implicitConversionRank(Type, resolveType(Binding->second.front())) <= ConversionLayer)) {
Match = false; break;
}
}
@@ -328,8 +328,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
if (!Type || Type->isVoid() || Actual[I].Rank) { Match = false; break; }
if (Index < Pack.size()) {
if (!Type->sameType(resolveType(Pack[Index])) &&
- !(AllowConversions && Params.back().Mode == PassMode::ByVal &&
- canImplicitlyConvert(Type, resolveType(Pack[Index])))) Match = false;
+ !(ConversionLayer && Params.back().Mode == PassMode::ByVal &&
+ implicitConversionRank(Type, resolveType(Pack[Index])) <= ConversionLayer)) Match = false;
} else Pack.push_back(Type->Name);
}
}
@@ -345,8 +345,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
std::string Expected = Binding != Bindings.end() ? Binding->second.front()
: TypeName.find('<') != std::string::npos ? Substitute.type(TypeName) : TypeName;
if (!Match || (!Actual[I].Type->sameType(resolveType(Expected)) &&
- !(AllowConversions && Params[I].Mode == PassMode::ByVal && !Params[I].IsArray &&
- canImplicitlyConvert(Actual[I].Type, resolveType(Expected))))) {
+ !(ConversionLayer && Params[I].Mode == PassMode::ByVal && !Params[I].IsArray &&
+ implicitConversionRank(Actual[I].Type, resolveType(Expected)) <= ConversionLayer))) {
Match = false; break;
}
}
diff --git a/lib/CodeGen/ConstantGen.cc b/lib/CodeGen/ConstantGen.cc
index a512490..66919d2 100644
--- a/lib/CodeGen/ConstantGen.cc
+++ b/lib/CodeGen/ConstantGen.cc
@@ -64,31 +64,8 @@ void CodeGen::emitConstantStmt(ConstantStmtAST *Stmt) {
return;
}
if (!prepareSymbolDeclaration(Name, true)) return;
- auto Existing = StringConstants.find(Stmt);
- if (Existing != StringConstants.end()) {
- Symbols[Name] = Existing->second;
- return;
- }
const TypeInfo *Type = getExprTypeInfo(Stmt->getValue());
Value *Val = emitExpr(Stmt->getValue());
- if (Type && Type->isString() && Val) {
- Value *Storage;
- bool TopLevel = ConstantScopes.back().ID == 0;
- if (TopLevel) {
- Storage = new GlobalVariable(*TheModule, Type->LLVMType, false,
- GlobalValue::InternalLinkage, Types->getZeroValue(Type->Name), "__cps_constant." + Name);
- } else {
- Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), Type->LLVMType, Name);
- }
- size_t Lifetime = registerStackObject(Storage, Type->Name, DiagnosticLine);
- Builder->CreateStore(Val, Storage);
- activateStackObject(Lifetime);
- SymbolInfo Info{Storage, Type->Name, false, nullptr, ConstantScopes.back().ID};
- Info.ReadOnly = true;
- Symbols[Name] = Info;
- if (TopLevel) StringConstants.emplace(Stmt, Info);
- return;
- }
auto *Literal = dyn_cast_or_null<Constant>(Val);
if (!Type || !Literal) {
reportError("CONSTANT initializer must be a literal");
diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc
index 81c448f..585249c 100644
--- a/lib/CodeGen/ConstructorGen.cc
+++ b/lib/CodeGen/ConstructorGen.cc
@@ -22,11 +22,9 @@ Value *CodeGen::emitOwnedValue(ExprAST *Expr, const TypeInfo *Type,
expectResult(Expr, Type);
if (!Type || (!Type->IsClass && !containsString(Type))) return emitCoercedExpr(Expr, Type);
const TypeInfo *Source = getExprTypeInfo(Expr);
- // A CHAR argument becomes a fresh STRING temporary, owned like any other.
- bool FromChar = Source && Type->isString() && Source->Kind == TypeKind::Char;
if (!Source) return emitCoercedExpr(Expr, Type);
Value *Value = nullptr;
- if (!FromChar && Source->sameType(Type) && isObjectLValue(Expr)) {
+ if (Source->sameType(Type) && isObjectLValue(Expr)) {
LValueInfo LV;
const auto *Var = dynamic_cast<VariableExprAST *>(Expr);
const auto *Symbol = Var ? getSymbolInfo(Var->getName()) : nullptr;
@@ -177,9 +175,7 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb
return;
}
const TypeInfo *ExprType = getExprTypeInfo(Expr);
- // A CHAR assigned to a STRING is converted first, not bound to OPERATOR<-.
- bool CharToString = Info && Info->isString() && ExprType && ExprType->Kind == TypeKind::Char;
- if (Info && Info->IsClass && !CharToString) {
+ if (Info && Info->IsClass) {
auto Resolution = resolveOperator(tok_assign, Info, ExprType);
if (Resolution.Ambiguous) {
reportError("Ambiguous assignment operator");
diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc
index fe9a954..3d16098 100644
--- a/lib/CodeGen/ControlFlowGen.cc
+++ b/lib/CodeGen/ControlFlowGen.cc
@@ -216,18 +216,7 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) {
Value *LabelValue = emitExpr(Label);
Value *LeftValue = Selected;
const TypeInfo *LeftType = SelectorType;
- bool LabelConverted = false;
- // CHAR and STRING compare as STRING.
- if (SelectorType->isString() && LabelType->Kind == TypeKind::Char) {
- LabelValue = emitStringFromChar(LabelValue);
- LabelType = SelectorType;
- LabelConverted = true;
- } else if (SelectorType->Kind == TypeKind::Char && LabelType->isString()) {
- LeftValue = emitStringFromChar(Selected);
- if (LeftValue) materializeOperatorReceiver(LeftValue, LabelType);
- LeftType = LabelType;
- }
- if (containsString(LabelType) && (LabelConverted || !isObjectLValue(Label)))
+ if (containsString(LabelType) && !isObjectLValue(Label))
materializeOperatorReceiver(LabelValue, LabelType);
ResultContext BoolContext{resolveType("BOOLEAN"), true};
auto Overload = resolveOperator(Op, LeftType, LabelType, &BoolContext);
diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc
index 463dbed..061bcd2 100644
--- a/lib/CodeGen/ExpressionGen.cc
+++ b/lib/CodeGen/ExpressionGen.cc
@@ -33,8 +33,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
}
if (auto *Str = dynamic_cast<StringExprAST*>(Expr)) {
- Value *Text = Builder->CreateGlobalString(Str->getVal());
- return Str->isCString() ? Text : emitStringFromCString(Text);
+ return Builder->CreateGlobalString(Str->getVal());
}
if (auto *Var = dynamic_cast<VariableExprAST*>(Expr)) {
diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc
index 15667ea..2e0ee3a 100644
--- a/lib/CodeGen/OperatorGen.cc
+++ b/lib/CodeGen/OperatorGen.cc
@@ -22,7 +22,7 @@ CodeGen::OperatorResolution CodeGen::resolveOperator(
// Keep builtin CHAR comparisons ahead of STRING construction.
if (Right && Left->Kind == TypeKind::Char && Right->Kind == TypeKind::Char &&
Op != '&' && Formal && Formal->isString()) return false;
- return Lenient && !Param.IsByRef && !Param.IsArray && canImplicitlyConvert(Actual, Formal);
+ return Lenient && !Param.IsByRef && !Param.IsArray && implicitConversionRank(Actual, Formal) == 1;
};
// Builtin operations on exact operand types precede converted overloads.
// Raw C text is the exception: visible STRING operators supply text semantics.
diff --git a/lib/CodeGen/OutputGen.cc b/lib/CodeGen/OutputGen.cc
index faf21df..cecf0b7 100644
--- a/lib/CodeGen/OutputGen.cc
+++ b/lib/CodeGen/OutputGen.cc
@@ -105,6 +105,19 @@ Value *CodeGen::emitStringNullGuard(Value *StrVal) {
void CodeGen::emitOutputValue(Value *Val, const TypeInfo *Info, bool AppendNewline) {
if (!Val || !Info) return;
+ if (Info->CIdentity == "char*") {
+ const TypeInfo *String = requireStringType();
+ const FuncSig *Constructor = findImplicitConstructor(Info, String);
+ if (!Constructor) {
+ if (String) reportError("STRING requires an IMPLICIT constructor NEW(Text : C_STRING)");
+ return;
+ }
+ Value *Converted = emitImplicitConstruct(*Constructor, String, Val);
+ if (!Converted) return;
+ materializeOperatorReceiver(Converted, String);
+ emitOutputValue(Converted, String, AppendNewline);
+ return;
+ }
if (Info->isString()) {
Value *Text = emitStringCString(Val);
if (Text) Builder->CreateCall(PrintfFunc, {
diff --git a/lib/CodeGen/StringValueGen.cc b/lib/CodeGen/StringValueGen.cc
index 989bac8..7a42e49 100644
--- a/lib/CodeGen/StringValueGen.cc
+++ b/lib/CodeGen/StringValueGen.cc
@@ -35,38 +35,15 @@ Value *CodeGen::emitStringFromCString(Value *Text) {
return Builder->CreateLoad(Type->LLVMType, Storage, "string");
}
-// CHAR -> STRING: STRING.NEW(1, ch).
-Value *CodeGen::emitStringFromChar(Value *Ch) {
- const TypeInfo *Type = requireStringType();
- if (!Type || !Ch) return nullptr;
- const TypeInfo *CChar = resolveType("C_CHAR");
- const FuncSig *Constructor = nullptr;
- for (const FuncSig *Sig : FuncGen->getOverloads(Type->Name + ".NEW"))
- if (Sig->Params.size() == 2 && !Sig->Params[1].IsArray && !Sig->Params[1].IsByRef &&
- CChar->sameType(resolveType(Sig->Params[1].TypeName)))
- Constructor = Sig;
- if (!Constructor) {
- reportError("STRING requires a constructor NEW(Count : INTEGER, Ch : C_CHAR)");
- return nullptr;
- }
- if (!checkOperatorAccess(*Constructor)) return nullptr;
- auto *Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(),
- Type->LLVMType, "string_value");
- Builder->CreateStore(Types->getZeroValue(Type->Name), Storage);
- if (!emitFieldDefaults(Type, Storage, DiagnosticLine)) return nullptr;
- Value *Count = ConstantInt::get(resolveType("INTEGER")->LLVMType, 1);
- if (!FuncGen->emitCall(*Constructor, {Storage, Count, Ch})) return nullptr;
- // The consumer takes ownership, just as with an ordinary class result.
- return Builder->CreateLoad(Type->LLVMType, Storage, "string");
-}
-
// STRING -> CHAR: the string must hold exactly one character.
Value *CodeGen::emitCharFromString(ExprAST *Expr, int Line) {
const TypeInfo *Type = requireStringType();
if (!Type) return nullptr;
- Value *Text = emitExpr(Expr);
+ const TypeInfo *Source = getExprTypeInfo(Expr);
+ Value *Text = emitCoercedExpr(Expr, Type);
if (!Text) return nullptr;
- if (!isObjectLValue(Expr)) materializeOperatorReceiver(Text, Type);
+ if (!isObjectLValue(Expr) || !Source || !Source->sameType(Type))
+ materializeOperatorReceiver(Text, Type);
auto *Receiver = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(),
Type->LLVMType, "string_view");
Builder->CreateStore(Text, Receiver);
@@ -101,14 +78,16 @@ Value *CodeGen::emitStringCString(Value *Value) {
}
Value *CodeGen::emitCStringExpr(ExprAST *Expr) {
- const TypeInfo *Type = getExprTypeInfo(Expr);
- if (!Type || !Type->isString() || isWholeArrayVar(Expr)) {
- reportError("Expected a STRING value");
+ const TypeInfo *Source = getExprTypeInfo(Expr);
+ const TypeInfo *Type = requireStringType();
+ if (!Type || !Source || isWholeArrayVar(Expr) ||
+ (!Source->sameType(Type) && !findImplicitConstructor(Source, Type))) {
+ reportError("Expected a STRING value or an IMPLICIT conversion to STRING");
return nullptr;
}
- Value *Value = emitExpr(Expr);
+ Value *Value = emitCoercedExpr(Expr, Type);
if (!Value) return nullptr;
- if (!isObjectLValue(Expr)) materializeOperatorReceiver(Value, Type);
+ if (!isObjectLValue(Expr) || !Source->sameType(Type)) materializeOperatorReceiver(Value, Type);
return emitStringCString(Value);
}
diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc
index b730e93..f253e55 100644
--- a/lib/CodeGen/TypeCheckGen.cc
+++ b/lib/CodeGen/TypeCheckGen.cc
@@ -16,12 +16,19 @@ const FuncSig *CodeGen::findImplicitConstructor(const TypeInfo *Source, const Ty
return nullptr;
}
-bool CodeGen::canImplicitlyConvert(const TypeInfo *Source, const TypeInfo *Target) {
- if (!Source || !Target) return false;
- if (Source->sameType(Target)) return true;
- if ((Source->Kind == TypeKind::Char && Target->isString()) ||
- (Source->isString() && Target->Kind == TypeKind::Char)) return true;
- return findImplicitConstructor(Source, Target) != nullptr;
+// 0: exact; 1: one conversion; 2: legacy raw text -> STRING -> CHAR;
+// 3: unavailable. The compatibility bridge must not compete with STRING
+// parameters in LCASE, UCASE, ASC and STR_TO_NUM.
+int CodeGen::implicitConversionRank(const TypeInfo *Source, const TypeInfo *Target) {
+ if (!Source || !Target) return 3;
+ if (Source->sameType(Target)) return 0;
+ if (findImplicitConstructor(Source, Target)) return 1;
+ if (Target->Kind == TypeKind::Char) {
+ if (Source->isString()) return 1;
+ if (Source->CIdentity == "char*" &&
+ findImplicitConstructor(Source, resolveType("STRING"))) return 2;
+ }
+ return 3;
}
Value *CodeGen::emitImplicitConstruct(const FuncSig &Constructor, const TypeInfo *Target,
@@ -63,9 +70,9 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) {
// CHAR and STRING convert into one another; a STRING that is not exactly
// one character long is a runtime error.
- if (Src && Src->Kind == TypeKind::Char && Target->isString())
- return emitStringFromChar(emitExpr(E));
- if (Src && Src->isString() && Target->Kind == TypeKind::Char)
+ if (Src && Target->Kind == TypeKind::Char &&
+ (Src->isString() || (Src->CIdentity == "char*" &&
+ findImplicitConstructor(Src, resolveType("STRING")))))
return emitCharFromString(E, E->getLine() ? E->getLine() : DiagnosticLine);
if (const FuncSig *Constructor = findImplicitConstructor(Src, Target)) {
diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc
index 6026821..85dcf13 100644
--- a/lib/CodeGen/ValueTypeGen.cc
+++ b/lib/CodeGen/ValueTypeGen.cc
@@ -28,8 +28,7 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) {
if (dynamic_cast<RealExprAST*>(Expr)) return resolveType("REAL");
if (dynamic_cast<BooleanExprAST*>(Expr)) return resolveType("BOOLEAN");
if (dynamic_cast<CharExprAST*>(Expr)) return resolveType("CHAR");
- if (auto *String = dynamic_cast<StringExprAST*>(Expr))
- return String->isCString() ? resolveType("C_STRING") : requireStringType();
+ if (dynamic_cast<StringExprAST*>(Expr)) return resolveType("C_STRING");
if (auto *Cast = dynamic_cast<CastExprAST*>(Expr))
return resolveType(Cast->getTypeName());