unit System;


interface

{$define FPC_IS_SYSTEM}
{ The heap for MSDOS is implemented
  in tinyheap.inc include file,
  but it uses default SysGetMem names }

{$define HAS_MEMORYMANAGER}
{ define TEST_FPU_INT10 to force keeping local int10,
  for testing purpose only }


{$DEFINE FPC_INCLUDE_SOFTWARE_MUL}
{$DEFINE FPC_INCLUDE_SOFTWARE_MOD_DIV}

{$DEFINE FPC_USE_SMALL_DEFAULTSTACKSIZE}
{ To avoid warnings in thread.inc code,
  but value must be really given after
  systemh.inc is included otherwise the
  $mode switch is not effective }

{ Use AnsiChar for files }
{$define FPC_ANSI_TEXTFILEREC}
{$define FPC_STDOUT_TRUE_ALIAS}

{$ifdef NO_WIDESTRINGS}
  { Do NOT use widechar for files }
  {$undef FPC_HAS_FEATURE_WIDESTRINGS}
{$endif NO_WIDESTRINGS}

{$I systemh.inc}
{$I tnyheaph.inc}
{$I portsh.inc}

{$ifndef FPUNONE}
{$ifdef FPC_HAS_FEATURE_SOFTFPU}

{$define fpc_softfpu_interface}
{$i softfpu.pp}
{$undef fpc_softfpu_interface}

{$endif FPC_HAS_FEATURE_SOFTFPU}
{$endif FPUNONE}

