-- BasicInterpB.mesa -- edited by Brotz and Hilton, September 23, 1982 2:33 PM DIRECTORY Ascii, BasicDefs, BasicImpDefs, BasicOps, BcdDefs, BcdOps, ControlDefs, FrameDefs, FrameOps, IODefs, LoaderOps, Real, SegmentDefs, Storage, String, vmD: FROM "VirtualMgrDefs"; BasicInterpB: PROGRAM IMPORTS BasicImpDefs, BasicOps, FrameDefs, FrameOps, IODefs, LoaderOps, Real, SegmentDefs, Storage, String, vmD EXPORTS BasicOps, BasicImpDefs = BEGIN OPEN BasicDefs, BasicImpDefs; LEOp: PUBLIC BasicOps.OpCodeProc = BEGIN PushInteger[IF PopAndCompare[] <= 0 THEN 1 ELSE 0]; RETURN[FALSE, pc]; END; -- of LEOp -- LesserOp: PUBLIC BasicOps.OpCodeProc = BEGIN PushInteger[IF PopAndCompare[] = -1 THEN 1 ELSE 0]; RETURN[FALSE, pc]; END; -- of LesserOp -- ListCodeLine: PROCEDURE [line: ProgramLineDescPtr] = BEGIN op, byte: BasicOps.Byte; var: VariablePtr; pc: vmD.CharIndex _ line.codeStart; PrintVariable: PROCEDURE [var: VariablePtr] = BEGIN IF var.name # NIL THEN IODefs.WriteString[var.name] ELSE BEGIN IODefs.WriteString["Constant: "L]; SELECT var.value.type FROM integer => WriteLongDecimal[var.value.integerValue]; real => BEGIN realString: STRING _ [40]; Real.AppendReal[realString, var.value.realValue]; IODefs.WriteString[realString]; END; string => IODefs.WriteString[var.value.stringValue]; ENDCASE; END; END; -- of PrintVariable -- UNTIL pc >= line.codeEnd DO [op, pc] _ FetchByte[codeCm, pc]; IF op ~IN [1 .. BasicOps.nOps) THEN {IODefs.WriteLine["Smash!!"L]; RETURN}; IODefs.WriteString[BasicOps.opCode[op].name]; SELECT BasicOps.opCode[op].type FROM loner => NULL; byte => BEGIN IODefs.WriteChar[Ascii.TAB]; [byte, pc] _ FetchByte[codeCm, pc]; IODefs.WriteDecimal[byte]; END; var => BEGIN IODefs.WriteChar[Ascii.TAB]; [var, pc] _ FetchPointer[codeCm, pc]; PrintVariable[var]; END; varList => BEGIN IODefs.WriteChar[Ascii.TAB]; DO [var, pc] _ FetchPointer[codeCm, pc]; IF var = LOOPHOLE[0] THEN EXIT; PrintVariable[var]; IODefs.WriteChar[Ascii.TAB]; ENDLOOP; END; label => BEGIN IODefs.WriteChar[Ascii.TAB]; [var, pc] _ FetchPointer[codeCm, pc]; IODefs.WriteDecimal[LOOPHOLE[var]]; END; labelList => BEGIN IODefs.WriteChar[Ascii.TAB]; DO [var, pc] _ FetchPointer[codeCm, pc]; IF var = LOOPHOLE[0] THEN EXIT; IODefs.WriteDecimal[LOOPHOLE[var]]; IODefs.WriteChar[Ascii.TAB]; ENDLOOP; END; defFn => BEGIN IODefs.WriteChar[Ascii.TAB]; [byte, pc] _ FetchByte[codeCm, pc]; IODefs.WriteString[IF byte = 1 THEN "single"L ELSE "multi"L]; IODefs.WriteChar[Ascii.TAB]; [var, pc] _ FetchPointer[codeCm, pc]; IODefs.WriteString[var.name]; IODefs.WriteChar[Ascii.TAB]; [var, pc] _ FetchPointer[codeCm, pc]; IODefs.WriteString[var.name]; END; ENDCASE; IODefs.WriteLine[""L]; ENDLOOP; END; -- of ListCodeLine -- ListOp: PUBLIC BasicOps.OpCodeProc = BEGIN ListOperation[ListSourceLine]; RETURN[FALSE, pc]; END; -- of ListOp -- ListOperation: PUBLIC PROCEDURE [proc: PROC [ProgramLineDescPtr]] = BEGIN bv: BasicValue; line: ProgramLineDescPtr; start, end: CARDINAL; bv _ Pop[]; end _ ConvertToCardinal[@bv]; bv _ Pop[]; start _ ConvertToCardinal[@bv]; FOR line _ programLineDescHead, line.next UNTIL line = NIL OR line.lineNumber >= start DO ENDLOOP; IF line = NIL THEN RunTimeError["Initial LIST line number does not exist."L]; UNTIL line = NIL OR (end # 0 AND line.lineNumber > end) DO proc[line]; line _ line.next; ENDLOOP; END; -- of ListOperation -- ListSourceAndCodeLine: PUBLIC PROCEDURE [line: ProgramLineDescPtr] = BEGIN ListSourceLine[line]; ListCodeLine[line]; END; -- of ListSourceAndCodeLine -- ListSourceLine: PROCEDURE [line: ProgramLineDescPtr] = BEGIN IODefs.WriteDecimal[line.lineNumber]; IODefs.WriteChar[Ascii.SP]; FOR index: vmD.CharIndex IN [line.start .. line.end) DO IODefs.WriteChar[vmD.GetMessageChar[cM, index]]; ENDLOOP; IODefs.WriteLine[""L]; END; -- of ListSourceLine -- LoadOp: PUBLIC BasicOps.OpCodeProc = BEGIN OPEN SegmentDefs; s: STRING _ [40]; extended: BOOLEAN _ FALSE; bcdFile: FileHandle _ NIL; ctrlmod: ControlDefs.ControlModule _ ControlDefs.NullControl; bcd: BcdOps.BcdBase; bcdseg: FileSegmentHandle; oldLoadedConfigs: LoadedConfigRecPtr; OurLoad: PROCEDURE RETURNS [worked: BOOLEAN] = -- This is derived from AltoLoader.Load and incorporates some bug -- fixes and some optimizations. BEGIN pages: CARDINAL; worked _ FALSE; bcdseg _ NewFileSegment[bcdFile, 1, 1, Read]; MakeSwappedIn[bcdseg, DefaultMDSBase, HardUp ! SegmentFault => GO TO bogus]; bcd _ FileSegmentAddress[bcdseg]; IF bcd.versionIdent # BcdDefs.VersionID OR bcd.definitions THEN {Unlock[bcdseg]; GO TO bogus} ELSE IF (pages _ bcd.nPages) > 1 THEN BEGIN Unlock[bcdseg]; MoveFileSegment[bcdseg, 1, pages]; MakeSwappedIn[bcdseg, DefaultMDSBase, HardUp]; bcd _ FileSegmentAddress[bcdseg]; END; worked _ TRUE; EXITS bogus => DeleteFileSegment[bcdseg]; END; -- of OurLoad -- OurUnload: PROCEDURE = BEGIN Unlock[bcdseg]; DeleteFileSegment[bcdseg]; END; -- of OurUnload -- BEGIN s: STRING _ [60]; bv: BasicValue _ Pop[]; IF bv.type # string THEN RunTimeError["No string for LOAD command"L]; String.AppendString[s, bv.stringValue]; Storage.FreeString[bv.stringValue]; String.AppendString[s, ".bcd"L]; bcdFile _ NewFile[s, Read, OldFileOnly ! FileNameError => GOTO BadName]; LockFile[bcdFile]; IF ~OurLoad[ ! InsufficientVM => GO TO out] THEN GO TO cantExecute; ctrlmod _ LoaderOps.New[bcd, TRUE, FALSE ! LoaderOps.BadCode => GO TO cantExecute; LoaderOps.InvalidFile, LoaderOps.VersionMismatch => GO TO BadVersion; String.StringBoundsFault, LoaderOps.FileNotFound => GO TO out; InsufficientVM => {OurUnload[]; GO TO out} ]; IF ctrlmod = ControlDefs.NullControl THEN GO TO cantExecute; FrameOps.Start[ctrlmod ! UNWIND => FrameDefs.UnNewConfig[ctrlmod.frame]]; oldLoadedConfigs _ loadedConfigs; loadedConfigs _ Storage.Node[SIZE[LoadedConfigRec]]; loadedConfigs^ _ LoadedConfigRec[oldLoadedConfigs, ctrlmod, bcdFile]; EXITS BadName => RunTimeError["Bad name in LOAD command."L]; BadVersion => RunTimeError["Mismatched version in loaded module."L]; cantExecute, out => IF bcdFile ~= NIL THEN {SegmentDefs.UnlockFile[bcdFile]; SegmentDefs.ReleaseFile[bcdFile]}; END; RETURN[FALSE, pc]; END; -- of LoadOp -- MulOp: PUBLIC BasicOps.OpCodeProc = BEGIN RETURN[BasicOps.ArithmeticOp[mul], pc]; END; -- of MulOp -- NEOp: PUBLIC BasicOps.OpCodeProc = BEGIN PushInteger[IF PopAndCompare[] # 0 THEN 1 ELSE 0]; RETURN[FALSE, pc]; END; -- of NEOp -- NextOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; node: NextStackPtr; [varPtr, newPc] _ FetchPointer[cm, pc]; IF nextStackHead = NIL OR nextStackHead.varPtr # varPtr THEN RunTimeError["Unmatched NEXT statement!"L]; Push[nextStackHead.varPtr.value]; Push[nextStackHead.stepValue]; [] _ BasicOps.ArithmeticOp[add]; nextStackHead.varPtr.value _ Pop[]; IF nextStackHead.stepDirection = Compare[@nextStackHead.varPtr.value, @nextStackHead.finalValue] THEN BEGIN node _ nextStackHead; nextStackHead _ node.next; Storage.Free[node]; currentProgLine _ currentProgLine.next; END ELSE currentProgLine _ nextStackHead.progLine.next; RETURN[TRUE, newPc]; END; -- of NextOp -- NormalOp: PUBLIC BasicOps.OpCodeProc = BEGIN autoOn _ FALSE; RETURN[FALSE, pc]; END; -- of NormalOp -- NotOp: PUBLIC BasicOps.OpCodeProc = BEGIN bv: BasicValue; i: CARDINAL; bv _ Pop[]; i _ ConvertToCardinal[@bv]; PushInteger[IF i = 1 THEN 0 ELSE 1]; RETURN[FALSE, pc]; END; -- of NotOp -- OnGoToOp: PUBLIC BasicOps.OpCodeProc = BEGIN BasicOps.GoToGuts[ChooseLineNumber[cm, pc]]; RETURN[TRUE, pc]; END; -- of OnGoToOp -- OnGoSubOp: PUBLIC BasicOps.OpCodeProc = BEGIN BasicOps.GoSubGuts[ChooseLineNumber[cm, pc]]; RETURN[TRUE, pc]; END; -- of OnGoSubOp -- OrOp: PUBLIC BasicOps.OpCodeProc = BEGIN bv1, bv2: BasicValue; i1, i2: CARDINAL; bv2 _ Pop[]; bv1 _ Pop[]; i1 _ ConvertToCardinal[@bv1]; i2 _ ConvertToCardinal[@bv1]; PushInteger[IF i1 = 1 OR i2 = 1 THEN 1 ELSE 0]; RETURN[FALSE, pc]; END; -- of OrOp -- Pop: PUBLIC PROCEDURE RETURNS [bv: BasicValue] = BEGIN IF stackPointer = 0 THEN ERROR; bv _ stack[stackPointer]; stackPointer _ stackPointer - 1; END; -- of Pop -- PopAndCompare: PUBLIC PROCEDURE RETURNS [result: INTEGER] = BEGIN bv1, bv2: BasicValue; bv2 _ Pop[]; bv1 _ Pop[]; RETURN[Compare[@bv1, @bv2]]; END; -- of PopAndCompare -- PopOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; bv: BasicValue; [varPtr, newPc] _ FetchPointer[cm, pc]; bv _ Pop[]; IF (bv.type = string) # (varPtr.value.type = string) THEN RunTimeError["Assignment between numeric and string!"L]; IF bv.type = string THEN BEGIN target: STRING _ varPtr.value.stringValue; target.length _ MIN[bv.stringValue.length, target.maxlength]; FOR i: CARDINAL IN [0 .. target.length) DO target[i] _ bv.stringValue[i]; ENDLOOP; Storage.FreeString[bv.stringValue]; END ELSE varPtr.value _ bv; RETURN[FALSE, newPc]; END; -- of PopOp -- PrintOp: PUBLIC BasicOps.OpCodeProc = BEGIN bv: BasicValue; realString: STRING _ [40]; bv _ Pop[]; SELECT bv.type FROM integer => WriteLongDecimal[bv.integerValue]; real => {Real.AppendReal[realString, bv.realValue]; IODefs.WriteString[realString]}; string => {IODefs.WriteString[bv.stringValue]; Storage.FreeString[bv.stringValue]}; ENDCASE; RETURN[FALSE, pc]; END; -- of PrintOp -- Push: PUBLIC PROCEDURE [vp: BasicValue] = BEGIN stackPointer _ stackPointer + 1; IF stackPointer >= stackLimit THEN ERROR; IF vp.type = string THEN vp.stringValue _ Storage.CopyString[vp.stringValue]; stack[stackPointer] _ vp; END; -- of Push -- PushInteger: PUBLIC PROCEDURE [i: INTEGER] = BEGIN bv: BasicValue _ BasicValue[integer, integer[integerValue: i]]; Push[bv]; END; -- of PushInteger -- PushIOp: PUBLIC BasicOps.OpCodeProc = BEGIN bv: BasicValue; byte: BasicOps.Byte; [byte, newPc] _ FetchByte[cm, pc]; bv _ BasicValue[integer, integer[integerValue: byte]]; Push[bv]; RETURN[FALSE, newPc]; END; -- of PushIOp -- PushOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; [varPtr, newPc] _ FetchPointer[cm, pc]; Push[varPtr.value]; RETURN[FALSE, newPc]; END; -- of PushOp -- QuitOp: PUBLIC BasicOps.OpCodeProc = BEGIN UNTIL loadedConfigs = NIL DO UnNewLastConfig[]; ENDLOOP; ERROR QuitSignal; END; -- of QuitOp -- ReadOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; DO IF noMoreData THEN RunTimeError["No more data!"L]; IF dataLinePc = NIL THEN BEGIN dataLinePc _ SearchForOpCodeByLines[cm, programLineDescHead, BasicOps.Data]; IF dataLinePc = NIL THEN noMoreData _ TRUE ELSE dataItem _ 0; LOOP; END; [varPtr, ] _ FetchPointer[cm, dataLinePc.codeStart + 1 + dataItem * 2]; IF varPtr = LOOPHOLE[0] THEN BEGIN dataLinePc _ SearchForOpCodeByLines[cm, dataLinePc.next, BasicOps.Data]; IF dataLinePc = NIL THEN noMoreData _ TRUE ELSE dataItem _ 0; LOOP; END; Push[varPtr.value]; dataItem _ dataItem + 1; EXIT; ENDLOOP; RETURN[FALSE, pc]; END; -- of ReadOp -- RenumberOp: PUBLIC BasicOps.OpCodeProc = BEGIN END; -- of RenumberOp -- RestoreOp: PUBLIC BasicOps.OpCodeProc = BEGIN dataItem _ 0; dataLinePc _ NIL; noMoreData _ FALSE; RETURN[FALSE, pc]; END; -- of RestoreOp -- ReturnOp: PUBLIC BasicOps.OpCodeProc = BEGIN gssp: GoSubStackPtr _ goSubHead; IF gssp = NIL THEN RunTimeError["Unmatched RETURN statement!"L]; currentProgLine _ gssp.returnPc.next; goSubHead _ gssp.next; Storage.Free[gssp]; RETURN[TRUE, newPc]; END; -- of ReturnOp -- RunOp: PUBLIC BasicOps.OpCodeProc = BEGIN bv: BasicValue _ Pop[]; startLine: CARDINAL _ ConvertToCardinal[@bv]; startPc: ProgramLineDescPtr _ programLineDescHead; IF startLine # 0 THEN UNTIL startPc = NIL OR startPc.lineNumber = startLine DO startPc _ startPc.next; ENDLOOP; IF startPc = NIL THEN RunTimeError["Can't find line number to start RUN."L]; currentProgLine _ startPc; optionBaseCalled _ FALSE; optionBase _ 0; noMoreInput _ FALSE; [ , ] _ RestoreOp[cm, pc]; UNTIL currentProgLine = NIL DO InterpretCode[codeCm, currentProgLine.codeStart ! RunTimeErrorSignal => EXIT]; ENDLOOP; currentProgLine _ NIL; RETURN[TRUE, pc]; END; -- of RunOp -- SearchForOpCodeByLines: PUBLIC PROCEDURE [cm: vmD.VirtualMessagePtr, pc: ProgramLineDescPtr, opcode: BasicOps.Byte] RETURNS [newPc: ProgramLineDescPtr] = -- Looks for a line whose first compiled opcode is "opcode". The search begins with "pc". -- If not found, then returns NIL. BEGIN FOR newPc _ pc, newPc.next UNTIL newPc = NIL DO IF vmD.GetMessageChar[cm, newPc.codeStart] = LOOPHOLE[opcode] THEN EXIT; ENDLOOP; END; -- of SearchForOpCodeByLines -- StopOp: PUBLIC BasicOps.OpCodeProc = BEGIN contPc _ currentProgLine.next; currentProgLine _ NIL; RETURN[TRUE, pc]; END; -- of StopOp -- Str1PopOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; bvString: BasicValue _ Pop[]; string: STRING _ bvString.stringValue; target: STRING; bv: BasicValue _ Pop[]; index: CARDINAL _ ConvertToCardinal[@bv]; length, maxLength: CARDINAL; index _ index - 1; IF bvString.type # string THEN RunTimeError["Assignment of numeric to substring!"L]; [varPtr, newPc] _ FetchPointer[cm, pc]; IF varPtr.value.type # string THEN RunTimeError["Substring operation on numeric value!"L]; target _ varPtr.value.stringValue; maxLength _ target.maxlength; IF index ~IN [0 .. maxLength) THEN RunTimeError["Substring index out of bounds!"L]; length _ MIN[maxLength - index, string.length]; FOR i: CARDINAL IN [target.length .. index) DO target[i] _ Ascii.SP; ENDLOOP; target.length _ index + length; FOR i: CARDINAL IN [0 .. length) DO target[index + i] _ string[i]; ENDLOOP; Storage.FreeString[string]; RETURN[FALSE, newPc]; END; -- of Str1PopOp -- Str1PushOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; string: STRING; bv: BasicValue _ Pop[]; index: CARDINAL _ ConvertToCardinal[@bv]; length, maxLength: CARDINAL; index _ index - 1; [varPtr, newPc] _ FetchPointer[cm, pc]; IF varPtr.value.type # string THEN RunTimeError["Substring operation on numeric value!"L]; maxLength _ varPtr.value.stringValue.maxlength; IF index ~IN [0 .. maxLength) THEN RunTimeError["Substring index out of bounds!"L]; length _ IF varPtr.value.stringValue.length > index THEN varPtr.value.stringValue.length - index ELSE 0; string _ Storage.String[length]; string.length _ length; FOR i: CARDINAL IN [0 .. length) DO string[i] _ varPtr.value.stringValue[index + i]; ENDLOOP; bv _ BasicValue[type: string, varPart: string[stringValue: string]]; Push[bv]; RETURN[FALSE, newPc]; END; -- of Str1PushOp -- Str2PopOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; bvString: BasicValue _ Pop[]; string: STRING _ bvString.stringValue; bv2: BasicValue _ Pop[]; bv1: BasicValue _ Pop[]; index1: CARDINAL _ ConvertToCardinal[@bv1]; index2: CARDINAL _ ConvertToCardinal[@bv2]; target: STRING; length, maxLength: CARDINAL; index1 _ index1 - 1; IF bvString.type # string THEN RunTimeError["Assignment of numeric to substring!"L]; [varPtr, newPc] _ FetchPointer[cm, pc]; IF varPtr.value.type # string THEN RunTimeError["Substring operation on numeric value!"L]; target _ varPtr.value.stringValue; maxLength _ target.maxlength; IF index1 ~IN [0 .. maxLength) OR index2 ~IN [0 .. maxLength] THEN RunTimeError["Substring index out of bounds!"L]; length _ IF index2 > index1 THEN index2 - index1 ELSE 0; FOR i: CARDINAL IN [target.length .. index1) DO target[i] _ Ascii.SP; ENDLOOP; target.length _ MAX[target.length, index1 + length]; FOR i: CARDINAL IN [0 .. MIN[length, string.length]) DO target[index1 + i] _ string[i]; ENDLOOP; FOR i: CARDINAL IN [MIN[length, string.length] .. length) DO target[index1 + i] _ Ascii.SP; ENDLOOP; Storage.FreeString[string]; RETURN[FALSE, newPc]; END; -- of Str2PopOp -- Str2PushOp: PUBLIC BasicOps.OpCodeProc = BEGIN varPtr: VariablePtr; string: STRING; bv2: BasicValue _ Pop[]; bv1: BasicValue _ Pop[]; index1: CARDINAL _ ConvertToCardinal[@bv1]; index2: CARDINAL _ ConvertToCardinal[@bv2]; length, goodLength, maxLength: CARDINAL; index1 _ index1 - 1; [varPtr, newPc] _ FetchPointer[cm, pc]; IF varPtr.value.type # string THEN RunTimeError["Substring operation on numeric value!"L]; maxLength _ varPtr.value.stringValue.maxlength; IF index1 ~IN [0 .. maxLength) OR index2 ~IN [0 .. maxLength] THEN RunTimeError["Substring index out of bounds!"L]; length _ index2 - index1; string _ Storage.String[length]; string.length _ length; goodLength _ IF varPtr.value.stringValue.length <= index1 THEN 0 ELSE IF varPtr.value.stringValue.length <= index2 THEN varPtr.value.stringValue.length - index1 ELSE length; FOR i: CARDINAL IN [0 .. goodLength) DO string[i] _ varPtr.value.stringValue[index1 + i]; ENDLOOP; FOR i: CARDINAL IN [goodLength .. length) DO string[i] _ Ascii.SP; ENDLOOP; bv1 _ BasicValue[type: string, varPart: string[stringValue: string]]; Push[bv1]; RETURN[FALSE, newPc]; END; -- of Str2PushOp -- SubOp: PUBLIC BasicOps.OpCodeProc = BEGIN RETURN[BasicOps.ArithmeticOp[sub], pc]; END; -- of SubOp -- TabOp: PUBLIC BasicOps.OpCodeProc = BEGIN arg: CARDINAL; bv: BasicValue; bv _ Pop[]; arg _ ConvertToCardinal[@bv]; SELECT arg FROM 255 => IODefs.WriteLine[""L]; 254 => IODefs.WriteString[" "L]; ENDCASE => THROUGH [1 .. arg] DO IODefs.WriteChar[Ascii.SP] ENDLOOP; RETURN[FALSE, pc]; END; -- of TabOp -- UnaryMinusOp: PUBLIC BasicOps.OpCodeProc = BEGIN bv: BasicValue _ Pop[]; SELECT bv.type FROM real => bv.realValue _ -bv.realValue; integer => bv.integerValue _ -bv.integerValue; ENDCASE => RunTimeError["Unary minus of non-numeric value"L]; Push[bv]; RETURN[FALSE, pc]; END; -- of UnaryMinusOp -- UnNewLastConfig: PROCEDURE = BEGIN newLoadedConfigs: LoadedConfigRecPtr; IF loadedConfigs = NIL THEN RETURN; newLoadedConfigs _ loadedConfigs.next; FrameDefs.UnNewConfig[loadedConfigs.cm.frame]; IF loadedConfigs.bcdFile ~= NIL THEN {SegmentDefs.UnlockFile[loadedConfigs.bcdFile]; SegmentDefs.ReleaseFile[loadedConfigs.bcdFile]}; Storage.Free[loadedConfigs]; loadedConfigs _ newLoadedConfigs; END; -- of UnNewLastConfig -- UsingOp: PUBLIC BasicOps.OpCodeProc = BEGIN [ , newPc] _ FetchPointer[cm, pc]; RETURN[FALSE, newPc]; END; -- of UsingOp -- WriteLongDecimal: PROCEDURE [n: Numeric] = BEGIN s: STRING _ [20]; String.AppendLongDecimal[s, n]; IODefs.WriteString[s]; END; -- of WriteLongDecimal -- END. -- of BasicInterpB --(1792)\f1