{
  Blaise - An Object Pascal Compiler
  Copyright (c) 2026 Graeme Geldenhuys
  SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
  Licensed under the Apache License v2.0 with Runtime Library Exception.
  See LICENSE file in the project root for full license terms.
}

unit cp.test.sar;

{ Unit + e2e tests for the `sar` (arithmetic shift right) operator.

  Background: in Pascal, `shr` is a *logical* right shift (zero-fill) —
  it discards the sign bit.  When the programmer wants sign-preserving
  right shift on a signed integer (for example, dividing a negative
  number by a power of two), Blaise provides a distinct `sar` operator
  that maps to QBE's `sar` instruction.

  Coverage:
    - `sar` is a recognised binary operator at the term-level precedence.
    - Codegen emits QBE `sar` (not `shr`) for Int64, UInt64, and 32-bit
      integer types.
    - End-to-end: a negative Int64 sar 1 keeps its sign; a positive
      value matches the shr result. }

interface

uses
  blaise.testing, cp.test.e2e.base,
  uLexer, uParser, uAST, uSymbolTable, uSemantic, blaise.codegen.qbe;

type
  TSarTests = class(TTestCase)
  private
    function AnalyseSrc(const ASrc: string): TProgram;
    function GenIR(const ASrc: string): string;
  published
    procedure TestCodegen_Int64_Sar_EmitsSar;
    procedure TestCodegen_UInt64_Sar_EmitsSar;
    procedure TestCodegen_Integer_Sar_EmitsSar;
    procedure TestCodegen_Shr_StillEmitsShr;
  end;

  [Threaded]
  TSarE2ETests = class(TE2ETestCase)
  protected
    procedure SetUp; override;
  published
    procedure TestRun_NegativeInt64_Sar_PreservesSign;
    procedure TestRun_NegativeInt64_Shr_DiscardsSign;
    procedure TestRun_PositiveInteger_Sar_MatchesShr;
  end;

implementation

{ -------------- helpers -------------- }

function TSarTests.AnalyseSrc(const ASrc: string): TProgram;
var
  L: TLexer;
  P: TParser;
  A: TSemanticAnalyser;
begin
  L := TLexer.Create(ASrc);
  P := TParser.Create(L);
  try
    Result := P.Parse();
  finally
    P.Free();
    L.Free();
  end;
  A := TSemanticAnalyser.Create();
  try
    A.Analyse(Result);
  finally
    A.Free();
  end;
end;

function TSarTests.GenIR(const ASrc: string): string;
var
  Prog: TProgram;
  CG:   TCodeGenQBE;
begin
  Prog := AnalyseSrc(ASrc);
  try
    CG := TCodeGenQBE.Create();
    try
      CG.Generate(Prog);
      Result := CG.GetOutput();
    finally
      CG.Free();
    end;
  finally
    Prog.Free();
  end;
end;

{ -------------- codegen -------------- }

procedure TSarTests.TestCodegen_Int64_Sar_EmitsSar;
var
  IR: string;
begin
  IR := GenIR(
    '''
        program P;
        var A, B: Int64;
        begin
          B := A sar 1
        end.
        ''');
  AssertTrue('Int64 sar emits sar', Pos(' sar ', IR) > 0);
end;

procedure TSarTests.TestCodegen_UInt64_Sar_EmitsSar;
var
  IR: string;
begin
  IR := GenIR(
    '''
        program P;
        var A, B: UInt64;
        begin
          B := A sar 1
        end.
        ''');
  AssertTrue('UInt64 sar emits sar', Pos(' sar ', IR) > 0);
end;

procedure TSarTests.TestCodegen_Integer_Sar_EmitsSar;
var
  IR: string;
begin
  IR := GenIR(
    '''
        program P;
        var A, B: Integer;
        begin
          B := A sar 1
        end.
        ''');
  AssertTrue('Integer sar emits sar', Pos(' sar ', IR) > 0);
end;

procedure TSarTests.TestCodegen_Shr_StillEmitsShr;
var
  IR: string;
begin
  { Regression: `shr` must continue to map to QBE `shr`, not `sar`.
    A unit test for this is cheap insurance against accidental
    cross-wiring of the two operators. }
  IR := GenIR(
    '''
        program P;
        var A, B: Int64;
        begin
          B := A shr 1
        end.
        ''');
  AssertTrue('Int64 shr emits shr', Pos(' shr ', IR) > 0);
  AssertFalse('Int64 shr does not emit sar', Pos(' sar ', IR) > 0);
end;

{ -------------- e2e -------------- }

procedure TSarE2ETests.SetUp;
begin
  inherited SetUp();
  SetUpScratch('compiler/target/test-e2e-sar');
end;

const
  LE = #10;

  SrcNegInt64Sar =
    '''
    program P;
    var A, B: Int64;
    begin
      A := -16;
      B := A sar 2;
      WriteLn(B)
    end.
    ''';

  SrcNegInt64Shr =
    '''
    program P;
    var A, B: Int64;
    begin
      A := -16;
      B := A shr 2;
      WriteLn(B)
    end.
    ''';

  SrcPosIntSar =
    '''
    program P;
    var A, B: Integer;
    begin
      A := 64;
      B := A sar 2;
      WriteLn(B)
    end.
    ''';

procedure TSarE2ETests.TestRun_NegativeInt64_Sar_PreservesSign;
var Output: string; RCode: Integer;
begin
  if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
  AssertTrue('compile+run', CompileAndRun(SrcNegInt64Sar, Output, RCode));
  AssertEquals('exit code 0', 0, RCode);
  { -16 sar 2 = -4 (sign preserved) }
  AssertEquals('-16 sar 2 = -4', '-4' + LE, Output);
end;

procedure TSarE2ETests.TestRun_NegativeInt64_Shr_DiscardsSign;
var Output: string; RCode: Integer;
begin
  if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
  AssertTrue('compile+run', CompileAndRun(SrcNegInt64Shr, Output, RCode));
  AssertEquals('exit code 0', 0, RCode);
  { -16 shr 2 = ((2^64 - 16) >> 2) = 2^62 - 4 = 4611686018427387900 }
  AssertEquals('-16 shr 2 = 4611686018427387900',
    '4611686018427387900' + LE, Output);
end;

procedure TSarE2ETests.TestRun_PositiveInteger_Sar_MatchesShr;
var Output: string; RCode: Integer;
begin
  if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
  AssertTrue('compile+run', CompileAndRun(SrcPosIntSar, Output, RCode));
  AssertEquals('exit code 0', 0, RCode);
  { 64 sar 2 = 16 (positive numbers behave identically) }
  AssertEquals('64 sar 2 = 16', '16' + LE, Output);
end;

initialization
  RegisterTest(TSarTests);
  RegisterTest(TSarE2ETests);

end.
