M2make.mod 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  1. MODULE M2make;
  2. (* M2make: build-order front-end for the M2 and GNU Modula-2 compilers.
  3. Usage: M2make [-n] [-v] [-c m2|gm2] [-I dir] [-o out]
  4. [--m2bin path] [--shim path] main.mod
  5. Follows IMPORT/FROM imports from main.mod transitively, sorts the
  6. modules topologically, skips the build when the output is newer
  7. than every source, otherwise invokes the selected compiler.
  8. Exits 0 on success (or nothing to do), 1 on build failure,
  9. 2 on usage errors.
  10. Portable source: compiles with gm2 -fiso and with the V3 M2
  11. driver. Only FileIO (console) and M2makeOS (files/time/exec)
  12. are imported; all string handling is NUL-based and local, since
  13. the two compilers' FileIO string helpers differ. *)
  14. IMPORT FileIO, M2makeOS;
  15. CONST
  16. MaxMods = 256;
  17. MaxDeps = 64;
  18. MaxDirs = 16;
  19. MaxArgs = 64;
  20. StackCap = 264;
  21. NameStride = 64;
  22. PathStride = 256;
  23. TYPE
  24. Name = ARRAY [0..63] OF CHAR;
  25. Path = ARRAY [0..255] OF CHAR;
  26. Line = ARRAY [0..2047] OF CHAR;
  27. Cmd = ARRAY [0..2047] OF CHAR;
  28. VAR
  29. nMods: CARDINAL;
  30. (* Flat pools (V3 cannot pass nested-array elements as open-array
  31. actuals, nor double-index; all access is single-indexed here). *)
  32. namePool: ARRAY [0..16383] OF CHAR;
  33. srcPool: ARRAY [0..65535] OF CHAR;
  34. defPool: ARRAY [0..65535] OF CHAR;
  35. dirPool: ARRAY [0..4095] OF CHAR;
  36. modScanned: ARRAY [0..255] OF BOOLEAN;
  37. modExternal: ARRAY [0..255] OF BOOLEAN;
  38. modIsMain: ARRAY [0..255] OF BOOLEAN;
  39. nDep: ARRAY [0..255] OF CARDINAL;
  40. depTab: ARRAY [0..16383] OF INTEGER;
  41. state: ARRAY [0..255] OF CARDINAL;
  42. childPos: ARRAY [0..255] OF CARDINAL;
  43. order: ARRAY [0..255] OF CARDINAL;
  44. nOrder: CARDINAL;
  45. dryRun, verbose, useGm2: BOOLEAN;
  46. outGiven: BOOLEAN;
  47. mainPath, outName, m2bin, shimPath: Path;
  48. nDirs: CARDINAL;
  49. progName: Name;
  50. wantProg: BOOLEAN;
  51. mainIdx: INTEGER;
  52. inComment: CARDINAL;
  53. gPendingFrom, gInImport, gSawHead, gWantName, gFromSeen: BOOLEAN;
  54. tmpA, tmpB: Path;
  55. tmpN: Name;
  56. tmpP, tmpQ: Path;
  57. tmpCmd: Cmd;
  58. segA: Cmd;
  59. (* ---------------- zeroed (NUL) string utilities ---------------- *)
  60. PROCEDURE Len(s: ARRAY OF CHAR): CARDINAL;
  61. (* NUL scan that never reads past HIGH(s): V3 evaluates AND operands
  62. fully (no short-circuit) and traps out-of-range access, and string
  63. literals carry no NUL padding, so the scan must stop by index. *)
  64. VAR i, hi: CARDINAL;
  65. BEGIN
  66. hi := HIGH(s);
  67. i := 0;
  68. WHILE i < hi DO
  69. IF s[i] = CHR(0) THEN RETURN i END;
  70. INC(i)
  71. END;
  72. IF s[hi] = CHR(0) THEN RETURN hi END;
  73. RETURN hi + 1
  74. END Len;
  75. PROCEDURE Zero(VAR s: ARRAY OF CHAR);
  76. VAR i: CARDINAL;
  77. BEGIN
  78. i := 0;
  79. WHILE i <= HIGH(s) DO s[i] := CHR(0); INC(i) END
  80. END Zero;
  81. PROCEDURE Copy(src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR);
  82. VAR i, n: CARDINAL;
  83. BEGIN
  84. Zero(dst);
  85. n := Len(src);
  86. IF n > HIGH(dst) THEN n := HIGH(dst) END;
  87. i := 0;
  88. WHILE i < n DO dst[i] := src[i]; INC(i) END
  89. END Copy;
  90. PROCEDURE Cmp(a, b: ARRAY OF CHAR): INTEGER;
  91. (* Length-aware comparison: also correct when one side is a string
  92. literal (no NUL padding under V3). No AND-access in conditions. *)
  93. VAR i, na, nb: CARDINAL;
  94. BEGIN
  95. na := Len(a);
  96. nb := Len(b);
  97. i := 0;
  98. WHILE (i < na) AND (i < nb) DO
  99. IF a[i] # b[i] THEN
  100. IF a[i] < b[i] THEN RETURN -1 ELSE RETURN 1 END
  101. END;
  102. INC(i)
  103. END;
  104. IF na < nb THEN RETURN -1 END;
  105. IF na > nb THEN RETURN 1 END;
  106. RETURN 0
  107. END Cmp;
  108. PROCEDURE Cat(a, b: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR);
  109. VAR i, k: CARDINAL; done: BOOLEAN;
  110. BEGIN
  111. Zero(dst);
  112. k := 0;
  113. i := 0;
  114. done := FALSE;
  115. WHILE (NOT done) AND (i <= HIGH(a)) AND (k <= HIGH(dst)) DO
  116. IF a[i] = CHR(0) THEN done := TRUE
  117. ELSE dst[k] := a[i]; INC(k); INC(i)
  118. END
  119. END;
  120. i := 0;
  121. done := FALSE;
  122. WHILE (NOT done) AND (i <= HIGH(b)) AND (k <= HIGH(dst)) DO
  123. IF b[i] = CHR(0) THEN done := TRUE
  124. ELSE dst[k] := b[i]; INC(k); INC(i)
  125. END
  126. END
  127. END Cat;
  128. PROCEDURE Sub(src: ARRAY OF CHAR; start, count: CARDINAL;
  129. VAR dst: ARRAY OF CHAR);
  130. VAR j: CARDINAL; done: BOOLEAN;
  131. BEGIN
  132. Zero(dst);
  133. j := 0;
  134. done := FALSE;
  135. WHILE (NOT done) AND (j < count) AND (start + j <= HIGH(src))
  136. AND (j <= HIGH(dst)) DO
  137. IF src[start + j] = CHR(0) THEN done := TRUE
  138. ELSE dst[j] := src[start + j]; INC(j)
  139. END
  140. END
  141. END Sub;
  142. PROCEDURE Lower(src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR);
  143. VAR i: CARDINAL; ch: CHAR; done: BOOLEAN;
  144. BEGIN
  145. Zero(dst);
  146. i := 0;
  147. done := FALSE;
  148. WHILE (NOT done) AND (i <= HIGH(src)) AND (i <= HIGH(dst)) DO
  149. IF src[i] = CHR(0) THEN done := TRUE
  150. ELSE
  151. ch := src[i];
  152. IF (ch >= "A") AND (ch <= "Z") THEN
  153. ch := CHR(ORD(ch) - ORD("A") + ORD("a"))
  154. END;
  155. dst[i] := ch; INC(i)
  156. END
  157. END
  158. END Lower;
  159. PROCEDURE IsLetter(ch: CHAR): BOOLEAN;
  160. BEGIN
  161. RETURN ((ch >= "A") AND (ch <= "Z"))
  162. OR ((ch >= "a") AND (ch <= "z"))
  163. END IsLetter;
  164. PROCEDURE IsDig(ch: CHAR): BOOLEAN;
  165. BEGIN
  166. RETURN (ch >= "0") AND (ch <= "9")
  167. END IsDig;
  168. (* ---------------- flat-pool accessors ---------------- *)
  169. (* Single-indexed pool traffic only (see VAR comment). *)
  170. PROCEDURE GetName(m: CARDINAL; VAR dst: Name);
  171. VAR k: CARDINAL;
  172. BEGIN
  173. k := 0;
  174. WHILE k <= HIGH(dst) DO
  175. dst[k] := namePool[m * NameStride + k];
  176. INC(k)
  177. END
  178. END GetName;
  179. PROCEDURE PutName(m: CARDINAL; src: Name);
  180. VAR k: CARDINAL;
  181. BEGIN
  182. k := 0;
  183. WHILE k <= HIGH(src) DO
  184. namePool[m * NameStride + k] := src[k];
  185. INC(k)
  186. END
  187. END PutName;
  188. PROCEDURE GetSrc(m: CARDINAL; VAR dst: Path);
  189. VAR k: CARDINAL;
  190. BEGIN
  191. k := 0;
  192. WHILE k <= HIGH(dst) DO
  193. dst[k] := srcPool[m * PathStride + k];
  194. INC(k)
  195. END
  196. END GetSrc;
  197. PROCEDURE PutSrc(m: CARDINAL; src: Path);
  198. VAR k: CARDINAL;
  199. BEGIN
  200. k := 0;
  201. WHILE k <= HIGH(src) DO
  202. srcPool[m * PathStride + k] := src[k];
  203. INC(k)
  204. END
  205. END PutSrc;
  206. PROCEDURE GetDef(m: CARDINAL; VAR dst: Path);
  207. VAR k: CARDINAL;
  208. BEGIN
  209. k := 0;
  210. WHILE k <= HIGH(dst) DO
  211. dst[k] := defPool[m * PathStride + k];
  212. INC(k)
  213. END
  214. END GetDef;
  215. PROCEDURE PutDef(m: CARDINAL; src: Path);
  216. VAR k: CARDINAL;
  217. BEGIN
  218. k := 0;
  219. WHILE k <= HIGH(src) DO
  220. defPool[m * PathStride + k] := src[k];
  221. INC(k)
  222. END
  223. END PutDef;
  224. PROCEDURE GetDir(m: CARDINAL; VAR dst: Path);
  225. VAR k: CARDINAL;
  226. BEGIN
  227. k := 0;
  228. WHILE k <= HIGH(dst) DO
  229. dst[k] := dirPool[m * PathStride + k];
  230. INC(k)
  231. END
  232. END GetDir;
  233. PROCEDURE PutDir(m: CARDINAL; src: Path);
  234. VAR k: CARDINAL;
  235. BEGIN
  236. k := 0;
  237. WHILE k <= HIGH(src) DO
  238. dirPool[m * PathStride + k] := src[k];
  239. INC(k)
  240. END
  241. END PutDir;
  242. (* ---------------- console output ---------------- *)
  243. PROCEDURE Out(s: ARRAY OF CHAR);
  244. (* Character by character: V3 FileIO.WriteString emits the whole
  245. descriptor capacity (including NUL padding), so loop instead. *)
  246. VAR i, n: CARDINAL;
  247. BEGIN
  248. n := Len(s);
  249. i := 0;
  250. WHILE i < n DO
  251. FileIO.Write(FileIO.StdOut, s[i]);
  252. INC(i)
  253. END
  254. END Out;
  255. PROCEDURE OutLn;
  256. BEGIN
  257. FileIO.WriteLn(FileIO.StdOut)
  258. END OutLn;
  259. PROCEDURE OutInt(i: INTEGER);
  260. BEGIN
  261. FileIO.WriteInt(FileIO.StdOut, i, 0)
  262. END OutInt;
  263. PROCEDURE Die(s: ARRAY OF CHAR; code: INTEGER);
  264. BEGIN
  265. Out(s); OutLn;
  266. M2makeOS.ExitNow(code)
  267. END Die;
  268. PROCEDURE Usage;
  269. BEGIN
  270. Out("Usage: M2make [-n] [-v] [-c m2|gm2] [-I dir] [-o out]"); OutLn;
  271. Out(" [--m2bin path] [--shim path] main.mod"); OutLn
  272. END Usage;
  273. (* ---------------- module table ---------------- *)
  274. PROCEDURE FindMod(n: ARRAY OF CHAR): INTEGER;
  275. VAR i: CARDINAL;
  276. BEGIN
  277. i := 0;
  278. WHILE i < nMods DO
  279. GetName(i, tmpN);
  280. IF Cmp(tmpN, n) = 0 THEN RETURN VAL(INTEGER, i) END;
  281. INC(i)
  282. END;
  283. RETURN -1
  284. END FindMod;
  285. PROCEDURE AddMod(n: ARRAY OF CHAR): INTEGER;
  286. BEGIN
  287. IF FindMod(n) >= 0 THEN RETURN FindMod(n) END;
  288. IF nMods >= MaxMods THEN Die("M2make: too many modules", 1) END;
  289. Copy(n, tmpN);
  290. PutName(nMods, tmpN);
  291. Zero(tmpP);
  292. PutSrc(nMods, tmpP);
  293. PutDef(nMods, tmpP);
  294. modScanned[nMods] := FALSE;
  295. modExternal[nMods] := FALSE;
  296. modIsMain[nMods] := FALSE;
  297. nDep[nMods] := 0;
  298. INC(nMods);
  299. RETURN VAL(INTEGER, nMods - 1)
  300. END AddMod;
  301. PROCEDURE AddDep(m, d: INTEGER);
  302. VAR mc: CARDINAL; k: CARDINAL;
  303. BEGIN
  304. IF (m < 0) OR (d < 0) OR (m = d) THEN RETURN END;
  305. mc := VAL(CARDINAL, m);
  306. k := 0;
  307. WHILE k < nDep[mc] DO
  308. IF depTab[mc * MaxDeps + k] = d THEN RETURN END;
  309. INC(k)
  310. END;
  311. IF nDep[mc] >= MaxDeps THEN Die("M2make: too many imports", 1) END;
  312. depTab[mc * MaxDeps + nDep[mc]] := d;
  313. INC(nDep[mc])
  314. END AddDep;
  315. PROCEDURE AddDepName(midx: INTEGER; n: ARRAY OF CHAR);
  316. VAR d: INTEGER;
  317. BEGIN
  318. d := AddMod(n);
  319. AddDep(midx, d)
  320. END AddDepName;
  321. (* ---------------- path handling ---------------- *)
  322. PROCEDURE DirOf(p: ARRAY OF CHAR; VAR dir: ARRAY OF CHAR);
  323. VAR i, n, k: CARDINAL;
  324. BEGIN
  325. Zero(dir);
  326. n := Len(p);
  327. k := 0;
  328. i := 0;
  329. WHILE i < n DO
  330. IF p[i] = "/" THEN k := i + 1 END;
  331. INC(i)
  332. END;
  333. i := 0;
  334. WHILE (i < k) AND (i <= HIGH(dir)) DO
  335. dir[i] := p[i]; INC(i)
  336. END
  337. END DirOf;
  338. PROCEDURE TryVariant(dir, base, ext: ARRAY OF CHAR;
  339. VAR out: ARRAY OF CHAR);
  340. VAR full: Path;
  341. BEGIN
  342. Zero(out);
  343. IF Len(dir) = 0 THEN
  344. Cat(base, ext, full)
  345. ELSE
  346. IF Len(dir) + 1 + Len(base) + Len(ext) > HIGH(full) THEN RETURN END;
  347. IF dir[Len(dir) - 1] = "/" THEN
  348. Cat(dir, base, tmpA);
  349. Cat(tmpA, ext, full)
  350. ELSE
  351. Cat(dir, "/", tmpA);
  352. Cat(tmpA, base, tmpB);
  353. Cat(tmpB, ext, full)
  354. END
  355. END;
  356. IF M2makeOS.FileExists(full) THEN Copy(full, out) END
  357. END TryVariant;
  358. PROCEDURE ResolveModule(n: ARRAY OF CHAR; VAR dp, mp: Path): BOOLEAN;
  359. VAR di: CARDINAL; low: Name; cand: Path; found: BOOLEAN;
  360. BEGIN
  361. Zero(dp);
  362. Zero(mp);
  363. Lower(n, low);
  364. found := FALSE;
  365. di := 0;
  366. WHILE di < nDirs DO
  367. GetDir(di, tmpQ);
  368. TryVariant(tmpQ, n, ".def", cand);
  369. IF Len(cand) > 0 THEN Copy(cand, dp); found := TRUE END;
  370. TryVariant(tmpQ, n, ".mod", cand);
  371. IF Len(cand) > 0 THEN Copy(cand, mp); found := TRUE END;
  372. TryVariant(tmpQ, low, ".def", cand);
  373. IF (Len(dp) = 0) AND (Len(cand) > 0) THEN
  374. Copy(cand, dp); found := TRUE
  375. END;
  376. TryVariant(tmpQ, low, ".mod", cand);
  377. IF (Len(mp) = 0) AND (Len(cand) > 0) THEN
  378. Copy(cand, mp); found := TRUE
  379. END;
  380. INC(di)
  381. END;
  382. RETURN found
  383. END ResolveModule;
  384. (* ---------------- import scanner ---------------- *)
  385. PROCEDURE HandleTok(tok: ARRAY OF CHAR; midx: INTEGER);
  386. BEGIN
  387. IF Cmp(tok, "FROM") = 0 THEN
  388. gPendingFrom := TRUE;
  389. gInImport := FALSE;
  390. gSawHead := FALSE;
  391. gWantName := FALSE;
  392. gFromSeen := FALSE;
  393. RETURN
  394. END;
  395. IF Cmp(tok, "IMPORT") = 0 THEN
  396. IF gPendingFrom OR gFromSeen THEN
  397. (* FROM M IMPORT ... : following names are items, not modules *)
  398. gPendingFrom := FALSE;
  399. gFromSeen := FALSE
  400. ELSE gInImport := TRUE
  401. END;
  402. gSawHead := FALSE;
  403. gWantName := FALSE;
  404. RETURN
  405. END;
  406. IF Cmp(tok, "DEFINITION") = 0 THEN
  407. gSawHead := TRUE;
  408. gPendingFrom := FALSE;
  409. gInImport := FALSE;
  410. gWantName := FALSE;
  411. gFromSeen := FALSE;
  412. RETURN
  413. END;
  414. IF Cmp(tok, "IMPLEMENTATION") = 0 THEN
  415. gSawHead := TRUE;
  416. gPendingFrom := FALSE;
  417. gInImport := FALSE;
  418. gWantName := FALSE;
  419. gFromSeen := FALSE;
  420. RETURN
  421. END;
  422. IF Cmp(tok, "MODULE") = 0 THEN
  423. IF gSawHead THEN gWantName := FALSE
  424. ELSE gWantName := TRUE
  425. END;
  426. gSawHead := FALSE;
  427. gPendingFrom := FALSE;
  428. gInImport := FALSE;
  429. gFromSeen := FALSE;
  430. RETURN
  431. END;
  432. IF gPendingFrom THEN
  433. AddDepName(midx, tok);
  434. gPendingFrom := FALSE;
  435. gFromSeen := TRUE;
  436. gSawHead := FALSE;
  437. RETURN
  438. END;
  439. IF gInImport THEN
  440. AddDepName(midx, tok);
  441. gSawHead := FALSE;
  442. gFromSeen := FALSE;
  443. RETURN
  444. END;
  445. IF gWantName THEN
  446. gWantName := FALSE;
  447. IF wantProg AND (midx = mainIdx) THEN
  448. Copy(tok, progName);
  449. wantProg := FALSE
  450. END;
  451. gSawHead := FALSE;
  452. gFromSeen := FALSE;
  453. RETURN
  454. END;
  455. gSawHead := FALSE;
  456. gFromSeen := FALSE
  457. END HandleTok;
  458. PROCEDURE ScanLine(line: ARRAY OF CHAR; midx: INTEGER);
  459. VAR i, n, k: CARDINAL; ch, qc: CHAR;
  460. inStr: BOOLEAN; tok: Name;
  461. BEGIN
  462. n := Len(line);
  463. i := 0;
  464. inStr := FALSE;
  465. qc := CHR(0);
  466. WHILE i < n DO
  467. ch := line[i];
  468. IF inStr THEN
  469. IF ch = qc THEN
  470. IF (i + 1 < n) AND (line[i + 1] = qc) THEN i := i + 2
  471. ELSE inStr := FALSE; INC(i)
  472. END
  473. ELSE INC(i)
  474. END
  475. ELSIF inComment > 0 THEN
  476. IF (ch = "(") AND (i + 1 < n) AND (line[i + 1] = "*") THEN
  477. INC(inComment); i := i + 2
  478. ELSIF (ch = "*") AND (i + 1 < n) AND (line[i + 1] = ")") THEN
  479. DEC(inComment); i := i + 2
  480. ELSE INC(i)
  481. END
  482. ELSE
  483. IF (ch = "(") AND (i + 1 < n) AND (line[i + 1] = "*") THEN
  484. INC(inComment); i := i + 2
  485. ELSIF (ch = "/") AND (i + 1 < n) AND (line[i + 1] = "/") THEN
  486. i := n
  487. ELSIF (ch = "'") OR (ch = '"') THEN
  488. inStr := TRUE; qc := ch; INC(i)
  489. ELSIF ch = ";" THEN
  490. gPendingFrom := FALSE;
  491. gInImport := FALSE;
  492. gSawHead := FALSE;
  493. gWantName := FALSE;
  494. gFromSeen := FALSE;
  495. INC(i)
  496. ELSIF IsLetter(ch) THEN
  497. k := i;
  498. WHILE (k < n) AND (IsLetter(line[k]) OR IsDig(line[k])) DO
  499. INC(k)
  500. END;
  501. Sub(line, i, k - i, tok);
  502. i := k;
  503. HandleTok(tok, midx)
  504. ELSE INC(i)
  505. END
  506. END
  507. END
  508. END ScanLine;
  509. PROCEDURE ScanFile(path: ARRAY OF CHAR; midx: INTEGER);
  510. VAR h: M2makeOS.RdFile; line: Line; ok: BOOLEAN;
  511. BEGIN
  512. h := M2makeOS.OpenRead(path);
  513. IF h = NIL THEN
  514. Cat("M2make: cannot open ", path, tmpCmd);
  515. Die(tmpCmd, 1)
  516. END;
  517. LOOP
  518. Zero(line);
  519. ok := M2makeOS.ReadLine(h, line, HIGH(line) + 1);
  520. IF NOT ok THEN EXIT END;
  521. ScanLine(line, midx)
  522. END;
  523. M2makeOS.CloseRead(h)
  524. END ScanFile;
  525. PROCEDURE ScanModule(m: CARDINAL);
  526. VAR dp, mp: Path;
  527. BEGIN
  528. IF NOT modIsMain[m] THEN
  529. Zero(dp);
  530. Zero(mp);
  531. GetName(m, tmpN);
  532. IF NOT ResolveModule(tmpN, dp, mp) THEN
  533. modExternal[m] := TRUE;
  534. modScanned[m] := TRUE;
  535. IF verbose THEN
  536. Out("M2make: external module ");
  537. Out(tmpN);
  538. OutLn
  539. END;
  540. RETURN
  541. END;
  542. Copy(dp, tmpP);
  543. PutDef(m, tmpP);
  544. Copy(mp, tmpP);
  545. PutSrc(m, tmpP)
  546. END;
  547. inComment := 0;
  548. gPendingFrom := FALSE;
  549. gInImport := FALSE;
  550. gSawHead := FALSE;
  551. gWantName := FALSE;
  552. gFromSeen := FALSE;
  553. GetDef(m, tmpP);
  554. IF Len(tmpP) > 0 THEN ScanFile(tmpP, VAL(INTEGER, m)) END;
  555. GetSrc(m, tmpP);
  556. IF Len(tmpP) > 0 THEN ScanFile(tmpP, VAL(INTEGER, m)) END;
  557. modScanned[m] := TRUE
  558. END ScanModule;
  559. PROCEDURE ResolveAll;
  560. VAR i: CARDINAL;
  561. BEGIN
  562. i := 0;
  563. WHILE i < nMods DO
  564. IF NOT modScanned[i] THEN ScanModule(i) END;
  565. INC(i)
  566. END
  567. END ResolveAll;
  568. (* ---------------- topological sort (iterative DFS) ---------------- *)
  569. PROCEDURE TopoSort;
  570. VAR stack: ARRAY [0..263] OF CARDINAL;
  571. top, cur: CARDINAL; d: INTEGER; dd: CARDINAL; i: CARDINAL;
  572. BEGIN
  573. i := 0;
  574. WHILE i < nMods DO
  575. state[i] := 0; childPos[i] := 0; INC(i)
  576. END;
  577. nOrder := 0;
  578. top := 0;
  579. stack[0] := VAL(CARDINAL, mainIdx);
  580. state[VAL(CARDINAL, mainIdx)] := 1;
  581. LOOP
  582. cur := stack[top];
  583. IF childPos[cur] < nDep[cur] THEN
  584. d := depTab[cur * MaxDeps + childPos[cur]];
  585. INC(childPos[cur]);
  586. IF d < 0 THEN Die("M2make: bad dependency", 1) END;
  587. dd := VAL(CARDINAL, d);
  588. IF NOT modExternal[dd] THEN
  589. IF state[dd] = 0 THEN
  590. state[dd] := 1;
  591. INC(top);
  592. IF top > HIGH(stack) THEN Die("M2make: stack overflow", 1) END;
  593. stack[top] := dd
  594. ELSIF state[dd] = 1 THEN
  595. GetName(dd, tmpN);
  596. Out("M2make: cyclic import involving ");
  597. Out(tmpN);
  598. OutLn;
  599. M2makeOS.ExitNow(1)
  600. END
  601. END
  602. ELSE
  603. state[cur] := 2;
  604. order[nOrder] := cur;
  605. INC(nOrder);
  606. IF top = 0 THEN EXIT END;
  607. DEC(top)
  608. END
  609. END
  610. END TopoSort;
  611. (* ---------------- staleness ---------------- *)
  612. PROCEDURE SrcNewer(src: ARRAY OF CHAR; outT: LONGINT): BOOLEAN;
  613. VAR t: LONGINT;
  614. BEGIN
  615. IF Len(src) = 0 THEN RETURN FALSE END;
  616. IF NOT M2makeOS.FileMTime(src, t) THEN RETURN TRUE END;
  617. RETURN t > outT
  618. END SrcNewer;
  619. PROCEDURE NeedBuild(out: ARRAY OF CHAR; VAR missing: BOOLEAN): BOOLEAN;
  620. VAR outT: LONGINT; k: CARDINAL; m: CARDINAL;
  621. BEGIN
  622. missing := FALSE;
  623. IF NOT M2makeOS.FileExists(out) THEN
  624. missing := TRUE;
  625. RETURN TRUE
  626. END;
  627. IF NOT M2makeOS.FileMTime(out, outT) THEN RETURN TRUE END;
  628. k := 0;
  629. WHILE k < nOrder DO
  630. m := order[k];
  631. GetDef(m, tmpP);
  632. IF SrcNewer(tmpP, outT) THEN RETURN TRUE END;
  633. GetSrc(m, tmpP);
  634. IF SrcNewer(tmpP, outT) THEN RETURN TRUE END;
  635. INC(k)
  636. END;
  637. RETURN FALSE
  638. END NeedBuild;
  639. (* ---------------- command construction ---------------- *)
  640. PROCEDURE AppendTok(VAR cmd: ARRAY OF CHAR; tok: ARRAY OF CHAR);
  641. BEGIN
  642. IF Len(cmd) > 0 THEN
  643. Cat(cmd, " ", segA);
  644. Copy(segA, cmd)
  645. END;
  646. Cat(cmd, tok, segA);
  647. Copy(segA, cmd)
  648. END AppendTok;
  649. PROCEDURE IFlags(VAR flags: ARRAY OF CHAR);
  650. VAR di: CARDINAL;
  651. BEGIN
  652. Zero(flags);
  653. di := 0;
  654. WHILE di < nDirs DO
  655. GetDir(di, tmpP);
  656. IF Len(tmpP) > 0 THEN
  657. AppendTok(flags, "-I");
  658. AppendTok(flags, tmpP)
  659. END;
  660. INC(di)
  661. END
  662. END IFlags;
  663. PROCEDURE AppendCh(VAR s: ARRAY OF CHAR; ch: CHAR);
  664. VAR n: CARDINAL;
  665. BEGIN
  666. n := Len(s);
  667. IF n <= HIGH(s) THEN
  668. s[n] := ch;
  669. IF n + 1 <= HIGH(s) THEN s[n + 1] := CHR(0) END
  670. END
  671. END AppendCh;
  672. PROCEDURE BaseObj(src: ARRAY OF CHAR; VAR obj: ARRAY OF CHAR);
  673. (* "some/dir/Calc.mod" -> "Calc.o" (gm2 -c drops objects in the cwd). *)
  674. VAR i, n, slash, dot, k: CARDINAL;
  675. BEGIN
  676. Zero(obj);
  677. n := Len(src);
  678. slash := 0;
  679. i := 0;
  680. WHILE i < n DO
  681. IF src[i] = "/" THEN slash := i + 1 END;
  682. INC(i)
  683. END;
  684. dot := n;
  685. i := slash;
  686. WHILE i < n DO
  687. IF src[i] = "." THEN dot := i END;
  688. INC(i)
  689. END;
  690. IF dot <= slash THEN dot := n END;
  691. k := 0;
  692. i := slash;
  693. WHILE (i < dot) AND (k <= HIGH(obj)) DO
  694. obj[k] := src[i]; INC(k); INC(i)
  695. END;
  696. Cat(obj, ".o", tmpA);
  697. Copy(tmpA, obj)
  698. END BaseObj;
  699. PROCEDURE ObjStale(m: CARDINAL; obj: ARRAY OF CHAR): BOOLEAN;
  700. VAR ot: LONGINT;
  701. BEGIN
  702. IF NOT M2makeOS.FileExists(obj) THEN RETURN TRUE END;
  703. IF NOT M2makeOS.FileMTime(obj, ot) THEN RETURN TRUE END;
  704. GetDef(m, tmpP);
  705. IF SrcNewer(tmpP, ot) THEN RETURN TRUE END;
  706. GetSrc(m, tmpP);
  707. IF SrcNewer(tmpP, ot) THEN RETURN TRUE END;
  708. RETURN FALSE
  709. END ObjStale;
  710. PROCEDURE ObjNewerThan(obj, out: ARRAY OF CHAR;
  711. outT: LONGINT): BOOLEAN;
  712. VAR t: LONGINT;
  713. BEGIN
  714. IF Len(obj) = 0 THEN RETURN FALSE END;
  715. IF NOT M2makeOS.FileMTime(obj, t) THEN RETURN TRUE END;
  716. RETURN t > outT
  717. END ObjNewerThan;
  718. PROCEDURE CheckLen(cmd: ARRAY OF CHAR);
  719. BEGIN
  720. IF Len(cmd) + 32 > HIGH(cmd) THEN
  721. Die("M2make: command too long", 1)
  722. END
  723. END CheckLen;
  724. PROCEDURE AppendCmd(VAR cmd: ARRAY OF CHAR; seg: ARRAY OF CHAR);
  725. BEGIN
  726. IF Len(cmd) > 0 THEN
  727. Cat(cmd, " && ", segA);
  728. Copy(segA, cmd)
  729. END;
  730. Cat(cmd, seg, segA);
  731. Copy(segA, cmd)
  732. END AppendCmd;
  733. PROCEDURE AppendSeg(VAR cmd: ARRAY OF CHAR; seg: ARRAY OF CHAR);
  734. (* Direct concatenation (segments carry their own spacing).
  735. segA is the dedicated Cmd-sized scratch: always distinct from
  736. cmd and seg, and big enough for whole command lines. *)
  737. BEGIN
  738. Cat(cmd, seg, segA);
  739. Copy(segA, cmd)
  740. END AppendSeg;
  741. PROCEDURE BuildM2(out: ARRAY OF CHAR);
  742. VAR cmd: Cmd; k: CARDINAL; m: CARDINAL; rc: INTEGER;
  743. BEGIN
  744. Zero(cmd);
  745. AppendSeg(cmd, "mkdir -p gen_ssa && ");
  746. AppendSeg(cmd, m2bin);
  747. k := 0;
  748. WHILE k < nOrder DO
  749. m := order[k];
  750. GetDef(m, tmpP);
  751. IF Len(tmpP) > 0 THEN AppendTok(cmd, tmpP) END;
  752. GetSrc(m, tmpP);
  753. IF Len(tmpP) > 0 THEN AppendTok(cmd, tmpP) END;
  754. INC(k)
  755. END;
  756. AppendSeg(cmd, " 2>&1 | tee m2make.log | grep -q Parsed");
  757. AppendSeg(cmd, " && qbe -o gen_ssa/");
  758. AppendSeg(cmd, progName);
  759. AppendSeg(cmd, ".s gen_ssa/");
  760. AppendSeg(cmd, progName);
  761. AppendSeg(cmd, ".ssa");
  762. AppendSeg(cmd, " && cc gen_ssa/");
  763. AppendSeg(cmd, progName);
  764. AppendSeg(cmd, ".s ");
  765. AppendTok(cmd, shimPath);
  766. AppendSeg(cmd, " -o ");
  767. AppendTok(cmd, out);
  768. AppendSeg(cmd, " -lm");
  769. CheckLen(cmd);
  770. Out(cmd);
  771. OutLn;
  772. IF dryRun THEN RETURN END;
  773. rc := M2makeOS.ExecCmd(cmd);
  774. IF rc # 0 THEN
  775. Out("M2make: build failed");
  776. OutLn;
  777. M2makeOS.ExitNow(1)
  778. END
  779. END BuildM2;
  780. PROCEDURE BuildGm2(out: ARRAY OF CHAR);
  781. VAR cmd, seg: Cmd; flags: Path; obj: Path;
  782. k, m: CARDINAL; rc: INTEGER; outT: LONGINT;
  783. compiledAny, linkNeeded: BOOLEAN;
  784. BEGIN
  785. Zero(cmd);
  786. IFlags(flags);
  787. compiledAny := FALSE;
  788. k := 0;
  789. WHILE k < nOrder DO
  790. m := order[k];
  791. GetSrc(m, tmpP);
  792. IF (NOT modIsMain[m]) AND (Len(tmpP) > 0) THEN
  793. BaseObj(tmpP, obj);
  794. IF ObjStale(m, obj) THEN
  795. Zero(seg);
  796. Cat("gm2 -fiso ", flags, seg);
  797. IF Len(flags) > 0 THEN
  798. Cat(seg, " ", segA);
  799. Copy(segA, seg)
  800. END;
  801. Cat(seg, "-c ", segA);
  802. Copy(segA, seg);
  803. Cat(seg, tmpP, segA);
  804. Copy(segA, seg);
  805. AppendCmd(cmd, seg);
  806. compiledAny := TRUE
  807. END
  808. END;
  809. INC(k)
  810. END;
  811. linkNeeded := compiledAny;
  812. IF NOT linkNeeded THEN
  813. IF NOT M2makeOS.FileExists(out) THEN linkNeeded := TRUE
  814. ELSIF NOT M2makeOS.FileMTime(out, outT) THEN linkNeeded := TRUE
  815. ELSE
  816. k := 0;
  817. WHILE k < nOrder DO
  818. m := order[k];
  819. GetSrc(m, tmpP);
  820. IF (NOT modIsMain[m]) AND (Len(tmpP) > 0) THEN
  821. BaseObj(tmpP, obj);
  822. IF ObjNewerThan(obj, out, outT) THEN linkNeeded := TRUE END
  823. END;
  824. INC(k)
  825. END
  826. END
  827. END;
  828. IF linkNeeded THEN
  829. Zero(seg);
  830. Cat("gm2 -fiso ", flags, seg);
  831. IF Len(flags) > 0 THEN
  832. Cat(seg, " ", segA);
  833. Copy(segA, seg)
  834. END;
  835. Cat(seg, "-o ", segA);
  836. Copy(segA, seg);
  837. Cat(seg, out, segA);
  838. Copy(segA, seg);
  839. Cat(seg, " ", segA);
  840. Copy(segA, seg);
  841. GetSrc(VAL(CARDINAL, mainIdx), tmpP);
  842. Cat(seg, tmpP, segA);
  843. Copy(segA, seg);
  844. k := 0;
  845. WHILE k < nOrder DO
  846. m := order[k];
  847. GetSrc(m, tmpP);
  848. IF (NOT modIsMain[m]) AND (Len(tmpP) > 0) THEN
  849. BaseObj(tmpP, obj);
  850. Cat(seg, " ", segA);
  851. Copy(segA, seg);
  852. Cat(seg, obj, segA);
  853. Copy(segA, seg)
  854. END;
  855. INC(k)
  856. END;
  857. AppendCmd(cmd, seg)
  858. END;
  859. IF Len(cmd) = 0 THEN
  860. Out("M2make: ");
  861. Out(out);
  862. Out(" up to date");
  863. OutLn;
  864. RETURN
  865. END;
  866. CheckLen(cmd);
  867. Out(cmd);
  868. OutLn;
  869. IF dryRun THEN RETURN END;
  870. rc := M2makeOS.ExecCmd(cmd);
  871. IF rc # 0 THEN
  872. Out("M2make: build failed");
  873. OutLn;
  874. M2makeOS.ExitNow(1)
  875. END
  876. END BuildGm2;
  877. PROCEDURE PrintOrder;
  878. VAR k: CARDINAL; m: CARDINAL;
  879. BEGIN
  880. Out("M2make: build order");
  881. OutLn;
  882. k := 0;
  883. WHILE k < nOrder DO
  884. m := order[k];
  885. Out(" ");
  886. GetName(m, tmpN);
  887. Out(tmpN);
  888. GetDef(m, tmpP);
  889. IF Len(tmpP) > 0 THEN
  890. Out(" : ");
  891. Out(tmpP)
  892. END;
  893. GetSrc(m, tmpP);
  894. IF Len(tmpP) > 0 THEN
  895. GetDef(m, tmpQ);
  896. IF Len(tmpQ) > 0 THEN Out(" ") ELSE Out(" : ") END;
  897. Out(tmpP)
  898. END;
  899. OutLn;
  900. INC(k)
  901. END
  902. END PrintOrder;
  903. (* ---------------- argument handling ---------------- *)
  904. PROCEDURE ParseArgs;
  905. VAR na: CARDINAL; s: Path; ok: BOOLEAN;
  906. BEGIN
  907. dryRun := FALSE;
  908. verbose := FALSE;
  909. useGm2 := FALSE;
  910. outGiven := FALSE;
  911. Zero(mainPath);
  912. Zero(outName);
  913. Copy("M2", m2bin);
  914. Copy("shim.c", shimPath);
  915. nDirs := 0;
  916. na := 0;
  917. LOOP
  918. IF na >= MaxArgs THEN Die("M2make: too many arguments", 2) END;
  919. ok := M2makeOS.GetArg(na, s, HIGH(s) + 1);
  920. IF NOT ok THEN EXIT END;
  921. INC(na);
  922. IF Cmp(s, "-n") = 0 THEN dryRun := TRUE
  923. ELSIF Cmp(s, "-v") = 0 THEN verbose := TRUE
  924. ELSIF Cmp(s, "-c") = 0 THEN
  925. ok := M2makeOS.GetArg(na, s, HIGH(s) + 1);
  926. IF NOT ok THEN
  927. Out("M2make: -c expects m2 or gm2"); OutLn;
  928. M2makeOS.ExitNow(2)
  929. END;
  930. INC(na);
  931. IF Cmp(s, "gm2") = 0 THEN useGm2 := TRUE
  932. ELSIF Cmp(s, "m2") = 0 THEN useGm2 := FALSE
  933. ELSE
  934. Out("M2make: -c expects m2 or gm2"); OutLn;
  935. M2makeOS.ExitNow(2)
  936. END
  937. ELSIF Cmp(s, "-I") = 0 THEN
  938. ok := M2makeOS.GetArg(na, s, HIGH(s) + 1);
  939. IF NOT ok THEN
  940. Out("M2make: -I expects a directory"); OutLn;
  941. M2makeOS.ExitNow(2)
  942. END;
  943. INC(na);
  944. IF nDirs >= MaxDirs THEN Die("M2make: too many -I dirs", 2) END;
  945. Copy(s, tmpP);
  946. PutDir(nDirs, tmpP);
  947. INC(nDirs)
  948. ELSIF Cmp(s, "-o") = 0 THEN
  949. ok := M2makeOS.GetArg(na, s, HIGH(s) + 1);
  950. IF NOT ok THEN
  951. Out("M2make: -o expects a name"); OutLn;
  952. M2makeOS.ExitNow(2)
  953. END;
  954. INC(na);
  955. Copy(s, outName);
  956. outGiven := TRUE
  957. ELSIF Cmp(s, "--m2bin") = 0 THEN
  958. ok := M2makeOS.GetArg(na, s, HIGH(s) + 1);
  959. IF NOT ok THEN
  960. Out("M2make: --m2bin expects a path"); OutLn;
  961. M2makeOS.ExitNow(2)
  962. END;
  963. INC(na);
  964. Copy(s, m2bin)
  965. ELSIF Cmp(s, "--shim") = 0 THEN
  966. ok := M2makeOS.GetArg(na, s, HIGH(s) + 1);
  967. IF NOT ok THEN
  968. Out("M2make: --shim expects a path"); OutLn;
  969. M2makeOS.ExitNow(2)
  970. END;
  971. INC(na);
  972. Copy(s, shimPath)
  973. ELSIF (Cmp(s, "-h") = 0) OR (Cmp(s, "--help") = 0) THEN
  974. Usage;
  975. M2makeOS.ExitNow(0)
  976. ELSIF s[0] = "-" THEN
  977. Out("M2make: unknown option ");
  978. Out(s);
  979. OutLn;
  980. Usage;
  981. M2makeOS.ExitNow(2)
  982. ELSE
  983. IF Len(mainPath) > 0 THEN
  984. Out("M2make: only one main file"); OutLn;
  985. Usage;
  986. M2makeOS.ExitNow(2)
  987. END;
  988. Copy(s, mainPath)
  989. END
  990. END;
  991. IF Len(mainPath) = 0 THEN
  992. Usage;
  993. M2makeOS.ExitNow(2)
  994. END
  995. END ParseArgs;
  996. (* ---------------- main ---------------- *)
  997. VAR
  998. missing: BOOLEAN;
  999. rebuild: BOOLEAN;
  1000. BEGIN
  1001. nMods := 0;
  1002. nOrder := 0;
  1003. wantProg := TRUE;
  1004. Zero(progName);
  1005. ParseArgs;
  1006. IF NOT M2makeOS.FileExists(mainPath) THEN
  1007. Cat("M2make: main file not found ", mainPath, tmpCmd);
  1008. Die(tmpCmd, 2)
  1009. END;
  1010. DirOf(mainPath, tmpA);
  1011. IF nDirs = 0 THEN
  1012. Copy(tmpA, tmpP);
  1013. PutDir(0, tmpP);
  1014. nDirs := 1
  1015. ELSE
  1016. (* main dir becomes an extra search dir when -I was given *)
  1017. IF nDirs >= MaxDirs THEN Die("M2make: too many -I dirs", 2) END;
  1018. Copy(tmpA, tmpP);
  1019. PutDir(nDirs, tmpP);
  1020. INC(nDirs)
  1021. END;
  1022. mainIdx := AddMod("M2makeMain");
  1023. modIsMain[VAL(CARDINAL, mainIdx)] := TRUE;
  1024. Copy(mainPath, tmpP);
  1025. PutSrc(VAL(CARDINAL, mainIdx), tmpP);
  1026. ResolveAll;
  1027. IF Len(progName) = 0 THEN
  1028. Die("M2make: no MODULE declaration in main file", 1)
  1029. END;
  1030. IF NOT outGiven THEN Copy(progName, outName) END;
  1031. TopoSort;
  1032. IF verbose OR dryRun THEN PrintOrder END;
  1033. rebuild := NeedBuild(outName, missing);
  1034. IF NOT rebuild THEN
  1035. Out("M2make: ");
  1036. Out(outName);
  1037. Out(" up to date");
  1038. OutLn
  1039. ELSE
  1040. IF useGm2 THEN BuildGm2(outName)
  1041. ELSE BuildM2(outName)
  1042. END
  1043. END
  1044. END M2make.