public string[] GetLines(int startLine, int endLine) { if (_protected) { throw new ReplRuntimeException(ReplExceptionCode.IllegalFunctionCall); } // 65529 is max insertable line number for GW-BASIC 3.23. // however, 65530-65535 are executed if present in tokenised form. // in GW-BASIC, 65530 appears in LIST, 65531 and above are hidden // sort by positions, not line numbers! var linesByPostion = _lineNumberMap.Where(x => x.Key >= startLine && x.Key <= endLine) .Select(x => x.Value).OrderBy(x => x); var lines = new List <string>(); var current = Bytecode.Position; foreach (var position in linesByPostion) { Bytecode.Seek(position + 1, SeekOrigin.Begin); lines.Add(Tokeniser.DetokeniseLine(Bytecode).Text); } Bytecode.Seek(current, SeekOrigin.Begin); return(lines.ToArray()); }
/// <summary> /// Update line number dictionary after deleting lines. /// </summary> private void UpdateLineMap(CodePosition position, int length) { // subtract length of line we replaced length -= (int)(position.AfterPosition - position.StartPosition); var address = Position + 1 + position.AfterPosition; Bytecode.Seek(position.AfterPosition + length + 1, SeekOrigin.Begin); // pass \x00 while (true) { var nextAddressStr = Bytecode.Read(2); if (nextAddressStr.Length < 2 || nextAddressStr == "\0\0") { break; } var nextAddress = nextAddressStr.ToUnsignedInteger(); Bytecode.Seek(-2, SeekOrigin.Current); Bytecode.Write((nextAddress + length).ToBasicUnsignedInteger()); Bytecode.Seek(nextAddress - address - 2, SeekOrigin.Current); address = nextAddress; } // update line number dict foreach (var line in position.Deleteable) { _lineNumberMap.Remove(line); } foreach (var line in position.Beyond) { _lineNumberMap[line] += length; } }
/// <summary> /// Store the given tokenized line buffer. /// </summary> private void StoreLine(Stream stream) { if (_protected) { throw new ReplRuntimeException(ReplExceptionCode.IllegalFunctionCall); } stream.Seek(1, SeekOrigin.Begin); var scanLine = stream.ReadLineNumber(); // check if stream is an empty line after the line number var nextNonWhitespace = stream.SkipWhitespace(); var empty = nextNonWhitespace == -1 || nextNonWhitespace == '\0'; var codePosition = FindCodePosition(scanLine, scanLine); if (empty && codePosition.Deleteable.Length == 0) { throw new ReplRuntimeException(ReplExceptionCode.UndefinedLineNumber); } // read the remainder of the program into a buffer to be pasted back after the write Bytecode.Seek(codePosition.AfterPosition, SeekOrigin.Begin); var rest = Bytecode.ReadToEnd(); Bytecode.Seek(codePosition.StartPosition, SeekOrigin.Begin); var length = 0; // write the line buffer to the program buffer if (!empty) { // set offsets length = stream.ReadToEnd().Length; stream.Seek(0, SeekOrigin.Begin); // pass \x00\xC0\xDE Bytecode.WriteByte(0); Bytecode.Write(((int)(Position + 1 + codePosition.StartPosition + length)).ToBasicUnsignedInteger()); Bytecode.WriteByte((byte)stream.ReadByte()); } // write back the remainder of the program Truncate(rest); // update all next offsets by shifting them by the length of the added line UpdateLineMap(codePosition, length); if (!empty) { _lineNumberMap[scanLine] = codePosition.StartPosition; } _lastStored = scanLine; }
/// <summary> /// Deletes the stored program within specified range /// </summary> /// <param name="startLine">Starting line</param> /// <param name="lastLine">Ending line</param> public void Delete(int startLine, int lastLine) { var codePosition = FindCodePosition(startLine, lastLine); if (codePosition.Deleteable.Length == 0) // no lines selected { throw new ReplRuntimeException(ReplExceptionCode.IllegalFunctionCall); } Bytecode.Seek(codePosition.AfterPosition, SeekOrigin.Begin); var rest = Bytecode.ReadToEnd(); Bytecode.Seek(codePosition.StartPosition, SeekOrigin.Begin); Truncate(rest); UpdateLineMap(codePosition, 0); }
public int this[long offset] { get { offset -= Position; var bytes = Bytecode.ToArray(); if (offset >= bytes.Length) { return(-1); } return(bytes[offset]); } set { if (!AllowCodePoke) { Trace.TraceWarning("Ignored POKE into program code"); return; } offset -= Position; var position = Bytecode.Position; // move pointer to end Bytecode.Seek(0, SeekOrigin.End); if (offset > Bytecode.Position) { Bytecode.Write(new string('\0', (int)(offset - Bytecode.Position))); } else { Bytecode.Seek(offset, SeekOrigin.Begin); } Bytecode.WriteByte((byte)value); // restore program pointer Bytecode.Seek(position, SeekOrigin.Begin); RebuildLineNumbers(); } }
/// <summary> /// Preparse to build line number dictionary. /// </summary> private void RebuildLineNumbers() { _lineNumberMap.Clear(); var offsets = new List <long>(); Bytecode.Seek(0, SeekOrigin.Begin); var scanPosition = 0L; while (true) { Bytecode.ReadByte(); // pass \x00 var scanline = Bytecode.ReadLineNumber(); if (scanline == -1) { // if parse_line_number returns -1, it leaves the stream pointer here: 00 _00_ 00 1A break; } _lineNumberMap[scanline] = scanPosition; Bytecode.SkipUntilLineEnd(); scanPosition = Bytecode.Position; offsets.Add(scanPosition); } _lineNumberMap[BasicLastLineNumber] = scanPosition; // rebuild offsets Bytecode.Seek(0, SeekOrigin.Begin); var lastPosition = 0L; foreach (var postion in offsets) { Bytecode.ReadByte(); Bytecode.Write(((int)(Position + 1 + postion)).ToBasicUnsignedInteger()); Bytecode.Read((int)(postion - lastPosition - 3)); lastPosition = postion; } // ensure program is properly sealed - last offset must be 00 00. keep, but ignore, anything after. Bytecode.Write("\0\0\0"); }
/// <summary> /// Save the program to stream stream in defined mode. /// </summary> /// <param name="stream">Stream to which to save</param> /// <param name="mode">Mode in which to save program</param> public void SaveTo(Stream stream, ProgramMode mode) { if (mode != ProgramMode.Protected && _protected) { throw new ReplRuntimeException(ReplExceptionCode.IllegalFunctionCall); } var current = Bytecode.Position; // skip first \x00 in bytecode Bytecode.Seek(1, SeekOrigin.Begin); switch (mode) { case ProgramMode.Binary: Bytecode.CopyTo(stream); break; case ProgramMode.Protected: ProtectedProgramEncoder.Encode(Bytecode).CopyTo(stream); break; case ProgramMode.Ascii: while (true) { var output = Tokeniser.DetokeniseLine(Bytecode); if (output.LineNumber == -1 || output.LineNumber > MaxLineNumber) { break; } stream.Write(output.Text); } break; } Bytecode.Seek(current, SeekOrigin.Begin); }