const
  LineEnding = #13#10;
  { LFNSupport is a variable here, defined below!!! }
  DirectorySeparator = '\';
  DriveSeparator = ':';
  ExtensionSeparator = '.';
  PathSeparator = ';';
  AllowDirectorySeparators : set of AnsiChar = ['\','/'];
  AllowDriveSeparators : set of AnsiChar = [':'];
  { FileNameCaseSensitive and FileNameCasePreserving are defined separately below!!! }
  maxExitCode = 255;
  MaxPathLen = 256;

const
{ Default filehandles }
  UnusedHandle    = $ffff;{ instead of -1, as it is a word value}
  StdInputHandle  = 0;
  StdOutputHandle = 1;
  { MSX-DOS does not have a separate StdErr }
  StdErrorHandle  = 1;

  FileNameCaseSensitive : boolean = false;
  FileNameCasePreserving: boolean = false;
  CtrlZMarksEOF: boolean = true; (* #26 is considered as end of file *)

  sLineBreak = LineEnding;
  DefaultTextLineBreakStyle : TTextLineBreakStyle = tlbsCRLF;

var
{ Mem[] support }
  mem  : array[0..$7fff-1] of byte absolute $0;
  memw : array[0..($7fff div sizeof(word))-1] of word absolute $0;
  meml : array[0..($7fff div sizeof(longint))-1] of longint absolute $0;
{ C-compatible arguments and environment }
  argc:smallint; //!! public name 'operatingsystem_parameter_argc';
  argv:PPAnsiChar; //!! public name 'operatingsystem_parameter_argv';

{ The DOS Program Segment Prefix segment (TP7 compatibility) }
  PrefixSeg:Word;public name '__fpc_PrefixSeg';

  SaveInt00: FarPointer;public name '__SaveInt00';
  SaveInt10: FarPointer;public name '__SaveInt10';
  SaveInt75: FarPointer;public name '__SaveInt75';
  fpu_status: word;public name '__fpu_status';

const
  AllFilesMask: string [3] = '*.*';

const
  LFNSupport = false;

implementation

procedure DebugWrite(s: PAnsiChar); forward;
procedure DebugWrite(const S: Shortstring); forward;
procedure DebugWriteLn(const S: ShortString); forward;

{$ifdef todo}
const
  { used for an offset fixup for accessing the proc parameters in asm routines
    that use nostackframe. We can't use the parameter name directly, because
    i8086 doesn't support sp relative addressing. }
{$ifdef FPC_X86_CODE_FAR}
  extra_param_offset = 2;
{$else FPC_X86_CODE_FAR}
  extra_param_offset = 0;
{$endif FPC_X86_CODE_FAR}
{$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
  extra_data_offset = 2;
{$else}
  extra_data_offset = 0;
{$endif}

type
  PFarByte = ^Byte;//far;
  PFarChar = ^AnsiChar;//far;
  PFarWord = ^Word;//far;
  PPFarChar = ^PFarChar;
{$endif}

var
  stklen: word; external name '__stklen';

  __heapsize: Word;external name '__heapsize';
  __fpc_initialheap: array[0..0] of byte;external name '__fpc_initialheap';

var
  __stktop : pointer;public name '__stktop';
  dos_version:Word;public name 'dos_version';
  dos_env_count:smallint;public name '__dos_env_count';
  dos_argv0 : PAnsiChar;public name '__fpc_dos_argv0';

{$I registers.inc}

procedure Intr(IntNo: Byte; var Regs: Registers); external name 'FPC_INTR';

procedure MsxDos(var Regs: Registers); assembler; nostackframe; public name 'FPC_MSXDOS';
asm
  //in a, (0x2e)
  { store registers contents }
  push AF
  push BC
  push DE
  push HL
  push IX
  push IY
  { allocate an additional scratch space }
  push IY
  { Regs now resides at SP + 16 }

  { IY is not used for parameters, so base everything on that;
    for that we need to load the address of Regs into IY }
  ld IX, 0x10
  add IX, SP

  ld L,(IX+0)
  ld H,(IX+1)

  push HL
  pop IY

  { fill IX with the help of HL }
  ld L,(IY+8)
  ld H,(IY+9)

  push HL
  pop IX

  ld B,(IY+1)
  ld C,(IY+0)
  ld D,(IY+3)
  ld E,(IY+2)
  // load A last
  //ld A,(IY+4)
  ld H,(IY+7)
  ld L,(IY+6)

  ld A,(IY+4)

  { store IY to scratch location }
  ex (SP),IY

  { call to DOS }
  call 0x0005

  { store IY to scratch and restore pointer address of Regs }
  ex (SP),IY

  ld (IY+1),B
  ld (IY+0),C
  ld (IY+3),D
  ld (IY+2),E
  ld (IY+4),A
  // skip F
  ld (IY+7),H
  ld (IY+6),L

  { store IX with the help of HL }
  push IX
  pop HL
  ld (IY+8),L
  ld (IY+9),H

  { store the stored IY with the help of HL }
  ex (SP),HL

  ld (IY+10),L
  ld (IY+11),H

  { cleanup stack }
  pop IY
  pop IY
  pop IX
  pop HL
  pop DE
  pop BC
  pop AF
end;

procedure InstallInterruptHandlers; external name 'FPC_INSTALL_INTERRUPT_HANDLERS';
procedure RestoreInterruptHandlers; external name 'FPC_RESTORE_INTERRUPT_HANDLERS';

function CheckNullArea: Boolean; external name 'FPC_CHECK_NULLAREA';

{$I system.inc}

{$I tinyheap.inc}

{$I ports.inc}

{$ifndef FPUNONE}
{$ifdef FPC_HAS_FEATURE_SOFTFPU}

{$define fpc_softfpu_implementation}
{$i softfpu.pp}
{$undef fpc_softfpu_implementation}

{ we get these functions and types from the softfpu code }
{$define FPC_SYSTEM_HAS_float64}
{$define FPC_SYSTEM_HAS_float32}
{$define FPC_SYSTEM_HAS_flag}
{$define FPC_SYSTEM_HAS_extractFloat64Frac0}
{$define FPC_SYSTEM_HAS_extractFloat64Frac1}
{$define FPC_SYSTEM_HAS_extractFloat64Exp}
{$define FPC_SYSTEM_HAS_extractFloat64Frac}
{$define FPC_SYSTEM_HAS_extractFloat64Sign}
{$define FPC_SYSTEM_HAS_ExtractFloat32Frac}
{$define FPC_SYSTEM_HAS_extractFloat32Exp}
{$define FPC_SYSTEM_HAS_extractFloat32Sign}

{$endif FPC_HAS_FEATURE_SOFTFPU}
{$endif FPUNONE}

procedure DebugWrite(S: PAnsiChar);
var
  regs: Registers;
begin
  while S^ <> #0 do begin
    regs.C := $02;
    regs.E := Ord(S^);
    MsxDos(regs);
    Inc(S);
  end;
end;

procedure DebugWrite(const S: shortstring);
var
  regs: Registers;
  i: Byte;
begin
  for i := 1 to Length(S) do begin
    regs.C := $02;
    regs.E := Ord(S[i]);
    MsxDos(regs);
  end;
end;

procedure DebugWriteLn(const S: shortstring);
begin
  DebugWrite(S);
  DebugWrite(#13#10);
end;

{*****************************************************************************
                              ParamStr/Randomize
*****************************************************************************}

var
  internal_envp : PPAnsiChar = nil;

procedure setup_environment;
{$ifdef todo}
var
  env_count : smallint;
  cp, dos_env: PFarChar;
{$endif}
begin
{$ifdef todo}
  env_count:=0;
  dos_env:=Ptr(MemW[PrefixSeg:$2C], 0);
  cp:=dos_env;
  while cp^<>#0 do
    begin
      inc(env_count);
      while (cp^ <> #0) do
        inc(cp); { skip to NUL }
      inc(cp); { skip to next character }
    end;
  internal_envp := getmem((env_count+1) * sizeof(PFarChar));
  cp:=dos_env;
  env_count:=0;
  while cp^<>#0 do
    begin
      internal_envp[env_count] := cp;
      inc(env_count);
      while (cp^ <> #0) do
        inc(cp); { skip to NUL }
      inc(cp); { skip to next character }
    end;
  internal_envp[env_count]:=nil;
  dos_env_count := env_count;
  if dos_version >= $300 then
    begin
      if cp=dos_env then
        inc(cp);
      inc(cp, 3);
      dos_argv0 := cp;
    end
  else
    dos_argv0 := nil;
{$endif}
end;

function envp:PPAnsiChar;public name '__fpc_envp';
begin
  if not assigned(internal_envp) then
    setup_environment;
  envp:=internal_envp;
end;

function GetEnvVar(aName: PAnsiChar): ShortString;
var
  regs: Registers;
  i: SizeInt;
begin
  SetLength(Result, 255);
  regs.C := $6B;
  regs.HL := PtrUInt(aName);
  regs.DE := PtrUInt(@Result[1]);
  regs.B := 255;
  regs.A := 0;
  MsxDos(regs);
  if regs.A = 0 then begin
    i := 1;
    aName := PAnsiChar(@Result[1]);
    while i < 256 do begin
      if aName^ = #0 then begin
        SetLength(Result, i);
        Break;
      end;
      Inc(i);
      Inc(aName);
    end;
  end else
    SetLength(Result, 0);
end;

procedure setup_arguments;
var
  i: SmallInt;
  pc: PAnsiChar;
  quote: AnsiChar;
  count: SmallInt;
  arglen, argv0len: SmallInt;
  argblock: PAnsiChar;
  arg: PAnsiChar;
  doscmd   : string[129];  { Dos commandline copied from PSP, max is 128 chars +1 for terminating zero }
  tmp: ShortString;
  regs: Registers;
begin
  tmp := GetEnvVar('PROGRAM');
  argv0len := Length(tmp);

  tmp := GetEnvVar('PARAMETERS');
{$IfDef SYSTEM_DEBUG_STARTUP}
  Writeln(stderr,'Dos command line is #',tmp,'# size = ',length(tmp));
{$EndIf }
  { parse dos commandline }
  pc:=@tmp[1];
  count:=1;
  { calc total arguments length and count }
  arglen:=argv0len+1;
  while pc^<>#0 do
    begin
      { skip leading spaces }
      while pc^ in [#1..#32] do
        inc(pc);
      if pc^=#0 then
        break;
      { calc argument length }
      quote:=' ';
      while (pc^<>#0) do
        begin
          case pc^ of
            #1..#32 :
              begin
                if quote<>' ' then
                  inc(arglen)
                else
                  break;
              end;
            '"' :
              begin
                if quote<>'''' then
                  begin
                    if PAnsiChar(pc+1)^<>'"' then
                      begin
                        if quote='"' then
                          quote:=' '
                        else
                          quote:='"';
                      end
                    else
                     inc(pc);
                  end
                else
                  inc(arglen);
              end;
            '''' :
              begin
                if quote<>'"' then
                  begin
                    if PAnsiChar(pc+1)^<>'''' then
                      begin
                        if quote=''''  then
                         quote:=' '
                        else
                         quote:='''';
                      end
                    else
                      inc(pc);
                  end
                else
                  inc(arglen);
              end;
            else
              inc(arglen);
          end;
          inc(pc);
        end;
      inc(arglen);  { for the null terminator }
      inc(count);
    end;
  Writeln(stderr,'Arg count: ', count, ', size: ', arglen);
  { set argc and allocate argv }
  argc:=count;
  argv:=AllocMem((count+1)*SizeOf(PAnsiChar));
  { allocate a single memory block for all arguments }
  argblock:=GetMem(arglen);
  writeln('Allocated arg vector at ', hexstr(argv), ' and block at ', hexstr(argblock));
  { create argv[0] }
  argv[0]:=argblock;
  arg:=argblock+argv0len;

  arg^:=#0;
  Inc(arg);

  pc:=@tmp[1];
  count:=1;
  while pc^<>#0 do
    begin
      { skip leading spaces }
      while pc^ in [#1..#32] do
        inc(pc);
      if pc^=#0 then
        break;
      { copy argument }
      //writeln('Setting arg ',count,' to ', hexstr(arg));
      asm
        in a,(0x2e)
      end ['a'];
      argv[count]:=arg;
      quote:=' ';
      while (pc^<>#0) do
        begin
          case pc^ of
            #1..#32 :
              begin
                if quote<>' ' then
                  begin
                    arg^:=pc^;
                    inc(arg);
                  end
                else
                  break;
              end;
            '"' :
              begin
                if quote<>'''' then
                  begin
                    if PAnsiChar(pc+1)^<>'"' then
                      begin
                        if quote='"' then
                          quote:=' '
                        else
                          quote:='"';
                      end
                    else
                      inc(pc);
                  end
                else
                  begin
                    arg^:=pc^;
                    inc(arg);
                  end;
              end;
            '''' :
              begin
                if quote<>'"' then
                  begin
                    if PAnsiChar(pc+1)^<>'''' then
                      begin
                        if quote=''''  then
                          quote:=' '
                        else
                          quote:='''';
                      end
                    else
                      inc(pc);
                  end
                else
                  begin
                    arg^:=pc^;
                    inc(arg);
                  end;
              end;
            else
              begin
                arg^:=pc^;
                inc(arg);
              end;
          end;
          inc(pc);
        end;
      arg^:=#0;
      Inc(arg);
 {$IfDef SYSTEM_DEBUG_STARTUP}
      Writeln(stderr,'dos arg ',count,' #',strlen(argv[count]),'#',argv[count],'#');
 {$EndIf SYSTEM_DEBUG_STARTUP}
      inc(count);
    end;

  arg:=argblock;
  tmp:=GetEnvVar('PROGRAM');
  pc:=@tmp[1];
  while pc^ <> #0 do
    begin
      arg^ := pc^;
      Inc(arg);
      Inc(pc);
    end;

  for count:=0 to argc-1 do
    writeln('arg ',count,' at ',hexstr(argv[count]));
end;


function paramcount : longint;
begin
  if argv=nil then
    setup_arguments;
  paramcount := argc - 1;
end;


function paramstr(l : longint) : ShortString;
begin
  if argv=nil then
    setup_arguments;
  if (l>=0) and (l+1<=argc) then
    paramstr:=strpas(argv[l])
  else
    paramstr:='';
end;


procedure randomize;
{$ifdef todo}
var
  hl   : longint;
  regs : Registers;
{$endif}
begin
{$ifdef todo}
  regs.AH:=$2C;
  MsDos(regs);
  hl:=regs.DX;
  randseed:=hl*$10000+ regs.CX;
{$endif}
end;

{*****************************************************************************
                         System Dependent Exit code
*****************************************************************************}

procedure system_exit;
var
  h : byte;
begin
{$ifdef todo}
  RestoreInterruptHandlers;
{$endif}
  for h:=0 to max_files-1 do
    if openfiles[h] then
      begin
{$ifdef SYSTEMDEBUG}
         writeln(stderr,'file ',h,' "',opennames[h],'" not closed at exit');
{$endif SYSTEMDEBUG}
         if h>=5 then
           do_close(h);
      end;
{$ifndef FPC_MM_TINY}
{$ifdef todo}
  if not CheckNullArea then
    writeln(stderr, 'Nil pointer assignment');
{$endif}
{$endif FPC_MM_TINY}
  asm
    ld a, exitcode
    ld b, a
    ld c, 0x62
    call 0x0005
  end;
end;

{*****************************************************************************
                         SystemUnit Initialization
*****************************************************************************}

procedure InitDosHeap;
begin
  RegisterTinyHeapBlock_Simple_Prealigned(@__fpc_initialheap,__heapsize);
end;

procedure SysInitStdIO;
begin
  OpenStdIO(Input,fmInput,StdInputHandle);
  OpenStdIO(Output,fmOutput,StdOutputHandle);
  OpenStdIO(ErrOutput,fmOutput,StdErrorHandle);
{$ifndef FPC_STDOUT_TRUE_ALIAS}
  OpenStdIO(StdOut,fmOutput,StdOutputHandle);
  OpenStdIO(StdErr,fmOutput,StdErrorHandle);
{$endif FPC_STDOUT_TRUE_ALIAS}
end;

function GetProcessID: SizeUInt;
begin
  GetProcessID := PrefixSeg;
end;

function CheckInitialStkLen(stklen : SizeUInt) : SizeUInt;
begin
  result := stklen;
end;

procedure InitDosVersion;
var
  regs: Registers;
begin
  regs.C := $6F;
  regs.A := 0;
  MsxDos(regs);
  if regs.A <> 0 then
    dos_version := 0
  else if regs.B < 2 then
    dos_version := $100
  else
    dos_version := regs.DE;
end;

begin
  StackLength := stklen;
  StackBottom := __stktop - stklen;
  InitDosVersion;
  { for now we don't support MSX-DOS 1 }
  if dos_version < $100 then
    Halt($85);
{$ifdef todo}
  InstallInterruptHandlers;
{$endif}
  { To be set if this is a GUI or console application }
  IsConsole := TRUE;
{$ifdef FPC_HAS_FEATURE_DYNLIBS}
  { If dynlibs feature is disabled,
    IsLibrary is a constant, which can thus not be set to a value }
  { To be set if this is a library and not a program  }
  IsLibrary := FALSE;
{$endif def FPC_HAS_FEATURE_DYNLIBS}
{ Setup heap }
  InitDosHeap;
  SysInitExceptions;
{$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
  initunicodestringmanager;
{$endif def FPC_HAS_FEATURE_UNICODESTRINGS}
{ Setup stdin, stdout and stderr }
  SysInitStdIO;
{ Setup environment and arguments }
  { Done on  request only Setup_Environment; }
  { Done on request only Setup_Arguments; }
{ Reset IO Error }
  InOutRes:=0;
{$ifdef FPC_HAS_FEATURE_THREADING}
  InitSystemThreads;
{$endif}
end.
