M2make.mod 26 KB

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