feat(rtl): WriteLn prints True/False for Boolean values

Add _SysWriteBool to the runtime platform layer so WriteLn(Boolean)
prints 'True' or 'False' instead of '1' or '0', matching Delphi/FPC
behaviour. Both QBE and native x86_64 backends emit the new call for
tyBoolean arguments. Updated ~65 assertions across 13 test files.
This commit is contained in:
Graeme Geldenhuys 2026-06-05 18:08:40 +01:00
parent 0c5910195c
commit d69fcacaca
19 changed files with 184 additions and 85 deletions

View file

@ -2753,8 +2753,9 @@ begin
UInt32 / Word (unsigned-32) -> _SysWriteUInt64, zero-extended, so a
value above 2^31 prints as a large
positive number, not a signed wrap
Boolean -> _SysWriteBool (prints True/False)
everything else (Integer,
Byte, Boolean, Enum, ...) -> _SysWriteInt (32-bit signed; their
Byte, Enum, ...) -> _SysWriteInt (32-bit signed; their
value range is non-negative there)
The value is already in %rax 64-bit-extended (unsigned narrow loads
zero-extend), so the unsigned-32 path needs no extra extension.
@ -2785,6 +2786,13 @@ begin
Self.Emit(#9'movl $1, %edi');
Self.Emit(#9'callq _SysWriteSingle');
end
else if K = tyBoolean then
begin
Self.EmitExprToEax(ArgExpr);
Self.Emit(#9'movl %eax, %esi');
Self.Emit(#9'movl $1, %edi');
Self.Emit(#9'callq _SysWriteBool');
end
else
begin
Self.EmitExprToEax(ArgExpr); { value -> %rax (64-bit-extended) }

View file

@ -10,7 +10,7 @@ unit uCodeGenQBE;
{ QBE IR emitter for Blaise.
WriteLn/Write are built-ins emitted as calls to _SysWriteStr/_SysWriteInt/
_SysWriteInt64/_SysWriteNewline (blaise_sys.pas / blaise_sys.c).
_SysWriteInt64/_SysWriteBool/_SysWriteNewline (rtl.platform.posix.pas).
Records are stack-allocated; field access uses pointer arithmetic. }
interface
@ -7021,6 +7021,9 @@ begin
ArgTemp := EmitExpr(ArgExpr);
if IsString then
EmitLine(Format(' call $_SysWriteStr(w %s, l %s)', [FdLit, ArgTemp]))
else if (ArgExpr.ResolvedType <> nil) and
(ArgExpr.ResolvedType.Kind = tyBoolean) then
EmitLine(Format(' call $_SysWriteBool(w %s, w %s)', [FdLit, ArgTemp]))
else if (ArgExpr.ResolvedType <> nil) and
(ArgExpr.ResolvedType.Kind = tyDouble) then
EmitLine(Format(' call $_SysWriteDouble(w %s, d %s)', [FdLit, ArgTemp]))

View file

@ -565,7 +565,7 @@ var Output: string;
begin
Output := CompileAndRun(Src);
if Output = '<toolchain-missing>' then begin Ignore('toolchain unavailable'); Exit end;
AssertEquals('stdout', '1' + #10, Output);
AssertEquals('stdout', 'True' + #10, Output);
end;
procedure TCustomAttributeTests.TestE2E_HasClassAttribute_False;
@ -584,7 +584,7 @@ var Output: string;
begin
Output := CompileAndRun(Src);
if Output = '<toolchain-missing>' then begin Ignore('toolchain unavailable'); Exit end;
AssertEquals('stdout', '0' + #10, Output);
AssertEquals('stdout', 'False' + #10, Output);
end;
procedure TCustomAttributeTests.TestE2E_HasClassAttribute_InheritedFromParent;
@ -606,7 +606,7 @@ var Output: string;
begin
Output := CompileAndRun(Src);
if Output = '<toolchain-missing>' then begin Ignore('toolchain unavailable'); Exit end;
AssertEquals('stdout', '1' + #10 + '1' + #10, Output);
AssertEquals('stdout', 'True' + #10 + 'True' + #10, Output);
end;
procedure TCustomAttributeTests.TestE2E_HasClassAttribute_MultipleAttributes;
@ -629,7 +629,7 @@ var Output: string;
begin
Output := CompileAndRun(Src);
if Output = '<toolchain-missing>' then begin Ignore('toolchain unavailable'); Exit end;
AssertEquals('stdout', '1' + #10 + '1' + #10, Output);
AssertEquals('stdout', 'True' + #10 + 'True' + #10, Output);
end;
initialization

View file

@ -32,6 +32,7 @@ type
procedure TestWriteLn_NoArgs_CallsSysWriteNewline;
procedure TestWriteLn_StringLit_CallsSysWriteStr;
procedure TestWriteLn_IntExpr_CallsSysWriteInt;
procedure TestWriteLn_BoolExpr_CallsSysWriteBool;
{ Variables and assignment }
procedure TestIntVar_HasAlloc;
@ -194,6 +195,15 @@ begin
IRContains(IR, 'call $_SysWriteNewline(w 1)'));
end;
procedure TCodeGenTests.TestWriteLn_BoolExpr_CallsSysWriteBool;
var
IR: string;
begin
IR := GenerateIR('program P; var b: Boolean; begin b := True; WriteLn(b) end.');
AssertTrue('Calls _SysWriteBool',
IRContains(IR, 'call $_SysWriteBool(w 1,'));
end;
{ Variables and assignment }
procedure TCodeGenTests.TestIntVar_HasAlloc;

View file

@ -849,16 +849,16 @@ begin
'--- walk ---' + LE +
' value=40' + LE +
' tag=0' + LE +
' marked=0' + LE +
' marked=False' + LE +
' value=30' + LE +
' tag=1' + LE +
' marked=1' + LE +
' marked=True' + LE +
' value=20' + LE +
' tag=0' + LE +
' marked=0' + LE +
' marked=False' + LE +
' value=10' + LE +
' tag=0' + LE +
' marked=0' + LE +
' marked=False' + LE +
'pop=40' + LE +
'pop=30' + LE +
'count_after_pops=2' + LE;
@ -917,10 +917,10 @@ begin
'list[1]_after_delete=30' + LE +
'dict.count=4' + LE +
'beta=2' + LE +
'has_gamma=1' + LE +
'has_gamma=True' + LE +
'beta_after_update=99' + LE +
'count_after_remove=3' + LE +
'has_alpha_after_remove=0' + LE;
'has_alpha_after_remove=False' + LE;
AssertEquals('milestone stdout', Expected, Output);
end;
@ -1194,7 +1194,7 @@ begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertTrue('compile+run', CompileAndRun(SrcVarParamClassFields, Output, RCode));
AssertEquals('exit code 0', 0, RCode);
AssertEquals('var-param writeback visible through class field', '4096' + LE + '1' + LE, Output);
AssertEquals('var-param writeback visible through class field', '4096' + LE + 'True' + LE, Output);
end;
procedure TE2EClasses2Tests.TestRun_LocalVar_ShadowsOwnMethod;

View file

@ -598,7 +598,7 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRunWithRTL(SrcInstantNow, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertEquals('InstantNow > 2020 epoch', '1', Trim(Output));
AssertEquals('InstantNow > 2020 epoch', 'True', Trim(Output));
end;
procedure TE2EDateUtilsTests.TestRun_MakeDate_ToString;
@ -789,9 +789,9 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('A.IsBefore(B)=true', '1', Lines.Strings[0]);
AssertEquals('A.IsAfter(B)=false', '0', Lines.Strings[1]);
AssertEquals('B.IsAfter(A)=true', '1', Lines.Strings[2]);
AssertEquals('A.IsBefore(B)=true', 'True', Lines.Strings[0]);
AssertEquals('A.IsAfter(B)=false', 'False', Lines.Strings[1]);
AssertEquals('B.IsAfter(A)=true', 'True', Lines.Strings[2]);
finally
Lines.Free
end
@ -809,8 +809,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('A.Equals(B)=true', '1', Lines.Strings[0]);
AssertEquals('A.Equals(C)=false', '0', Lines.Strings[1]);
AssertEquals('A.Equals(B)=true', 'True', Lines.Strings[0]);
AssertEquals('A.Equals(C)=false', 'False', Lines.Strings[1]);
finally
Lines.Free
end
@ -909,8 +909,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('IsNegative=true', '1', Lines.Strings[0]);
AssertEquals('Abs.IsNegative=false', '0', Lines.Strings[1]);
AssertEquals('IsNegative=true', 'True', Lines.Strings[0]);
AssertEquals('Abs.IsNegative=false', 'False', Lines.Strings[1]);
AssertEquals('Abs.TotalSeconds=120', '120', Lines.Strings[2]);
finally
Lines.Free
@ -1035,7 +1035,7 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRunWithRTL(SrcParseInstantOffset, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertEquals('offset == UTC equivalent', '1', Trim(Output));
AssertEquals('offset == UTC equivalent', 'True', Trim(Output));
end;
procedure TE2EDateUtilsTests.TestRun_ParseDuration;
@ -1091,10 +1091,10 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('2000 is leap', '1', Lines.Strings[0]);
AssertEquals('1900 not leap', '0', Lines.Strings[1]);
AssertEquals('2024 is leap', '1', Lines.Strings[2]);
AssertEquals('2025 not leap', '0', Lines.Strings[3]);
AssertEquals('2000 is leap', 'True', Lines.Strings[0]);
AssertEquals('1900 not leap', 'False', Lines.Strings[1]);
AssertEquals('2024 is leap', 'True', Lines.Strings[2]);
AssertEquals('2025 not leap', 'False', Lines.Strings[3]);
finally
Lines.Free
end
@ -1230,8 +1230,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('>= -50400s', '1', Lines.Strings[0]);
AssertEquals('<= +50400s', '1', Lines.Strings[1]);
AssertEquals('>= -50400s', 'True', Lines.Strings[0]);
AssertEquals('<= +50400s', 'True', Lines.Strings[1]);
finally
Lines.Free
end

View file

@ -342,10 +342,10 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcDictAddContains, Output, RCode));
AssertEquals('exit 0', 0, RCode);
{ ContainsKey(10) -> true (1), ContainsKey(99) -> false (0), Count=2 }
AssertTrue('contains 10 -> 1', Pos('1', Output) >= 0);
AssertTrue('contains 99 -> 0', Pos('0', Output) >= 0);
AssertTrue('count=2', Pos('2', Output) >= 0);
{ ContainsKey(10) -> true, ContainsKey(99) -> false, Count=2 }
AssertTrue('contains 10 -> True', Pos('True', Output) >= 0);
AssertTrue('contains 99 -> False', Pos('False', Output) >= 0);
AssertTrue('count=2', Pos('2', Output) >= 0);
end;
procedure TE2EIMapTests.TestRun_IMap_TDictionary_TryGetValue;
@ -356,9 +356,9 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcDictTryGet, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertTrue('found -> 1', Pos('1', Output) >= 0);
AssertTrue('value=99', Pos('99', Output) >= 0);
AssertTrue('missing -> 0', Pos('0', Output) >= 0);
AssertTrue('found -> True', Pos('True', Output) >= 0);
AssertTrue('value=99', Pos('99', Output) >= 0);
AssertTrue('missing -> False', Pos('False', Output) >= 0);
end;
procedure TE2EIMapTests.TestRun_IMap_TDictionary_Remove;
@ -369,9 +369,9 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcDictRemove, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertTrue('count=2', Pos('2', Output) >= 0);
AssertTrue('removed key gone', Pos('0', Output) >= 0);
AssertTrue('other key stays', Pos('1', Output) >= 0);
AssertTrue('count=2', Pos('2', Output) >= 0);
AssertTrue('removed key gone', Pos('False', Output) >= 0);
AssertTrue('other key stays', Pos('True', Output) >= 0);
end;
procedure TE2EIMapTests.TestRun_IMap_TOrderedDictionary_AddAndContainsKey;
@ -382,9 +382,9 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcOrdDictAddContains, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertTrue('contains 10 -> 1', Pos('1', Output) >= 0);
AssertTrue('contains 99 -> 0', Pos('0', Output) >= 0);
AssertTrue('count=2', Pos('2', Output) >= 0);
AssertTrue('contains 10 -> True', Pos('True', Output) >= 0);
AssertTrue('contains 99 -> False', Pos('False', Output) >= 0);
AssertTrue('count=2', Pos('2', Output) >= 0);
end;
procedure TE2EIMapTests.TestRun_IMap_TOrderedDictionary_TryGetValue;
@ -395,9 +395,9 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcOrdDictTryGet, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertTrue('found -> 1', Pos('1', Output) >= 0);
AssertTrue('value=99', Pos('99', Output) >= 0);
AssertTrue('missing -> 0', Pos('0', Output) >= 0);
AssertTrue('found -> True', Pos('True', Output) >= 0);
AssertTrue('value=99', Pos('99', Output) >= 0);
AssertTrue('missing -> False', Pos('False', Output) >= 0);
end;
procedure TE2EIMapTests.TestRun_IMap_SwapImplementation_SameCallSite;
@ -408,8 +408,8 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcSwapImpl, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertTrue('TDict dispatch correct', Pos('1', Output) >= 0);
AssertTrue('TOrdDict dispatch correct', Pos('1', Output) >= 0);
AssertTrue('TDict dispatch correct', Pos('True', Output) >= 0);
AssertTrue('TOrdDict dispatch correct', Pos('True', Output) >= 0);
end;
{ Regression: a generic class implementing a generic interface

View file

@ -25,6 +25,8 @@ type
published
{ Boolean, WriteLn, break/exit }
procedure TestRun_BooleanOps_AllExpressions;
procedure TestRun_WriteLn_BoolVar_PrintsTrueOrFalse;
procedure TestRun_WriteLn_BoolExpr_PrintsTrueOrFalse;
procedure TestRun_MultiArgWriteLn_PrintsAllArgs;
procedure TestRun_ForBreak_StopsAtFiveHalt;
procedure TestRun_ExitFromFunction_ReturnsImmediately;
@ -102,6 +104,25 @@ const
end.
''';
SrcWriteLnBoolVar = '''
program P;
var B: Boolean;
begin
B := True;
WriteLn(B);
B := False;
WriteLn(B)
end.
''';
SrcWriteLnBoolExpr = '''
program P;
begin
WriteLn(3 > 2);
WriteLn(1 = 2)
end.
''';
SrcMultiArg = '''
program P;
var I, J, K: Integer;
@ -522,6 +543,18 @@ begin
't1' + LE + 't2' + LE + 't3' + LE, Output);
end;
procedure TE2EMiscTests.TestRun_WriteLn_BoolVar_PrintsTrueOrFalse;
begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(SrcWriteLnBoolVar, 'True' + LE + 'False' + LE, 0);
end;
procedure TE2EMiscTests.TestRun_WriteLn_BoolExpr_PrintsTrueOrFalse;
begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(SrcWriteLnBoolExpr, 'True' + LE + 'False' + LE, 0);
end;
procedure TE2EMiscTests.TestRun_MultiArgWriteLn_PrintsAllArgs;
var Output: string; RCode: Integer;
begin

View file

@ -1536,7 +1536,7 @@ const
procedure TE2ENativeTests.TestRun_Native_String_SameText;
begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(SrcStrSameText, '1' + LE, 0);
AssertRunsOnBoth(SrcStrSameText, 'True' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_String_Format_IntArg;

View file

@ -320,7 +320,7 @@ var Output: string; RCode: Integer;
begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertTrue('compile+run', CompileAndRun(SrcStringSameText, Output, RCode));
AssertEquals('SameText(''Hello'', ''hello'') = True (1)', '1', Trim(Output));
AssertEquals('SameText(''Hello'', ''hello'') = True', 'True', Trim(Output));
end;
procedure TE2EStringOpsTests.TestRun_StringOps_IntToStr;

View file

@ -292,8 +292,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('existing file = 1', '1', Lines.Strings[0]);
AssertEquals('missing file = 0', '0', Lines.Strings[1]);
AssertEquals('existing file = True', 'True', Lines.Strings[0]);
AssertEquals('missing file = False', 'False', Lines.Strings[1]);
finally
Lines.Free;
end;
@ -401,8 +401,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('/tmp exists = 1', '1', Lines.Strings[0]);
AssertEquals('missing dir = 0', '0', Lines.Strings[1]);
AssertEquals('/tmp exists = True', 'True', Lines.Strings[0]);
AssertEquals('missing dir = False', 'False', Lines.Strings[1]);
finally
Lines.Free;
end;
@ -436,8 +436,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('ForceDirectories returned 1', '1', Lines.Strings[0]);
AssertEquals('DirectoryExists returned 1', '1', Lines.Strings[1]);
AssertEquals('ForceDirectories returned True', 'True', Lines.Strings[0]);
AssertEquals('DirectoryExists returned True', 'True', Lines.Strings[1]);
finally
Lines.Free;
end;
@ -568,9 +568,9 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('RenameFile returns 1', '1', Lines.Strings[0]);
AssertEquals('old name gone (0)', '0', Lines.Strings[1]);
AssertEquals('new name exists (1)', '1', Lines.Strings[2]);
AssertEquals('RenameFile returns True', 'True', Lines.Strings[0]);
AssertEquals('old name gone (False)', 'False', Lines.Strings[1]);
AssertEquals('new name exists (True)', 'True', Lines.Strings[2]);
finally
Lines.Free;
end;
@ -586,7 +586,7 @@ begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertTrue('compile+run', CompileAndRun(SrcRenameFileMissing, Output, RCode));
AssertEquals('exit code 0', 0, RCode);
AssertEquals('RenameFile missing = 0', '0', Trim(Output));
AssertEquals('RenameFile missing = False', 'False', Trim(Output));
end;
procedure TE2ESysUtilsTests.TestRun_SetCurrentDir_ChangesDir;
@ -601,8 +601,8 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('SetCurrentDir /tmp returns 1', '1', Lines.Strings[0]);
AssertEquals('GetCurrentDir non-empty', '1', Lines.Strings[1]);
AssertEquals('SetCurrentDir /tmp returns True', 'True', Lines.Strings[0]);
AssertEquals('GetCurrentDir non-empty', 'True', Lines.Strings[1]);
finally
Lines.Free;
end;
@ -659,9 +659,9 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('LineEnding = #10', '1', Lines.Strings[0]);
AssertEquals('DirectorySeparator = /', '1', Lines.Strings[1]);
AssertEquals('PathSeparator = :', '1', Lines.Strings[2]);
AssertEquals('LineEnding = #10', 'True', Lines.Strings[0]);
AssertEquals('DirectorySeparator = /', 'True', Lines.Strings[1]);
AssertEquals('PathSeparator = :', 'True', Lines.Strings[2]);
finally
Lines.Free;
end;
@ -673,7 +673,7 @@ begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
AssertTrue('compile+run', CompileAndRun(SrcGetCurrentDir, Output, RCode));
AssertEquals('exit code 0', 0, RCode);
AssertEquals('GetCurrentDir non-empty', '1', Trim(Output));
AssertEquals('GetCurrentDir non-empty', 'True', Trim(Output));
end;
initialization

View file

@ -147,7 +147,7 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('Owner=nil is true', '1', Lines.Strings[0]);
AssertEquals('Owner=nil is true', 'True', Lines.Strings[0]);
AssertEquals('ComponentCount=0', '0', Lines.Strings[1]);
finally
Lines.Free
@ -173,7 +173,7 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRunWithRTL(SrcOwnerRef, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertEquals('Child.Owner=Parent', '1', Trim(Output));
AssertEquals('Child.Owner=Parent', 'True', Trim(Output));
end;
procedure TE2ETComponentTests.TestRun_ComponentCount;

View file

@ -275,7 +275,7 @@ begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit end;
AssertTrue('compile+run', CompileAndRunWithRTL(SrcTerminateFlag, Output, RCode));
AssertEquals('exit code', 0, RCode);
AssertEquals('stdout', '1' + LE + 'ok' + LE, Output)
AssertEquals('stdout', 'True' + LE + 'ok' + LE, Output)
end;
procedure TE2EThreadingTests.TestRun_Thread_FinishedAfterExecute;
@ -284,7 +284,7 @@ begin
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit end;
AssertTrue('compile+run', CompileAndRunWithRTL(SrcFinishedFlag, Output, RCode));
AssertEquals('exit code', 0, RCode);
AssertEquals('stdout', '0' + LE + '1' + LE, Output)
AssertEquals('stdout', 'False' + LE + 'True' + LE, Output)
end;
procedure TE2EThreadingTests.TestRun_Thread_MultipleThreads;

View file

@ -215,10 +215,10 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcTryGetValue, Output, RCode));
AssertEquals('exit 0', 0, RCode);
{ Boolean true prints as 1, false as 0 in Blaise }
AssertTrue('found -> true (1)', Pos('1', Output) >= 0);
AssertTrue('value=99', Pos('99', Output) >= 0);
AssertTrue('missing -> false (0)', Pos('0', Output) >= 0);
{ Boolean true prints as True, false as False in Blaise }
AssertTrue('found -> true (True)', Pos('True', Output) >= 0);
AssertTrue('value=99', Pos('99', Output) >= 0);
AssertTrue('missing -> false (False)', Pos('False', Output) >= 0);
end;
procedure TE2EOrdDictTests.TestRun_OrdDict_Remove;

View file

@ -176,8 +176,8 @@ begin
AssertTrue('compile+run', CompileAndRun(SrcExclude, Output, RCode));
AssertEquals('exit 0', 0, RCode);
AssertTrue('count=2 after exclude', Pos('2', Output) >= 0);
{ Boolean false prints as 0 in Blaise }
AssertTrue('contains returns false (0)', Pos('0', Output) >= 0);
{ Boolean false prints as False in Blaise }
AssertTrue('contains returns false (False)', Pos('False', Output) >= 0);
end;
procedure TE2ESetTests.TestRun_TSet_ContainsMembership;
@ -188,9 +188,9 @@ begin
if not ToolchainAvailable then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(SrcContains, Output, RCode));
AssertEquals('exit 0', 0, RCode);
{ Boolean true prints as 1, false as 0 in Blaise }
AssertTrue('contains 42 -> true (1)', Pos('1', Output) >= 0);
AssertTrue('contains 99 -> false (0)', Pos('0', Output) >= 0);
{ Boolean true prints as True, false as False in Blaise }
AssertTrue('contains 42 -> true (True)', Pos('True', Output) >= 0);
AssertTrue('contains 99 -> false (False)', Pos('False', Output) >= 0);
end;
procedure TE2ESetTests.TestRun_TSet_CountTracking;

View file

@ -669,8 +669,8 @@ begin
try
Lines.Text := Trim(Output);
AssertEquals('Count=2', '2', Lines.Strings[0]);
AssertEquals('Text contains line1', '1', Lines.Strings[1]);
AssertEquals('Text contains line2', '1', Lines.Strings[2]);
AssertEquals('Text contains line1', 'True', Lines.Strings[1]);
AssertEquals('Text contains line2', 'True', Lines.Strings[2]);
finally
Lines.Free
end
@ -692,9 +692,9 @@ begin
AssertEquals('Count=500', '500', Lines.Strings[0]);
LenStr := Lines.Strings[1];
AssertTrue('Length(Text) > 3500', StrToInt(LenStr) > 3500);
AssertEquals('contains line_1', '1', Lines.Strings[2]);
AssertEquals('contains line_250', '1', Lines.Strings[3]);
AssertEquals('contains line_499', '1', Lines.Strings[4]);
AssertEquals('contains line_1', 'True', Lines.Strings[2]);
AssertEquals('contains line_250', 'True', Lines.Strings[3]);
AssertEquals('contains line_499', 'True', Lines.Strings[4]);
finally
Lines.Free
end
@ -788,9 +788,9 @@ begin
Lines := TStringList.Create;
try
Lines.Text := Trim(Output);
AssertEquals('Find(cherry)=1 (true)', '1', Lines.Strings[0]);
AssertEquals('Find(cherry) idx', '1', Lines.Strings[1]);
AssertEquals('Find(durian)=0 (false)', '0', Lines.Strings[2]);
AssertEquals('Find(cherry)=True (true)', 'True', Lines.Strings[0]);
AssertEquals('Find(cherry) idx', '1', Lines.Strings[1]);
AssertEquals('Find(durian)=False (false)', 'False', Lines.Strings[2]);
finally
Lines.Free
end

View file

@ -3172,3 +3172,24 @@ All three constants are defined in `system.pas` and pre-seeded as compiler
built-ins in `RegisterBuiltins`. Their values are derived from the compilation
target (`GTarget` in `blaise.codegen.target`), so cross-compilation to a
different OS produces the correct constants without conditional compilation.
== Boolean formatting — `WriteLn` prints `True` / `False`
Both Delphi and FPC print `TRUE` / `FALSE` (case varies by RTL version) when a
Boolean value is passed to `Write` / `WriteLn`. Early Blaise treated booleans as
plain integers, so `WriteLn(True)` printed `1` and `WriteLn(False)` printed `0`.
This was a codegen shortcut, not a deliberate design choice. It surprised users
familiar with any other Pascal dialect and made program output harder to read.
Blaise now emits a dedicated `_SysWriteBool` call for Boolean-typed arguments.
The runtime writes the literal string `True` or `False` — no heap allocation, no
string conversion. `BoolToStr` in `sysutils.pas` is unaffected and continues to
return `'True'` / `'False'` as a string value.
*Alternatives considered:*
* `TRUE` / `FALSE` (all-caps, matching Delphi) — rejected; Blaise identifiers are
`True` / `False` (title-case), and the output should match the language.
* Keeping `1` / `0` and requiring explicit `BoolToStr` — rejected; the implicit
integer rendering was a bug, not a feature.

View file

@ -60,6 +60,7 @@ type
procedure SysWriteUInt64(Fd: Integer; N: UInt64); virtual; abstract;
procedure SysWriteDouble(Fd: Integer; V: Double); virtual; abstract;
procedure SysWriteSingle(Fd: Integer; V: Single); virtual; abstract;
procedure SysWriteBool(Fd: Integer; B: Boolean); virtual; abstract;
procedure SysWriteNewline(Fd: Integer); virtual; abstract;
{ File-descriptor primitives — used by streams }

View file

@ -68,6 +68,7 @@ type
procedure SysWriteUInt64(Fd: Integer; N: UInt64); override;
procedure SysWriteDouble(Fd: Integer; V: Double); override;
procedure SysWriteSingle(Fd: Integer; V: Single); override;
procedure SysWriteBool(Fd: Integer; B: Boolean); override;
procedure SysWriteNewline(Fd: Integer); override;
{ File-descriptor primitives }
@ -243,6 +244,7 @@ procedure _SysWriteInt64(Fd: Integer; N: Int64);
procedure _SysWriteUInt64(Fd: Integer; N: UInt64);
procedure _SysWriteDouble(Fd: Integer; V: Double);
procedure _SysWriteSingle(Fd: Integer; V: Single);
procedure _SysWriteBool(Fd: Integer; B: Boolean);
procedure _SysWriteNewline(Fd: Integer);
{ File-descriptor primitives }
@ -802,6 +804,22 @@ begin
_StringRelease(S);
end;
procedure TRtlPlatformPosix.SysWriteBool(Fd: Integer; B: Boolean);
var
Buf: array[0..4] of Byte;
begin
if B then
begin
Buf[0] := 84; Buf[1] := 114; Buf[2] := 117; Buf[3] := 101;
WriteAllToFd(Fd, PChar(@Buf[0]), 4);
end
else
begin
Buf[0] := 70; Buf[1] := 97; Buf[2] := 108; Buf[3] := 115; Buf[4] := 101;
WriteAllToFd(Fd, PChar(@Buf[0]), 5);
end;
end;
{ ================================================================== }
{ TRtlPlatformPosix — File-descriptor primitives }
{ ================================================================== }
@ -1277,6 +1295,11 @@ begin
GRtlPlatform.SysWriteSingle(Fd, V);
end;
procedure _SysWriteBool(Fd: Integer; B: Boolean);
begin
GRtlPlatform.SysWriteBool(Fd, B);
end;
procedure _SysWriteNewline(Fd: Integer);
begin
GRtlPlatform.SysWriteNewline(Fd);