InterpreterShell.Mod 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  1. MODULE InterpreterShell; (** AUTHOR "fof"; PURPOSE "Shell of the Fox Interpreter " **)
  2. IMPORT Modules, Commands, Streams, Pipes, Strings, Files, Interpreter := FoxInterpreter, Diagnostics, Scanner := FoxScanner, SyntaxTree := FoxSyntaxTree, Printout := FoxPrintout, InterpreterSymbols := FoxInterpreterSymbols;
  3. CONST
  4. (* Notify procedure command codes *)
  5. ExitShell* = 1;
  6. Clear* = 2;
  7. Version = "InterpreterShell v1.0";
  8. DefaultAliasFile = "Shell.Alias";
  9. NestingLevelIndicator = ">";
  10. MaxLen = 512;
  11. CmdLen = 64;
  12. ParamLen = MaxLen;
  13. CR = 0DX; LF = 0AX; TAB = 9X;
  14. Backspace = 08X;
  15. Space = 20X;
  16. Delete = 7FX;
  17. Escape = 1BX;
  18. EscapeChar1 = Escape;
  19. EscapeChar2 = '[';
  20. (* Non-ASCII characters *)
  21. CursorUp = 0C1X;
  22. CursorDown = 0C2X;
  23. (* symbols *)
  24. start = {};
  25. inputFile = {0}; (* 01H *)
  26. pipe = {1}; (* 02H *)
  27. outputFile = {2}; (* 04H *)
  28. outputFileAppend = {3}; (* 08H *)
  29. ampersand = {4}; (* 10H *)
  30. whitespace = {5}; (* 20H *)
  31. eoln = {6}; (* 40H *)
  32. char = {7}; (* 80H *)
  33. EndOfParam = pipe + inputFile + outputFile + outputFileAppend + ampersand + eoln;
  34. (* errors *)
  35. ErrFileNotFound = 1;
  36. ErrInvalidFilename = 2;
  37. ErrAlreadyPiped = 3;
  38. ErrPipeAtBeginning = 4;
  39. ErrInvalidCommand = 5;
  40. ErrEolnExpected = 6;
  41. TYPE
  42. CommandsString = POINTER TO RECORD
  43. prev, next: CommandsString;
  44. string: ARRAY MaxLen OF CHAR;
  45. END;
  46. CommandHistory = OBJECT
  47. VAR
  48. first, current: CommandsString;
  49. PROCEDURE GetNextCommand(VAR cmd : ARRAY OF CHAR);
  50. BEGIN
  51. IF first = NIL THEN RETURN END;
  52. IF current = NIL THEN current := first ELSE current := current.next END;
  53. COPY(current.string, cmd);
  54. END GetNextCommand;
  55. PROCEDURE GetPreviousCommand(VAR cmd : ARRAY OF CHAR);
  56. BEGIN
  57. IF first = NIL THEN RETURN END;
  58. IF current = NIL THEN current := first.prev ELSE current := current.prev END;
  59. COPY(current.string, cmd);
  60. END GetPreviousCommand;
  61. PROCEDURE AddCommand(CONST cmd : ARRAY OF CHAR);
  62. VAR command: CommandsString;
  63. BEGIN
  64. IF (cmd = "") THEN (* Don't add to history *) RETURN; END;
  65. command := first;
  66. IF command # NIL THEN
  67. WHILE (command.string # cmd) & (command.next # first) DO command := command.next END;
  68. IF command.string # cmd THEN command := NIL END
  69. END;
  70. IF command # NIL THEN
  71. IF first = command THEN first := command.next END;
  72. command.prev.next := command.next;
  73. command.next.prev := command.prev;
  74. ELSE
  75. NEW (command);
  76. COPY (cmd, command.string);
  77. END;
  78. IF first = NIL THEN
  79. first := command; first.next := first; first.prev := first
  80. ELSE
  81. command.prev := first.prev; command.next := first;
  82. first.prev.next := command; first.prev := command;
  83. END;
  84. current := NIL;
  85. END AddCommand;
  86. PROCEDURE &Init*;
  87. BEGIN first := NIL; current := NIL;
  88. END Init;
  89. END CommandHistory;
  90. TYPE
  91. Command = POINTER TO RECORD
  92. command: ARRAY CmdLen OF CHAR; (* command (e.g. <module>"."<command> *)
  93. parameters: ARRAY ParamLen OF CHAR; (* parameters *)
  94. context: Commands.Context; (* context (in, out & err streams *)
  95. pipe : Pipes.Pipe;
  96. next: Command;
  97. END;
  98. Alias = POINTER TO RECORD
  99. alias,
  100. command: ARRAY CmdLen OF CHAR;
  101. parameters: ARRAY ParamLen OF CHAR;
  102. next: Alias;
  103. END;
  104. NotifyProcedure* = PROCEDURE {DELEGATE} (command : LONGINT);
  105. TYPE
  106. (*
  107. Blocker* = OBJECT (Streams.Writer)
  108. VAR
  109. interpreter: Interpreter.Interpreter; i: LONGINT;
  110. parser: Interpreter.Parser;
  111. reader-: Streams.Reader;
  112. writer-: Streams.Writer;
  113. scanner: Scanner.Scanner;
  114. diagnostics: Diagnostics.StreamDiagnostics;
  115. PROCEDURE & InitBlocker(context: Commands.Context);
  116. VAR pipe: Pipes.Pipe;
  117. BEGIN
  118. TRACE(1);
  119. NEW(diagnostics, context.error);
  120. TRACE(2);
  121. NEW(pipe, 256);
  122. TRACE(3);
  123. NEW(reader, pipe.Receive, 256);
  124. TRACE(4);
  125. NEW(writer, pipe.Send, 256);
  126. END InitBlocker;
  127. PROCEDURE Statements;
  128. VAR statement: SyntaxTree.Statement; statements: SyntaxTree.StatementSequence;
  129. BEGIN
  130. NEW(scanner, "", reader, 0, diagnostics);
  131. TRACE(6);
  132. NEW(parser, scanner, diagnostics);
  133. TRACE(7);
  134. statements := SyntaxTree.NewStatementSequence();
  135. LOOP
  136. WHILE parser.Statement(statements, NIL) DO TRACE("parser statement");
  137. IF parser.Optional(Scanner.Semicolon) THEN END;
  138. END;
  139. TRACE("failure");
  140. END;
  141. END Statements;
  142. BEGIN{ACTIVE}
  143. Statements
  144. END Blocker;
  145. *)
  146. Shell* = OBJECT
  147. VAR
  148. echo, dead, close: BOOLEAN;
  149. context: Commands.Context;
  150. command: ARRAY MaxLen OF CHAR;
  151. res: WORD;
  152. nestingLevel : LONGINT; (* how many shells run in this shell? *)
  153. aliases: Alias;
  154. prompt: ARRAY 32 OF CHAR;
  155. seenCR: CHAR;
  156. (* Connection to the entiry hosting this shell instance *)
  157. upcall : NotifyProcedure;
  158. commandHistory : CommandHistory;
  159. PROCEDURE &Init*(in: Streams.Reader; out, err: Streams.Writer; echo: BOOLEAN; CONST prompt: ARRAY OF CHAR);
  160. BEGIN
  161. ASSERT((in # NIL) & (out # NIL) & (err # NIL));
  162. NEW(context, in, NIL, out, err, SELF);
  163. close := FALSE; dead := FALSE; command[0] := 0X; res := 0; SELF.echo := echo; COPY(prompt, SELF.prompt);
  164. NEW(commandHistory);
  165. END Init;
  166. PROCEDURE Exit*;
  167. BEGIN
  168. close := TRUE;
  169. END Exit;
  170. PROCEDURE DeleteStringFromDisplay(CONST x : ARRAY OF CHAR);
  171. VAR i, len : LONGINT;
  172. BEGIN
  173. len := Strings.Length(x);
  174. FOR i := 0 TO len-1 DO context.out.Char(Backspace); END;
  175. FOR i := 0 TO len-1 DO context.out.Char(Space); END;
  176. FOR i := 0 TO len-1 DO context.out.Char(Backspace); END;
  177. END DeleteStringFromDisplay;
  178. PROCEDURE ReadCommand(w: Streams.Writer (*VAR command : ARRAY OF CHAR*));
  179. VAR
  180. ch: CHAR;
  181. currentIndex : LONGINT;
  182. PROCEDURE IsAsciiCharacter(ch : CHAR) : BOOLEAN;
  183. BEGIN
  184. RETURN ORD(ch) <= 127;
  185. END IsAsciiCharacter;
  186. PROCEDURE IsControlCharacter(ch : CHAR) : BOOLEAN;
  187. BEGIN
  188. RETURN ORD(ch) < 32;
  189. END IsControlCharacter;
  190. PROCEDURE HandleEscapeSequence;
  191. BEGIN
  192. ch := context.in.Get();
  193. ch := CHR(ORD(ch)+128);
  194. IF (ch = CursorDown) OR (ch = CursorUp) THEN (* Command History Keys *)
  195. command[currentIndex+1] := 0X;
  196. DeleteStringFromDisplay(command);
  197. IF ch = CursorUp THEN
  198. commandHistory.GetPreviousCommand(command);
  199. ELSE
  200. commandHistory.GetNextCommand(command);
  201. END;
  202. currentIndex := Strings.Length(command)-1;
  203. IF echo & (command # "") THEN context.out.String(command); context.out.Update; END;
  204. ELSE
  205. (* ignore escaped character *)
  206. END;
  207. END HandleEscapeSequence;
  208. BEGIN
  209. command := ""; currentIndex := -1;
  210. LOOP
  211. ch := context.in.Get();
  212. IF IsAsciiCharacter(ch) THEN
  213. IF IsControlCharacter(ch) OR (ch = Delete) THEN
  214. IF ((ch = CR) OR (ch = LF)) OR (ch = Streams.EOT) OR (context.in.res # Streams.Ok) THEN
  215. IF seenCR = 0X THEN seenCR := ch
  216. ELSIF seenCR = ch THEN
  217. EXIT
  218. ELSE (* ignore *)
  219. END;
  220. ELSIF (ch = Backspace) OR (ch = Delete)THEN
  221. IF currentIndex >= 0 THEN (* There is a character at the left of the cursor *)
  222. command[currentIndex] := 0X;
  223. DEC(currentIndex);
  224. IF echo THEN
  225. context.out.Char(Backspace); context.out.Char(Space); context.out.Char(Backspace); context.out.Update;
  226. END;
  227. END;
  228. ELSIF (ORD(ch) = 03H) THEN
  229. (* IF runner # NIL THEN AosActive.TerminateThis(runner); END; *)
  230. ELSIF (ch = EscapeChar1) THEN (* Escape sequence *)
  231. IF context.in.Peek() = EscapeChar2 THEN ch := context.in.Get(); HandleEscapeSequence;
  232. ELSIF context.in.Peek () = Escape THEN
  233. command[currentIndex+1] := 0X;
  234. DeleteStringFromDisplay (command); context.out.Update;
  235. ch := context.in.Get (); command := ""; currentIndex := -1;
  236. END;
  237. ELSE
  238. (* ignore other control characters *)
  239. END;
  240. ELSE
  241. IF currentIndex <= LEN(command) - 2 (* Always need space for 0X *) THEN
  242. INC(currentIndex);
  243. command[currentIndex] := ch;
  244. IF echo THEN context.out.Char(ch); context.out.Update; END;
  245. END;
  246. END;
  247. ELSE
  248. (* ignore non-ascii characters *)
  249. END;
  250. END;
  251. command[currentIndex+1] := 0X;
  252. IF (ch = CR) OR (ch = LF) THEN
  253. commandHistory.AddCommand(command);
  254. (*IF (*(ch = CR) & *)(context.in.Available() > 0) & (context.in.Peek() = LF) THEN ch := context.in.Get() END;*)
  255. IF echo THEN context.out.Ln; context.out.Update END
  256. END;
  257. w.String(command);
  258. END ReadCommand;
  259. (*
  260. PROCEDURE ReadCommand(w: Streams.Writer);
  261. VAR
  262. ch: CHAR;
  263. currentIndex : LONGINT;
  264. PROCEDURE IsAsciiCharacter(ch : CHAR) : BOOLEAN;
  265. BEGIN
  266. RETURN ORD(ch) <= 127;
  267. END IsAsciiCharacter;
  268. PROCEDURE IsControlCharacter(ch : CHAR) : BOOLEAN;
  269. BEGIN
  270. RETURN ORD(ch) < 32;
  271. END IsControlCharacter;
  272. PROCEDURE HandleEscapeSequence;
  273. BEGIN
  274. ch := context.in.Get();
  275. ch := CHR(ORD(ch)+128);
  276. IF (ch = CursorDown) OR (ch = CursorUp) THEN (* Command History Keys *)
  277. command[currentIndex+1] := 0X;
  278. DeleteStringFromDisplay(command);
  279. IF ch = CursorUp THEN
  280. commandHistory.GetPreviousCommand(command);
  281. ELSE
  282. commandHistory.GetNextCommand(command);
  283. END;
  284. currentIndex := Strings.Length(command)-1;
  285. IF echo & (command # "") THEN context.out.String(command); context.out.Update; END;
  286. ELSE
  287. (* ignore escaped character *)
  288. END;
  289. END HandleEscapeSequence;
  290. BEGIN
  291. command := ""; currentIndex := -1;
  292. LOOP
  293. ch := context.in.Get();
  294. TRACE(ch);
  295. IF IsAsciiCharacter(ch) THEN
  296. IF IsControlCharacter(ch) OR (ch = Delete) THEN
  297. IF (ch = Streams.EOT) OR (context.in.res # Streams.Ok) THEN
  298. EXIT
  299. ELSIF (ch = Backspace) OR (ch = Delete)THEN
  300. IF currentIndex >= 0 THEN (* There is a character at the left of the cursor *)
  301. IF command[currentIndex] = CR THEN
  302. context.out.Char(Backspace); context.out.Char(Space); context.out.Char(Backspace); context.out.Update;
  303. END;
  304. command[currentIndex] := 0X;
  305. DEC(currentIndex);
  306. IF echo THEN
  307. context.out.Char(Backspace); context.out.Char(Space); context.out.Char(Backspace); context.out.Update;
  308. END;
  309. END;
  310. ELSIF (ORD(ch) = 03H) THEN
  311. (* IF runner # NIL THEN AosActive.TerminateThis(runner); END; *)
  312. ELSIF (ch = EscapeChar1) THEN (* Escape sequence *)
  313. IF context.in.Peek() = EscapeChar2 THEN ch := context.in.Get(); HandleEscapeSequence;
  314. ELSIF context.in.Peek() =
  315. ELSIF context.in.Peek() = 0DX THEN (* command *)
  316. ch := context.in.Get();
  317. INC(currentIndex); command[currentIndex] := ch;
  318. EXIT;
  319. ELSIF context.in.Peek () = Escape THEN
  320. command[currentIndex+1] := 0X;
  321. DeleteStringFromDisplay (command); context.out.Update;
  322. ch := context.in.Get (); command := ""; currentIndex := -1;
  323. END;
  324. ELSIF (ch =CR) OR (ch = LF) THEN
  325. INC(currentIndex); command[currentIndex] := ch;
  326. IF (ch = CR) & (context.in.Available() > 0) & (context.in.Peek() = LF) THEN
  327. ch := context.in.Get();
  328. INC(currentIndex); command[currentIndex] := ch;
  329. END;
  330. IF echo THEN context.out.Ln; context.out.Update END;
  331. ELSE
  332. INC(currentIndex);
  333. command[currentIndex] := ch;
  334. IF echo THEN context.out.Char(ch); context.out.Update; END;
  335. END;
  336. ELSE
  337. IF currentIndex <= LEN(command) - 2 (* Always need space for 0X *) THEN
  338. INC(currentIndex);
  339. command[currentIndex] := ch;
  340. IF echo THEN context.out.Char(ch); context.out.Update; END;
  341. END;
  342. END;
  343. ELSE
  344. (* ignore non-ascii characters *)
  345. END;
  346. END;
  347. command[currentIndex+1] := 0X;
  348. IF ch = CR THEN
  349. commandHistory.AddCommand(command);
  350. IF (context.in.Available() > 0) & (context.in.Peek() = LF) THEN ch := context.in.Get() END;
  351. (* IF echo THEN context.out.Ln; context.out.Update END; *)
  352. w.String(command);
  353. END;
  354. END ReadCommand;
  355. *)
  356. (*
  357. PROCEDURE Parse(VAR cmd: Command; VAR wait: BOOLEAN): LONGINT;
  358. VAR sym: SET; pos: LONGINT; c, next: CHAR;
  359. PROCEDURE Init;
  360. BEGIN
  361. pos := 0; c := 0X; next := command[pos]; sym := start; Scan
  362. END Init;
  363. PROCEDURE Scan;
  364. BEGIN
  365. IF (sym # eoln) THEN
  366. c := next; INC(pos); next := command[pos];
  367. CASE c OF
  368. | "<": sym := inputFile
  369. | "|": sym := pipe
  370. | ">": IF (next = ">") THEN sym := outputFileAppend; INC(pos); next := command[pos]; ELSE sym := outputFile END
  371. | "&": sym := ampersand
  372. | " ", 09X: sym := whitespace
  373. | 0X: sym := eoln
  374. ELSE sym := char
  375. END
  376. END
  377. END Scan;
  378. PROCEDURE Match(symbol: SET): BOOLEAN;
  379. BEGIN IF (symbol = sym) THEN Scan; RETURN TRUE ELSE RETURN FALSE END
  380. END Match;
  381. PROCEDURE Skip;
  382. BEGIN
  383. WHILE (sym = whitespace) & (sym # eoln) DO Scan END
  384. END Skip;
  385. PROCEDURE Token(VAR s: ARRAY OF CHAR; cond: SET): BOOLEAN;
  386. VAR i: LONGINT; quote: BOOLEAN;
  387. BEGIN
  388. quote := FALSE;
  389. WHILE (sym * cond = {}) OR (quote & (sym # eoln)) DO
  390. s[i] := c; INC(i); IF (c = '"') OR (c = "'") THEN quote := ~quote END; Scan
  391. END;
  392. s[i] := 0X;
  393. RETURN ~quote
  394. END Token;
  395. PROCEDURE Cmd(): Command;
  396. VAR i: LONGINT; cmd: Command; arg : Streams.StringReader;
  397. BEGIN Skip;
  398. IF (sym = char) THEN
  399. NEW(cmd);
  400. i := 0;
  401. WHILE (sym = char) DO cmd.command[i] := c; INC(i); Scan END; cmd.command[i] := 0X; Skip;
  402. IF (cmd.command # "") THEN
  403. IF (sym * EndOfParam = {}) THEN
  404. IF ~Token(cmd.parameters, EndOfParam) THEN cmd := NIL END
  405. END;
  406. REPEAT UNTIL ~ReplaceAlias(cmd);
  407. NEW(arg, LEN(cmd.parameters)); arg.SetRaw(cmd.parameters, 0, LEN(cmd.parameters));
  408. NEW(cmd.context, context.in, arg, context.out, context.error, SELF);
  409. ELSE cmd := NIL (* invalid command (empty string) *)
  410. END
  411. ELSE cmd := NIL
  412. END;
  413. RETURN cmd
  414. END Cmd;
  415. PROCEDURE CmdLine(VAR command: Command): LONGINT;
  416. VAR cmd, prev: Command; fn: Files.FileName; f: Files.File; fr: Files.Reader; fw: Files.Writer;
  417. r: Streams.Reader; w: Streams.Writer; append, piped: BOOLEAN; s: ARRAY 64 OF CHAR;
  418. BEGIN
  419. cmd := NIL; prev := NIL; command := NIL; res := 0; piped := FALSE;
  420. Init;
  421. REPEAT
  422. cmd := Cmd();
  423. IF (cmd # NIL) THEN
  424. IF (command = NIL) THEN command := cmd END;
  425. IF piped THEN
  426. piped := FALSE;
  427. IF (prev # NIL) THEN
  428. IF (prev.context.out = context.out) & (cmd.context.in = context.in) THEN
  429. NEW(prev.pipe, 1024);
  430. Streams.OpenReader(r, prev.pipe.Receive); Streams.OpenWriter(w, prev.pipe.Send);
  431. prev.context.Init(r, prev.context.arg, w, prev.context.error, SELF);
  432. prev.next := cmd
  433. ELSE res := ErrAlreadyPiped (* already piped *)
  434. END
  435. ELSE res := ErrPipeAtBeginning (* pipe cannot be first symbol *)
  436. END
  437. END;
  438. IF Match(inputFile) THEN (* "<" filename *)
  439. IF (cmd.context.in = context.in) THEN
  440. Skip;
  441. IF Token(fn, -char) & (fn # "") THEN
  442. f := Files.Old(fn);
  443. IF (f # NIL) THEN
  444. Files.OpenReader(fr, f, 0);
  445. cmd.context.Init(fr, cmd.context.arg, cmd.context.out, cmd.context.error, SELF)
  446. ELSE res := ErrFileNotFound (* file not found *)
  447. END
  448. ELSE res := ErrInvalidFilename (* invalid filename *)
  449. END
  450. ELSE res := ErrAlreadyPiped (* error: already piped *)
  451. END
  452. ELSIF Match(pipe) THEN (* "|" command *)
  453. piped := TRUE
  454. END;
  455. prev := cmd
  456. ELSE res := ErrInvalidCommand (* invalid command *)
  457. END
  458. UNTIL (res # 0) OR (cmd = NIL) OR ~piped;
  459. IF (res = 0) THEN
  460. IF (sym * (outputFile+outputFileAppend) # {}) THEN (* ">"[">"] filename *)
  461. append := (sym = outputFileAppend);
  462. Scan; Skip;
  463. IF Token (fn, EndOfParam (*-char *)) & (fn # "") THEN
  464. Skip; f := NIL;
  465. IF append THEN f := Files.Old(fn) END;
  466. IF (f = NIL) THEN f := Files.New(fn); Files.Register(f) END;
  467. IF (f # NIL) THEN
  468. IF append THEN
  469. Files.OpenWriter(fw, f, f.Length());
  470. ELSE
  471. Files.OpenWriter(fw, f, 0);
  472. END;
  473. cmd.context.Init(cmd.context.in, cmd.context.arg, fw, cmd.context.error, SELF);
  474. fw.Update;
  475. ELSE res := ErrFileNotFound (* cannot open output file *)
  476. END
  477. ELSE res := ErrInvalidFilename (* invalid filename *)
  478. END
  479. END
  480. END;
  481. IF (res = 0) THEN
  482. wait := ~Match(ampersand);
  483. WHILE (sym # eoln) & Match(whitespace) DO END;
  484. IF ~Match(eoln) THEN res := ErrEolnExpected END (* end of line expected *)
  485. END;
  486. IF (res # 0) THEN
  487. context.error.String("Error at position "); context.error.Int(pos, 0); context.error.String(": ");
  488. CASE res OF
  489. | ErrFileNotFound: COPY("file not found.", s)
  490. | ErrInvalidFilename: COPY("invalid file name.", s)
  491. | ErrAlreadyPiped: COPY("two input streams.", s)
  492. | ErrPipeAtBeginning: COPY("syntax error.", s)
  493. | ErrInvalidCommand: COPY("invalid command.", s)
  494. | ErrEolnExpected: COPY("too many arguments.", s)
  495. ELSE COPY("unknown error.", s)
  496. END;
  497. context.error.String(s); context.error.Ln; context.error.Update;
  498. command := NIL
  499. END;
  500. RETURN res
  501. END CmdLine;
  502. BEGIN
  503. wait := TRUE;
  504. RETURN CmdLine(cmd)
  505. END Parse;
  506. *)
  507. PROCEDURE ReadAlias(cmd : Command; verbose : BOOLEAN);
  508. VAR s: ARRAY MaxLen OF CHAR; alias, p, q: Alias; i, k: LONGINT; c: CHAR;
  509. BEGIN
  510. IF (cmd.parameters # "") THEN
  511. COPY(cmd.parameters, s);
  512. NEW(alias);
  513. i := 0; c := s[i];
  514. WHILE (c # 0X) & (c # "=") DO alias.alias[i] := c; INC(i); c := s[i] END;
  515. IF (c = "=") THEN
  516. k := 0; INC(i); c := s[i];
  517. WHILE (c # 0X) & (c # " ") & (c # TAB) DO alias.command[k] := c; INC(k); INC(i); c := s[i] END;
  518. END;
  519. IF verbose THEN context.out.String(alias.alias); END;
  520. IF (alias.command # "") THEN (* add an alias *)
  521. WHILE (c # 0X) & ((c = " ") OR (c = TAB)) DO INC(i); c := s[i] END;
  522. k := 0;
  523. WHILE (c # 0X) DO alias.parameters[k] := c; INC(k); INC(i); c := s[i] END;
  524. p := aliases; q := NIL;
  525. WHILE (p # NIL) & (p.alias < alias.alias) DO q := p; p := p.next END;
  526. IF (q = NIL) THEN aliases := alias; aliases.next := p
  527. ELSE q.next := alias; alias.next := p
  528. END;
  529. IF verbose THEN
  530. context.out.String(" = "); context.out.String(alias.command); context.out.Char(" "); context.out.String(alias.parameters);
  531. END;
  532. ELSE (* remove an alias *)
  533. p := aliases; q := NIL;
  534. WHILE (p # NIL) & (p.alias < alias.alias) DO q := p; p := p.next END;
  535. IF (p # NIL) & (p.alias = alias.alias) THEN
  536. IF (q = NIL) THEN aliases := aliases.next
  537. ELSE q.next := p.next
  538. END
  539. END;
  540. IF verbose THEN context.out.String(" removed"); END;
  541. END;
  542. IF verbose THEN context.out.Ln; END;
  543. ELSE (* list aliases *)
  544. p := aliases;
  545. WHILE (p # NIL) DO
  546. IF verbose THEN
  547. context.out.String(p.alias); context.out.String(" = "); context.out.String(p.command); context.out.Char(" ");
  548. context.out.String(p.parameters); context.out.Ln;
  549. END;
  550. p := p.next
  551. END
  552. END
  553. END ReadAlias;
  554. (*
  555. PROCEDURE ReplaceAlias(cmd: Command): BOOLEAN;
  556. VAR a: Alias; d, i: LONGINT;
  557. BEGIN
  558. a := aliases;
  559. WHILE (a # NIL) & (a.alias < cmd.command) DO a := a.next END;
  560. IF (a # NIL) & (a.alias = cmd.command) THEN
  561. COPY(a.command, cmd.command);
  562. IF (a.parameters # "") THEN
  563. IF (cmd.parameters = "") THEN COPY(a.parameters, cmd.parameters)
  564. ELSE
  565. d := Strings.Length(a.parameters) + 1;
  566. FOR i := Strings.Length(cmd.parameters) TO 0 BY -1 DO
  567. cmd.parameters[i+d] := cmd.parameters[i]
  568. END;
  569. FOR i := 0 TO d-2 DO cmd.parameters[i] := a.parameters[i] END;
  570. cmd.parameters[d-1] := " "
  571. END
  572. END;
  573. RETURN TRUE
  574. ELSE
  575. RETURN FALSE
  576. END
  577. END ReplaceAlias;
  578. PROCEDURE ShowHelp;
  579. BEGIN
  580. context.out.String("--- Help --- "); context.out.Ln;
  581. context.out.String("alias: Show list of aliases"); context.out.Ln;
  582. context.out.String("alias 'string'='command': Create alias for command"); context.out.Ln;
  583. context.out.String("alias 'string': Remove alias"); context.out.Ln;
  584. context.out.String("batch: start a new instance of Shell"); context.out.Ln;
  585. context.out.String("clear: Clear screen"); context.out.Ln;
  586. context.out.String("version: Show BimboShell version"); context.out.Ln;
  587. context.out.String("help: Show this help text"); context.out.Ln;
  588. context.out.String("exit: Exit Shell"); context.out.Ln;
  589. context.out.Update;
  590. END ShowHelp;
  591. PROCEDURE Execute(cmd: Command; wait: BOOLEAN; VAR exit: BOOLEAN);
  592. VAR
  593. c: Command; flags: SET;
  594. res : WORD; msg: ARRAY MaxLen OF CHAR; oldContext: Commands.Context;
  595. moduleName, commandName : Modules.Name; errormsg : ARRAY 128 OF CHAR;
  596. BEGIN
  597. IF (cmd.command = "alias") THEN
  598. ReadAlias(cmd, TRUE)
  599. ELSIF (cmd.command = "loadalias") THEN
  600. LoadAliasesFromFile(cmd.parameters);
  601. ELSIF (cmd.command = "batch") THEN
  602. context.out.String(Version); context.out.Ln; context.out.Update;
  603. oldContext := context; context := cmd.context;
  604. INC(nestingLevel);
  605. Run;
  606. context := oldContext
  607. ELSIF (cmd.command = "exit") THEN
  608. DEC(nestingLevel);
  609. exit := TRUE
  610. ELSIF (cmd.command = "version") THEN
  611. context.out.String(Version); context.out.Ln; context.out.Update;
  612. ELSIF (cmd.command = "help") THEN
  613. ShowHelp;
  614. ELSIF (cmd.command = "clear") THEN
  615. IF upcall # NIL THEN upcall(Clear); END;
  616. ELSE
  617. c := cmd; res := 0;
  618. WHILE (c # NIL) & (res = 0) DO
  619. IF (c.next = NIL) & wait THEN flags := {Commands.Wait}
  620. ELSE flags := {}
  621. END;
  622. Commands.Split(c.command, moduleName, commandName, res, errormsg);
  623. IF (res # Commands.Ok) THEN
  624. context.error.String(errormsg); context.error.Ln;
  625. ELSE
  626. Commands.Activate(c.command, c.context, flags, res, msg);
  627. (* IF wait & (cmd.pipe # NIL) THEN
  628. KernelLog.String("Pipe closed"); KernelLog.Ln;
  629. cmd.pipe.Close;
  630. END; *)
  631. IF (res # 0) THEN
  632. context.error.String("Error in command: "); context.error.String(cmd.command);
  633. context.error.String(", params: ");
  634. IF c.parameters # "" THEN
  635. context.error.String(c.parameters);
  636. ELSE
  637. context.error.String("None");
  638. END;
  639. context.error.String(", res: "); context.error.Int(res, 0);
  640. context.error.String(" ("); context.error.String(msg); context.error.Char(")");
  641. context.error.Ln
  642. ELSE c := c.next
  643. END;
  644. END;
  645. END
  646. END;
  647. context.out.Update; context.error.Update
  648. END Execute;
  649. *)
  650. TYPE
  651. StringType = POINTER TO ARRAY OF CHAR;
  652. Reader* = OBJECT (Streams.Reader)
  653. VAR length : LONGINT;
  654. data : StringType;
  655. rofs: LONGINT;
  656. PROCEDURE &Init*(initialSize : LONGINT);
  657. BEGIN
  658. IF initialSize < 256 THEN initialSize := 256 END;
  659. NEW(data, initialSize); length := 0; rofs := 0;
  660. InitReader( Receive, initialSize )
  661. END Init;
  662. PROCEDURE Add*(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: WORD);
  663. VAR i,pos : LONGINT; n: StringType;
  664. BEGIN{EXCLUSIVE}
  665. IF length + len + 1 >= LEN(data) THEN
  666. NEW(n, LEN(data) + len + 1); FOR i := 0 TO length - 1 DO n[i] := data[i] END;
  667. data := n
  668. END;
  669. pos := (rofs + length) MOD LEN(data);
  670. WHILE (len > 0) & (buf[ofs] # 0X) DO
  671. data[pos] := buf[ofs];
  672. pos := (pos+1) MOD LEN(data);
  673. INC(ofs); INC(length); DEC(len)
  674. END;
  675. END Add;
  676. PROCEDURE Receive( VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT );
  677. VAR o,pos: LONGINT;
  678. BEGIN{EXCLUSIVE}
  679. AWAIT(length >= min);
  680. pos := rofs;
  681. len := 0;
  682. WHILE (length > 0) & (size >0) DO
  683. buf[ofs] := data[pos];
  684. pos := (pos + 1) MOD LEN(data);
  685. INC(ofs); DEC(length); INC(len); DEC(size);
  686. END;
  687. rofs := pos;
  688. IF ofs < size THEN
  689. buf[ofs] := 0X; (* safety / trace *)
  690. END;
  691. END Receive;
  692. END Reader;
  693. PROCEDURE Run;
  694. VAR cmdList: Command; wait, exit: BOOLEAN; i : LONGINT; interpreter: Interpreter.Interpreter; s: Scanner.StringMaker; w: Streams.Writer; r: Streams.Reader;
  695. scanner: Scanner.Scanner; parser: Interpreter.Parser;
  696. diagnostics: Diagnostics.StreamDiagnostics; seq: SyntaxTree.StatementSequence;
  697. str: Scanner.StringType; len: LONGINT; container: Interpreter.Container; scope: Interpreter.Scope; e: SyntaxTree.Expression; value: Interpreter.Value;
  698. reader: Reader;
  699. runner: OBJECT
  700. VAR
  701. r: Streams.Reader;
  702. scanner: Scanner.Scanner; parser: Interpreter.Parser;
  703. stm: SyntaxTree.Statement;
  704. diagnostics: Diagnostics.Diagnostics;
  705. seq: SyntaxTree.StatementSequence;
  706. expression: SyntaxTree.Expression;
  707. interpreter: Interpreter.Interpreter;
  708. container: Interpreter.Container; scope: Interpreter.Scope;
  709. context: Commands.Context;
  710. value: Interpreter.Value;
  711. first: BOOLEAN;
  712. PROCEDURE &Init(r: Streams.Reader; diag: Diagnostics.Diagnostics; ctxt: Commands.Context);
  713. BEGIN
  714. SELF.r := r; diagnostics := diag; SELF.r := r;
  715. context := ctxt;
  716. END Init;
  717. BEGIN{ACTIVE}
  718. first := TRUE;
  719. ASSERT(diagnostics # NIL);
  720. context.out.Ln;
  721. context.out.String(">");
  722. context.out.Update;
  723. scanner := Scanner.NewScanner("", r, 0, diagnostics);
  724. scanner.SetCase(Scanner.Lowercase);
  725. NEW(parser, scanner, diagnostics); (* silent *)
  726. parser.SetLax;
  727. NEW(container);
  728. NEW(scope, Interpreter.global, container);
  729. NEW(interpreter, scope, diagnostics, context);
  730. LOOP
  731. (*diagnostics.Information("interpreter",Streams.Invalid,"start statement");*)
  732. seq := SyntaxTree.NewStatementSequence();
  733. IF parser.Optional(Scanner.Questionmark) THEN
  734. first := TRUE;
  735. expression := parser.Expression();
  736. IF interpreter.GetValue(expression, value) THEN
  737. value.WriteValue(context.out);
  738. ELSE
  739. context.out.String("NIL")
  740. END;
  741. context.out.Ln;
  742. context.out.String(">");
  743. context.out.Update;
  744. WHILE parser.Optional(Scanner.Escape) DO
  745. END;
  746. ELSIF parser.Statement(seq, NIL) THEN
  747. first := TRUE;
  748. (*Printout.Info("executing ", seq);*)
  749. interpreter.StatementSequence(seq);
  750. context.out.Update;
  751. context.out.Ln;
  752. context.out.String(">");
  753. context.out.Update;
  754. IF interpreter.error THEN interpreter.Reset END;
  755. WHILE parser.Optional(Scanner.Escape) OR parser.Optional(Scanner.Semicolon) DO
  756. (*TRACE(parser.Token());*)
  757. END;
  758. IF interpreter.error THEN interpreter.Reset END;
  759. ELSE
  760. IF ~parser.error & first THEN
  761. diagnostics.Error("",Streams.Invalid, "no statement");
  762. first := FALSE;
  763. context.out.Ln;
  764. context.out.String(">");
  765. context.out.Update;
  766. END;
  767. IF parser.error THEN parser.Reset END;
  768. parser.NextSymbol;
  769. (*NEW(scanner, "",r, 0, diagnostics);
  770. NEW(parser, scanner, diagnostics); (* silent *)*)
  771. END;
  772. END;
  773. END;
  774. BEGIN
  775. NEW(s,0);
  776. w := s.GetWriter();
  777. NEW(diagnostics, context.out);
  778. exit := FALSE;
  779. (*NEW(container);
  780. NEW(scope, Interpreter.global, container);
  781. NEW(interpreter, scope, diagnostics, context);
  782. *)
  783. NEW(reader, 1024);
  784. (*NEW(w, reader.Add,1024);*)
  785. NEW(runner, reader, diagnostics, context);
  786. (*seq := parser.StatementSequence(NIL);*)
  787. WHILE ~close & ~exit & (context.in.res = Streams.Ok) DO
  788. s.Clear;
  789. ReadCommand(w);w.Char(Escape);w.Ln; w.Update;(*
  790. context.out.Ln; context.out.String("------------");
  791. context.out.Ln; context.out.Update;
  792. *)
  793. str := s.GetString(len);
  794. reader.Add(str^,0,len,TRUE,res);
  795. (*
  796. NEW(scanner, "", s.GetReader(), 0, diagnostics);
  797. NEW(parser, scanner, NIL); (* silent *)
  798. *)
  799. (*
  800. e := parser.Expression();
  801. interpreter.Reset;
  802. IF ~parser.error & parser.Optional(Scanner.EndOfText) THEN
  803. IF interpreter.GetValue(e,value) THEN
  804. value(InterpreterSymbols.Value).WriteValue(context.out); context.out.Update;
  805. END;
  806. ELSE
  807. str := s.GetString(len);
  808. NEW(scanner, "", s.GetReader(), 0, diagnostics);
  809. NEW(parser, scanner, diagnostics);
  810. *)
  811. (*
  812. seq := parser.StatementSequence(NIL);
  813. IF parser.Mandatory(Scanner.EndOfText) THEN
  814. interpreter.StatementSequence(seq);
  815. IF ~interpreter.error THEN
  816. context.out.String("[ok]");
  817. END;
  818. END;
  819. *)
  820. (*END;*)
  821. END;
  822. context.out.Update; context.error.Update
  823. END Run;
  824. PROCEDURE AwaitDeath*;
  825. BEGIN {EXCLUSIVE}
  826. AWAIT(dead)
  827. END AwaitDeath;
  828. PROCEDURE SetUpcall*(proc : NotifyProcedure);
  829. BEGIN
  830. ASSERT((proc # NIL) & (upcall = NIL));
  831. upcall := proc;
  832. END SetUpcall;
  833. PROCEDURE ParseAliases(r : Files.Reader);
  834. VAR cmd : Command;
  835. BEGIN
  836. NEW(cmd);
  837. LOOP
  838. cmd.parameters := "";
  839. r.Ln(cmd.parameters);
  840. IF r.res # Streams.Ok THEN EXIT; END;
  841. ReadAlias(cmd, FALSE);
  842. END;
  843. END ParseAliases;
  844. (* Read aliases from specified file. Returns NIL if file not found or parsing failed. *)
  845. PROCEDURE LoadAliasesFromFile(filename : ARRAY OF CHAR);
  846. VAR in : Files.Reader; f : Files.File;
  847. BEGIN
  848. IF filename = "" THEN COPY(DefaultAliasFile, filename); END;
  849. f := Files.Old(filename);
  850. IF f # NIL THEN
  851. Files.OpenReader(in, f, 0);
  852. IF in # NIL THEN
  853. context.out.String("Loading aliases from "); context.out.String(filename); context.out.String("...");
  854. ParseAliases(in);
  855. context.out.String("done."); context.out.Ln;
  856. END;
  857. ELSE
  858. context.out.String("Loading aliases failed: File "); context.out.String(filename);
  859. context.out.String(" not found."); context.out.Ln;
  860. END;
  861. context.out.Update;
  862. END LoadAliasesFromFile;
  863. BEGIN {ACTIVE, SAFE}
  864. context.out.String(Version); context.out.Ln;
  865. context.out.String("Enter statement sequence in lower case with lax syntax"); context.out.Ln;
  866. context.out.Update;
  867. Run;
  868. IF (upcall # NIL) THEN upcall(ExitShell); END;
  869. BEGIN {EXCLUSIVE} dead := TRUE; END;
  870. END Shell;
  871. PROCEDURE Start*(context: Commands.Context);
  872. VAR shell: Shell;
  873. BEGIN
  874. NEW(shell, context.in, context.out, context.error, FALSE, ">");
  875. shell.AwaitDeath()
  876. END Start;
  877. END InterpreterShell.
  878. System.Free WMInterpreterShell InterpreterShell FoxInterpreter FoxInterpreterSymbols Test ~
  879. WMInterpreterShell.Open ~
  880. try this:
  881. o := context.out
  882. for i := 0 to 100 do
  883. o.String("i = "); o.Int(i,1); o.Ln
  884. end
  885. CMD "System.Show ?{i}?"