(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *) program shell; const EDITORPROG = '#SYSTEM:editor.prog'; COMPILERPROG = '#SYSTEM:pcomp.prog'; ASMPROG = '#SYSTEM:sasm.prog'; RECLAIMPROG = '#SYSTEM:reclaim.prog'; const PageMargin = 3; MenuHeight = 6; var cmd:char; ShellWorkfile:pathnamestr external; ShellCmd:string[40] external; ShellArg:integer external; procedure checkClock; var line:string; digits:string[4]; error:integer; yy,mm,dd,h,m,s:integer; isValid:boolean; function lineIsValid:boolean; var c:char; begin lineIsValid := false; if length(line) = 14 then begin for c in line do if not isDigit(c) then break; lineIsValid := true; end; end; function isInRange(v,lo,hi:integer):boolean; begin isInRange := (v>=lo) and (v<=hi); end; begin if SysClock.year = 0 then begin writeln('System clock not set - please enter date and time:'); repeat isValid := false; write('YYYYMMDDHHMMSS> '); readln(line); if lineIsValid then begin isValid := true; digits := copy(line,1,4); val(digits, yy, error); isValid := isValid and isInRange(yy, 1950, 3000); digits := copy(line,5,2); val(digits, mm, error); isValid := isValid and isInRange(mm, 1, 12); digits := copy(line,7,2); val(digits, dd, error); isValid := isValid and isInRange(dd, 1, 31); digits := copy(line,9,2); val(digits, h, error); isValid := isValid and isInRange(h, 0, 23); digits := copy(line,11,2); val(digits, m, error); isValid := isValid and isInRange(m, 0, 59); digits := copy(line,13,2); val(digits, s, error); isValid := isValid and isInRange(s, 0, 59); end; until isValid; SysClock.year := yy; SysClock.month := mm; SysClock.day := dd; SysClock.hours := h; SysClock.minutes := m; SysClock.seconds := s; writeln('System clock is ', DateStr(SysClock), ' ', TimeStr(SysClock, true)); end; end; procedure writew(s:string;width:integer); var w,i:integer; begin write(s); w := width - length(s); if w > 0 then for i := 1 to w do write(' '); end; procedure splitFilename(var n:filenamestr; var basename:filenamestr;var extension:filenamestr); var i:integer; begin for i := length(n) downto 1 do begin if n[i] = '.' then break; end; if i > 1 then begin basename := copy(n, 1, i - 1); extension := copy(n, i, length(n) - i + 1); { writeln('** splitFilename ',basename, ' ', extension); } end else begin basename := n; extension := ''; end; end; function replaceExtension(var n:pathnamestr; newExt:filenamestr):pathnamestr; var basename:filenamestr; ext:filenamestr; begin splitFilename(n, basename, ext); replaceExtension := basename + newExt; end; procedure waitForKey; forward; procedure listDirectory; var volid:integer; error:integer; index:integer; dirs:DirectorySlot; ftime:DateTime; screenW,screenH:integer; count:integer; begin GetTermSize(screenW, screenH); volid := findvolume(DefaultVolume); if volid < 1 then writeln('Volume ', DefaultVolume, ' not found.') else begin count := PageMargin; writeln('reading directory of ', DefaultVolume); openvolumeid(volid); readdirfirst(volid, index, dirs, error); while index > 0 do begin if dirs.modTime = 0 then begin ftime.year := 1970; ftime.month := 1; ftime.day := 1; ftime.hours := 0; ftime.minutes := 0; ftime.seconds := 0; end else ftime := GetDateTime(dirs.modTime); writew(dirs.name, 34); writew(DateStr(ftime) + ' ' + TimeStr(ftime,false), 22); writeln(dirs.sizeBytes:12, ' ', dirs.generation); count := count + 1; if count >= screenH then begin count := PageMargin; waitForKey; end; readdirnext(volid, index, dirs, error); end; closevolumeid(volid); if count + MenuHeight >= screenH then waitForKey; end; end; function volumeExists(var n:volumenamestr):boolean; var volid:integer; begin volid := findvolume(n); if volid < 1 then volumeExists := false else begin closevolumeid(volid); volumeExists := true; end; end; procedure listVolumes; var i:integer; begin InitDevices; writeln('Available volumes:'); for i := 1 to VolumeCount do writeln(VolumeTable[i].part.name); end; procedure changeVolume; var n:volumenamestr; begin listVolumes; write('Enter volume name: '); readln(n); if length(n) > 0 then if volumeExists(n) then SetDefaultVolume(n) else writeln('Volume ', n , ' not found.'); end; procedure removeFile; var n:filenamestr; error:integer; begin write('File to delete: '); readln(n); if length(n) > 0 then begin erase(n, error); if error <> 0 then writeln('Error deleting ', n, ': ', ErrorStr(error)); end; end; procedure renameFile; var n1,n2:filenamestr; error:integer; begin write('File to rename: '); readln(n1); write('New name: '); readln(n2); rename(n1, n2, error); if error <> 0 then writeln('Error renaming ', n1, ': ', ErrorStr(error)); end; procedure copyFile; var n1,n2:filenamestr; error:integer; src,dst:file; ch:char; count:integer; begin write('File to copy: '); readln(n1); write('New file name: '); readln(n2); open(src, n1, ModeReadonly); if IOResult(src) <> 0 then begin writeln('Error opening ', n1, ': ', ErrorStr(IOResult(src))); exit; end; open(dst, n2, ModeCreate); if IOResult(dst) <> 0 then begin writeln('Error opening ', n2, ': ', ErrorStr(IOResult(dst))); close(src); exit; end; write('Copying ',n1, ' to ', n2, '...'); count := 0; while not eof(src) do begin read(src,ch); (* not efficient but keep it simple *) write(dst,ch); count := count + 1; if (count and 8191) = 0 then write('.'); end; close(dst); close(src); writeln; end; procedure setWorkfile; var n:filenamestr; begin write('Enter workfile name: '); readln(n); ShellWorkfile := n; ShellCmd := ''; ShellArg := 0; end; procedure requireWorkfile; begin while length(ShellWorkFile) = 0 do setWorkfile; end; procedure edit(gotoLine:integer); var error:integer; digits:string[10]; args:PArgVec; begin requireWorkfile; if gotoLine > 0 then begin str(gotoLine,digits); args[0] := '-l'; args[1] := digits; args[2] := ShellWorkFile; PExec(EDITORPROG, args, 3, error); end else PExec2(EDITORPROG, ShellWorkFile, error); writeln('PExec error ', error); end; procedure assemble; var filename:filenamestr; error:integer; begin requireWorkfile; filename := replaceExtension(ShellWorkFile, '.s'); PExec2(ASMPROG, filename, error); writeln('PExec error ', error); end; procedure compile; var filename:filenamestr; error:integer; begin requireWorkfile; filename := replaceExtension(ShellWorkFile, '.pas'); PExec3(COMPILERPROG, '-S', filename, error); writeln('PExec error ', error); end; procedure build; var filename:filenamestr; error:integer; begin requireWorkfile; filename := replaceExtension(ShellWorkFile, '.pas'); PExec2(COMPILERPROG, filename, error); writeln('PExec error ', error); end; procedure run; var args:PArgVec; error:integer; prgname:pathnamestr; begin requireWorkfile; prgname := replaceExtension(ShellWorkfile, '.prog'); writeln('Running ', prgname); PExec(prgname, args, 0, error); writeln('Pexec failed, error ', error); end; procedure krunch; var error:integer; begin PExec2(RECLAIMPROG, DefaultVolume, error); writeln('PExec error ', error); end; procedure runProgram; var args:PArgVec; argCount:integer; error:integer; prgname:pathnamestr; a:string; begin write('Enter program file name: '); readln(prgname); if length(prgname) > 0 then begin if pos('.', prgname) = 0 then prgname := prgname + '.prog'; writeln('Enter program arguments line-by-line, empty line to finish.'); (* entering the arguments line by line is ugly, but it saves us from the hassle of dealing with word boundary detection and quoting *) argCount := 0; repeat write('arg ', argCount + 1, ': '); readln(a); if length(a) > 0 then begin args[argCount] := a; argCount := argCount + 1; end; until (length(a) = 0) or (argCount > PArgMax); writeln('Running ', prgname); PExec(prgname, args, argCount, error); writeln('Pexec failed, error ', error); end; end; procedure showMenu; begin writeln; writeln('W)orkfile: ', ShellWorkfile); writeln('V)olume: ', DefaultVolume); writeln('L)ist directory K)runch volume O)ther program'); writeln('D)elete file RenaM)e file coP)y file'); writeln('E)dit C)ompile A)ssemble B)uild R)un'); write('> '); end; procedure command(cmd:char;arg:integer); begin case cmd of 'L': listDirectory; 'V': changeVolume; 'W': setWorkfile; 'R': run; 'A': assemble; 'C': compile; 'B': build; 'E': edit(arg); 'D': removeFile; 'M': renameFile; 'P': copyFile; 'K': krunch; 'O': runProgram; else ; end; end; procedure waitForKey; var c:char; begin writeln; writeln('-- press any key to continue --'); c := conin; end; begin if length(DefaultVolume) = 0 then SetDefaultVolume('SYSTEM'); checkClock; if length(ShellCmd) > 0 then begin if ShellCmd[1] = 'W' then begin waitForKey; delete(Shellcmd,1,1); end; if length(ShellCmd) > 0 then command(ShellCmd[1], ShellArg); ShellCmd := ''; end; while true do begin showMenu; read(cmd); writeln; command(Upcase(cmd), ShellArg); end; end.