M2make.mod 27 KB

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