#!/bin/bash # mcdiff.sh — verification harness for the src/compiler re-read. # Stage 1 (runs HERE, no VM needed): static consistency checks. # Stage 2 (needs the Reloaded VM + original compiler): true MCD diff; # procedure documented below. Exit nonzero on any stage-1 failure. # # Stage 2 procedure (once, per module MOD in dependency order # Compiler Scanner SymTab Errors CodeGen Express Statemen Pass1): # 1. Build the VM: cd Turbo-Modula-2-Reloaded-main/vm_linux && make # 2. Stage sources: copy src/compiler/X.{DEF,MOD} onto m2.dsk # (mtools: mcopy -i m2.dsk, or sudo mount -o loop). # File names must be 8+3 uppercase (COMPILER.DEF etc. already are). # 3. Boot: ./m2_disk m2.dsk ; at the M2 prompt run the resident # compiler on each MOD (c ), producing X.MCD on the image. # 4. Extract: mcopy -i m2.dsk ::COMPILER.DIR/X.MCD ./build/ # 5. Disassemble both: unassemble.c built from Reversing-Turbo-Modula2-main # (cc -o unassemble unassemble.c), run on original .MCD from # Turbo-Modula-2-Reloaded-main/disk_content/COMPILER.DIR/ and on build/. # NOTE 2026-10-03: the VM needs a real terminal (termios raw mode); plain # pipes hang. Drive it under a pty, e.g. # script -qec "./vm_linux/m2 m2.dsk" /dev/null # or expect(1), sending keys with delays (CR-terminated lines). mtools is # NOT installed in this environment — staging currently needs manual mount. # 6. Diff against Reversing-Turbo-Modula2-main/MCode_disassembly/.txt. # Byte-identical MCD (hence identical disassembly modulo addresses) = # proof of a faithful rename/decompile. Record result per module in # docs/compiler/70-rename-plan.md style commit notes. set -u SRC="${1:-src/compiler}" fail=0 say() { printf '%s\n' "$*"; } err() { printf 'FAIL: %s\n' "$*" >&2; fail=1; } say "== stage 1a: numbered identifiers (must be zero outside comments) ==" # strip (* *) comments, then look for procN/globalN/paramN/localN/PROC N tmp=$(mktemp -d) for f in "$SRC"/*.MOD "$SRC"/*.DEF; do [ -f "$f" ] || continue python3 - "$f" "$tmp" <<'PY' import re, sys, pathlib src = pathlib.Path(sys.argv[1]).read_text() nos = re.sub(r'\(\*.*?\*\)', '', src, flags=re.S) # drop (* *) comments hits = re.findall(r'(? 1 and k not in fwds] missing = [w for w in fwds if w not in defs] if dupes or missing: bad = True print(f'{f.name}: dupes={dupes} missing-defs={missing}') print('FORWARD-OK' if not bad else 'FORWARD-BAD') PY say "== stage 1c: FIXME inventory ==" grep -c "FIXME" "$SRC"/*.MOD "$SRC"/*.DEF 2>/dev/null || true say "== stage 1d: unknown cross-module callees (spot check) ==" # every Module.Proc call should resolve to a renamed export or a flagged FIXME nearby python3 - "$SRC" <<'PY' import re, sys, pathlib known = set() for f in pathlib.Path(sys.argv[1]).glob('*.DEF'): nos = re.sub(r'\(\*.*?\*\)', '', f.read_text(), flags=re.S) mod = f.stem.capitalize() for m in re.findall(r'PROCEDURE\s+(\w+)', nos): known.add(f'{mod}.{m}') for m in re.findall(r'^\s{4}(\w+)\s*[:,\[]', nos, re.M): known.add(f'{mod}.{m}') # + known drafts without DEF for mod, procs in { 'Express': ['ParseExpression','ParseSimpleExpression','EmitRangeCheck','EmitCompare','ParseDesignatorTail','ParseTerm','ParseFactor','ParseSetCons','ParseFuncCall','ParseTypeCast','ExpandStdProc43','ExpandStdProc44','ExpandStdProc45','ExpandStdProc46','ParseSetOrCastTail','LoadOperand','StoreOperand','FoldRelation','CheckRelationTypes','GetConstVal','GetTypeDesc','ParseAssignment','IsOrdinal','IsSet','BaseTypeOf','FoldConstOp','ConstToCard','ConstToInt','MatchOpClass','CheckAssignable','ParseSelector','PushExprDesc','PopExprDesc','CentralError','EvalConstExpr'], 'Statemen': ['ParseStatement','GuardedStatSeq','ParseBeginBlock','ParseCaseStmt','ParseCaseTail','ParseFor','ParseWhile','ParseRepeat','ParseIf','ParseLoop','ParseExit','ParseResult','ParseWith','ParseReturn','CalcMemOffset','SyncToStatStart','ParseStatSeqLoop','EmitJumpFixup','NextSymWrap','CloseBlockChain','EmitNameHash','EmitComBytes'], 'Pass1': ['ParseType','ParseFormalParams','ParseValParam','ParseVarParam','ParseFieldList','EvalConstExpr','CheckConformant','ParseRecordType','CloseScopeFixup','ParseConstDecl','ParseVarDecl','GetIdent','ParseImportItem','OpenScopeEmit','TypeSizeRec','ParseProcedureDecl','ParseModuleHead','ParseExportList','ParseOneDecl','ParseDeclarations','ParseImportFrom','ParseModuleBlock','ParseProcBody','ParseMainModule','WriteMcdHeader','WriteOutputFiles','EmitConstVal','EmitTypeDesc','PushTempMark','PopTempMark','AllocSymEntry','LinkSymEntry','InsertSetConst','CondInsert','InsertEntry','CheckForwardResolved','Pass1Main','InitPass1','EmitModuleProlog','ParseDeclSync','ParseConstOrTypeArm','ParseModuleArm','TryParseDecl','ParseImportScope','CappedStrLen'], 'SymTab': ['FindModule','EnterModule','LookupChain','MatchSym','SymbolAssert','CheckNode','NewString','GetName','PutAttr','GetAttr','AllocSym','EnterChained','EnterIdent','UnlinkScope','InitHeaps','InsertBuiltin','EnterStandards','LoadSymFile','FindExport','ListExports','ImportOne','ImportAll','SkipQualident','DefineDefinitionModule','BuildBitsetConst','DefineProgramModule','InitSymbolTables','NoteImported','DoImportList','CompileBody','CheckForwards','TryCompile','CompileDispatch','Checksum7w','SaveSymbolFile','RestoreAfterEdit'], }.items(): known.update(f'{mod}.{p}' for p in procs) import collections susp = collections.Counter() for f in sorted(pathlib.Path(sys.argv[1]).glob('*.MOD')): for i, line in enumerate(f.read_text().splitlines(), 1): code = line.split('(*')[0] for m in re.findall(r'\b(Scanner|Errors|CodeGen|Compiler|SymTab|Express|Statemen|Pass1|Loader|Files|Texts|Terminal|ComLine|Doubles|Editor|Storage|System)\.(\w+)', code): q = f'{m[0]}.{m[1]}' if q not in known and m[1] not in ('WriteString','WriteLn','WriteChar','WriteCard','SetCol','ReadBytes','WriteBytes','SetPos','Open','Close','Create','Delete','Allocate','MARK','RELEASE','MOVE','ADR','BITSET','CARDINAL','ADDRESS','ORD','CHR','CAP','HIGH','NIL','TRUE','TRUE','HALT','RETURN','INC','DEC','INCL','EXCL','IN','ABS','ODD','SIZE','HALT','Close','Create','Delete','Open','ReadBytes','WriteBytes','SetPos','FileSize','NextPos','NoTrailer','ReadByte','WriteByte','ResetSys','UseError','EndError','DiskFull','Call','LibOpen','LibRead','InitLibs','LoadError','firstDrive','Edit','editPosition','editOkFlag','ReadChar','ReadAgain','numCols','commandLine','inName','outName','codepos','execute','RedirectOutput','PromptFor','qcp','qadd','qsub','qmul','qdiv','qneg','qabs','StrToDouble','DerefAliasType','StrLen','FindIdent','InsertSymbol','CopyStringToHeap','Allocate','GetStackMark','CheckStackMark','OpenScope','NewNode','NewSizedNode','DeclareIdentifier','MakeIdentifierNode','ExpectStringLiteral','ChangeFileExtension','GetSym','AcceptSymbol','ExpectSymbol','TestSymbolInSet','NeedIdentifier','ReportError'): susp[(f.name, q)] += 1 for (f, q), n in sorted(susp.items()): print(f'{f}:{q} x{n}') PY if [ "$fail" -ne 0 ]; then say "STAGE 1 FAILED"; exit 1; fi say "STAGE 1 PASSED" rm -rf "$tmp"