InterpreterShell.Mod 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. MODULE InterpreterShell; (** AUTHOR "be"; PURPOSE "Simple command shell" **)
  2. (**
  3. * Simple echo-based command shell.
  4. *
  5. * History:
  6. *
  7. * 16.05.2006 Added command history, backspace key handling, factored out serial port related code into ShellSerials.Mod (staubesv)
  8. *
  9. *)
  10. IMPORT Modules, Commands, Streams, Pipes, Strings, Files, Interpreter := FoxInterpreter, Diagnostics, Scanner := FoxScanner, SyntaxTree := FoxSyntaxTree, Printout := FoxPrintout, InterpreterSymbols := FoxInterpreterSymbols;
  11. CONST
  12. (* Notify procedure command codes *)
  13. ExitShell* = 1;
  14. Clear* = 2;
  15. Version = "InterpreterShell v1.0";
  16. DefaultAliasFile = "Shell.Alias";
  17. NestingLevelIndicator = ">";
  18. MaxLen = 512;
  19. CmdLen = 64;
  20. ParamLen = MaxLen;
  21. CR = 0DX; LF = 0AX; TAB = 9X;
  22. Backspace = 08X;
  23. Space = 20X;
  24. Delete = 7FX;
  25. Escape = 1BX;
  26. EscapeChar1 = Escape;
  27. EscapeChar2 = '[';
  28. (* Non-ASCII characters *)
  29. CursorUp = 0C1X;
  30. CursorDown = 0C2X;
  31. (* symbols *)
  32. start = {};
  33. inputFile = {0}; (* 01H *)
  34. pipe = {1}; (* 02H *)
  35. outputFile = {2}; (* 04H *)
  36. outputFileAppend = {3}; (* 08H *)
  37. ampersand = {4}; (* 10H *)
  38. whitespace = {5}; (* 20H *)
  39. eoln = {6}; (* 40H *)
  40. char = {7}; (* 80H *)
  41. EndOfParam = pipe + inputFile + outputFile + outputFileAppend + ampersand + eoln;
  42. (* errors *)
  43. ErrFileNotFound = 1;
  44. ErrInvalidFilename = 2;
  45. ErrAlreadyPiped = 3;
  46. ErrPipeAtBeginning = 4;
  47. ErrInvalidCommand = 5;
  48. ErrEolnExpected = 6;
  49. TYPE
  50. CommandsString = POINTER TO RECORD
  51. prev, next: CommandsString;
  52. string: ARRAY MaxLen OF CHAR;
  53. END;
  54. CommandHistory = OBJECT
  55. VAR
  56. first, current: CommandsString;
  57. PROCEDURE GetNextCommand(VAR cmd : ARRAY OF CHAR);
  58. BEGIN
  59. IF first = NIL THEN RETURN END;
  60. IF current = NIL THEN current := first ELSE current := current.next END;
  61. COPY(current.string, cmd);
  62. END GetNextCommand;
  63. PROCEDURE GetPreviousCommand(VAR cmd : ARRAY OF CHAR);
  64. BEGIN
  65. IF first = NIL THEN RETURN END;
  66. IF current = NIL THEN current := first.prev ELSE current := current.prev END;
  67. COPY(current.string, cmd);
  68. END GetPreviousCommand;
  69. PROCEDURE AddCommand(CONST cmd : ARRAY OF CHAR);
  70. VAR command: CommandsString;
  71. BEGIN
  72. IF (cmd = "") THEN (* Don't add to history *) RETURN; END;
  73. command := first;
  74. IF command # NIL THEN
  75. WHILE (command.string # cmd) & (command.next # first) DO command := command.next END;
  76. IF command.string # cmd THEN command := NIL END
  77. END;
  78. IF command # NIL THEN
  79. IF first = command THEN first := command.next END;
  80. command.prev.next := command.next;
  81. command.next.prev := command.prev;
  82. ELSE
  83. NEW (command);
  84. COPY (cmd, command.string);
  85. END;
  86. IF first = NIL THEN
  87. first := command; first.next := first; first.prev := first
  88. ELSE
  89. command.prev := first.prev; command.next := first;
  90. first.prev.next := command; first.prev := command;
  91. END;
  92. current := NIL;
  93. END AddCommand;
  94. PROCEDURE &Init*;
  95. BEGIN first := NIL; current := NIL;
  96. END Init;
  97. END CommandHistory;
  98. TYPE
  99. Command = POINTER TO RECORD
  100. command: ARRAY CmdLen OF CHAR; (* command (e.g. <module>"."<command> *)
  101. parameters: ARRAY ParamLen OF CHAR; (* parameters *)
  102. context: Commands.Context; (* context (in, out & err streams *)
  103. pipe : Pipes.Pipe;
  104. next: Command;
  105. END;
  106. Alias = POINTER TO RECORD
  107. alias,
  108. command: ARRAY CmdLen OF CHAR;
  109. parameters: ARRAY ParamLen OF CHAR;
  110. next: Alias;
  111. END;
  112. NotifyProcedure* = PROCEDURE {DELEGATE} (command : LONGINT);
  113. TYPE
  114. (*
  115. Blocker* = OBJECT (Streams.Writer)
  116. VAR
  117. interpreter: Interpreter.Interpreter; i: LONGINT;
  118. parser: Interpreter.Parser;
  119. reader-: Streams.Reader;
  120. writer-: Streams.Writer;
  121. scanner: Scanner.Scanner;
  122. diagnostics: Diagnostics.StreamDiagnostics;
  123. PROCEDURE & InitBlocker(context: Commands.Context);
  124. VAR pipe: Pipes.Pipe;
  125. BEGIN
  126. TRACE(1);
  127. NEW(diagnostics, context.error);
  128. TRACE(2);
  129. NEW(pipe, 256);
  130. TRACE(3);
  131. NEW(reader, pipe.Receive, 256);
  132. TRACE(4);
  133. NEW(writer, pipe.Send, 256);
  134. END InitBlocker;
  135. PROCEDURE Statements;
  136. VAR statement: SyntaxTree.Statement; statements: SyntaxTree.StatementSequence;
  137. BEGIN
  138. NEW(scanner, "", reader, 0, diagnostics);
  139. TRACE(6);
  140. NEW(parser, scanner, diagnostics);
  141. TRACE(7);
  142. statements := SyntaxTree.NewStatementSequence();
  143. LOOP
  144. WHILE parser.Statement(statements, NIL) DO TRACE("parser statement");
  145. IF parser.Optional(Scanner.Semicolon) THEN END;
  146. END;
  147. TRACE("failure");
  148. END;
  149. END Statements;
  150. BEGIN{ACTIVE}
  151. Statements
  152. END Blocker;
  153. *)
  154. Shell* = OBJECT
  155. VAR
  156. echo, dead, close: BOOLEAN;
  157. context: Commands.Context;
  158. command: ARRAY MaxLen OF CHAR;
  159. res: LONGINT;
  160. nestingLevel : LONGINT; (* how many shells run in this shell? *)
  161. aliases: Alias;
  162. prompt: ARRAY 32 OF CHAR;
  163. (* Connection to the entiry hosting this shell instance *)
  164. upcall : NotifyProcedure;
  165. commandHistory : CommandHistory;
  166. PROCEDURE &Init*(in: Streams.Reader; out, err: Streams.Writer; echo: BOOLEAN; CONST prompt: ARRAY OF CHAR);
  167. BEGIN
  168. ASSERT((in # NIL) & (out # NIL) & (err # NIL));
  169. NEW(context, in, NIL, out, err, SELF);
  170. close := FALSE; dead := FALSE; command[0] := 0X; res := 0; SELF.echo := echo; COPY(prompt, SELF.prompt);
  171. NEW(commandHistory);
  172. END Init;
  173. PROCEDURE Exit*;
  174. BEGIN
  175. close := TRUE;
  176. END Exit;
  177. PROCEDURE DeleteStringFromDisplay(CONST x : ARRAY OF CHAR);
  178. VAR i, len : LONGINT;
  179. BEGIN
  180. len := Strings.Length(x);
  181. FOR i := 0 TO len-1 DO context.out.Char(Backspace); END;
  182. FOR i := 0 TO len-1 DO context.out.Char(Space); END;
  183. FOR i := 0 TO len-1 DO context.out.Char(Backspace); END;
  184. END DeleteStringFromDisplay;
  185. PROCEDURE ReadCommand(w: Streams.Writer);
  186. VAR
  187. ch: CHAR;
  188. currentIndex : LONGINT;
  189. PROCEDURE IsAsciiCharacter(ch : CHAR) : BOOLEAN;
  190. BEGIN
  191. RETURN ORD(ch) <= 127;
  192. END IsAsciiCharacter;
  193. PROCEDURE IsControlCharacter(ch : CHAR) : BOOLEAN;
  194. BEGIN
  195. RETURN ORD(ch) < 32;
  196. END IsControlCharacter;
  197. PROCEDURE HandleEscapeSequence;
  198. BEGIN
  199. ch := context.in.Get();
  200. ch := CHR(ORD(ch)+128);
  201. IF (ch = CursorDown) OR (ch = CursorUp) THEN (* Command History Keys *)
  202. command[currentIndex+1] := 0X;
  203. DeleteStringFromDisplay(command);
  204. IF ch = CursorUp THEN
  205. commandHistory.GetPreviousCommand(command);
  206. ELSE
  207. commandHistory.GetNextCommand(command);
  208. END;
  209. currentIndex := Strings.Length(command)-1;
  210. IF echo & (command # "") THEN context.out.String(command); context.out.Update; END;
  211. ELSE
  212. (* ignore escaped character *)
  213. END;
  214. END HandleEscapeSequence;
  215. BEGIN
  216. command := ""; currentIndex := -1;
  217. LOOP
  218. ch := context.in.Get();
  219. IF IsAsciiCharacter(ch) THEN
  220. IF IsControlCharacter(ch) OR (ch = Delete) THEN
  221. IF (ch = Streams.EOT) OR (context.in.res # Streams.Ok) THEN
  222. EXIT
  223. ELSIF (ch = Backspace) OR (ch = Delete)THEN
  224. IF currentIndex >= 0 THEN (* There is a character at the left of the cursor *)
  225. IF command[currentIndex] = CR THEN
  226. context.out.Char(Backspace); context.out.Char(Space); context.out.Char(Backspace); context.out.Update;
  227. END;
  228. command[currentIndex] := 0X;
  229. DEC(currentIndex);
  230. IF echo THEN
  231. context.out.Char(Backspace); context.out.Char(Space); context.out.Char(Backspace); context.out.Update;
  232. END;
  233. END;
  234. ELSIF (ORD(ch) = 03H) THEN
  235. (* IF runner # NIL THEN AosActive.TerminateThis(runner); END; *)
  236. ELSIF (ch = EscapeChar1) THEN (* Escape sequence *)
  237. IF context.in.Peek() = EscapeChar2 THEN ch := context.in.Get(); HandleEscapeSequence;
  238. ELSIF context.in.Peek() = 0DX THEN (* command *)
  239. ch := context.in.Get();
  240. EXIT;
  241. ELSIF context.in.Peek () = Escape THEN
  242. command[currentIndex+1] := 0X;
  243. DeleteStringFromDisplay (command); context.out.Update;
  244. ch := context.in.Get (); command := ""; currentIndex := -1;
  245. END;
  246. ELSIF (ch =CR) OR (ch = LF) THEN
  247. INC(currentIndex); command[currentIndex] := ch;
  248. IF (ch = CR) & (context.in.Available() > 0) & (context.in.Peek() = LF) THEN
  249. ch := context.in.Get();
  250. INC(currentIndex); command[currentIndex] := ch;
  251. END;
  252. IF echo THEN context.out.Ln; context.out.Update END;
  253. ELSE
  254. INC(currentIndex);
  255. command[currentIndex] := ch;
  256. IF echo THEN context.out.Char(ch); context.out.Update; END;
  257. END;
  258. ELSE
  259. IF currentIndex <= LEN(command) - 2 (* Always need space for 0X *) THEN
  260. INC(currentIndex);
  261. command[currentIndex] := ch;
  262. IF echo THEN context.out.Char(ch); context.out.Update; END;
  263. END;
  264. END;
  265. ELSE
  266. (* ignore non-ascii characters *)
  267. END;
  268. END;
  269. command[currentIndex+1] := 0X;
  270. IF ch = CR THEN
  271. commandHistory.AddCommand(command);
  272. IF (context.in.Available() > 0) & (context.in.Peek() = LF) THEN ch := context.in.Get() END;
  273. (* IF echo THEN context.out.Ln; context.out.Update END; *)
  274. w.String(command);
  275. END;
  276. END ReadCommand;
  277. (*
  278. PROCEDURE Parse(VAR cmd: Command; VAR wait: BOOLEAN): LONGINT;
  279. VAR sym: SET; pos: LONGINT; c, next: CHAR;
  280. PROCEDURE Init;
  281. BEGIN
  282. pos := 0; c := 0X; next := command[pos]; sym := start; Scan
  283. END Init;
  284. PROCEDURE Scan;
  285. BEGIN
  286. IF (sym # eoln) THEN
  287. c := next; INC(pos); next := command[pos];
  288. CASE c OF
  289. | "<": sym := inputFile
  290. | "|": sym := pipe
  291. | ">": IF (next = ">") THEN sym := outputFileAppend; INC(pos); next := command[pos]; ELSE sym := outputFile END
  292. | "&": sym := ampersand
  293. | " ", 09X: sym := whitespace
  294. | 0X: sym := eoln
  295. ELSE sym := char
  296. END
  297. END
  298. END Scan;
  299. PROCEDURE Match(symbol: SET): BOOLEAN;
  300. BEGIN IF (symbol = sym) THEN Scan; RETURN TRUE ELSE RETURN FALSE END
  301. END Match;
  302. PROCEDURE Skip;
  303. BEGIN
  304. WHILE (sym = whitespace) & (sym # eoln) DO Scan END
  305. END Skip;
  306. PROCEDURE Token(VAR s: ARRAY OF CHAR; cond: SET): BOOLEAN;
  307. VAR i: LONGINT; quote: BOOLEAN;
  308. BEGIN
  309. quote := FALSE;
  310. WHILE (sym * cond = {}) OR (quote & (sym # eoln)) DO
  311. s[i] := c; INC(i); IF (c = '"') OR (c = "'") THEN quote := ~quote END; Scan
  312. END;
  313. s[i] := 0X;
  314. RETURN ~quote
  315. END Token;
  316. PROCEDURE Cmd(): Command;
  317. VAR i: LONGINT; cmd: Command; arg : Streams.StringReader;
  318. BEGIN Skip;
  319. IF (sym = char) THEN
  320. NEW(cmd);
  321. i := 0;
  322. WHILE (sym = char) DO cmd.command[i] := c; INC(i); Scan END; cmd.command[i] := 0X; Skip;
  323. IF (cmd.command # "") THEN
  324. IF (sym * EndOfParam = {}) THEN
  325. IF ~Token(cmd.parameters, EndOfParam) THEN cmd := NIL END
  326. END;
  327. REPEAT UNTIL ~ReplaceAlias(cmd);
  328. NEW(arg, LEN(cmd.parameters)); arg.SetRaw(cmd.parameters, 0, LEN(cmd.parameters));
  329. NEW(cmd.context, context.in, arg, context.out, context.error, SELF);
  330. ELSE cmd := NIL (* invalid command (empty string) *)
  331. END
  332. ELSE cmd := NIL
  333. END;
  334. RETURN cmd
  335. END Cmd;
  336. PROCEDURE CmdLine(VAR command: Command): LONGINT;
  337. VAR cmd, prev: Command; fn: Files.FileName; f: Files.File; fr: Files.Reader; fw: Files.Writer;
  338. r: Streams.Reader; w: Streams.Writer; append, piped: BOOLEAN; s: ARRAY 64 OF CHAR;
  339. BEGIN
  340. cmd := NIL; prev := NIL; command := NIL; res := 0; piped := FALSE;
  341. Init;
  342. REPEAT
  343. cmd := Cmd();
  344. IF (cmd # NIL) THEN
  345. IF (command = NIL) THEN command := cmd END;
  346. IF piped THEN
  347. piped := FALSE;
  348. IF (prev # NIL) THEN
  349. IF (prev.context.out = context.out) & (cmd.context.in = context.in) THEN
  350. NEW(prev.pipe, 1024);
  351. Streams.OpenReader(r, prev.pipe.Receive); Streams.OpenWriter(w, prev.pipe.Send);
  352. prev.context.Init(r, prev.context.arg, w, prev.context.error, SELF);
  353. prev.next := cmd
  354. ELSE res := ErrAlreadyPiped (* already piped *)
  355. END
  356. ELSE res := ErrPipeAtBeginning (* pipe cannot be first symbol *)
  357. END
  358. END;
  359. IF Match(inputFile) THEN (* "<" filename *)
  360. IF (cmd.context.in = context.in) THEN
  361. Skip;
  362. IF Token(fn, -char) & (fn # "") THEN
  363. f := Files.Old(fn);
  364. IF (f # NIL) THEN
  365. Files.OpenReader(fr, f, 0);
  366. cmd.context.Init(fr, cmd.context.arg, cmd.context.out, cmd.context.error, SELF)
  367. ELSE res := ErrFileNotFound (* file not found *)
  368. END
  369. ELSE res := ErrInvalidFilename (* invalid filename *)
  370. END
  371. ELSE res := ErrAlreadyPiped (* error: already piped *)
  372. END
  373. ELSIF Match(pipe) THEN (* "|" command *)
  374. piped := TRUE
  375. END;
  376. prev := cmd
  377. ELSE res := ErrInvalidCommand (* invalid command *)
  378. END
  379. UNTIL (res # 0) OR (cmd = NIL) OR ~piped;
  380. IF (res = 0) THEN
  381. IF (sym * (outputFile+outputFileAppend) # {}) THEN (* ">"[">"] filename *)
  382. append := (sym = outputFileAppend);
  383. Scan; Skip;
  384. IF Token (fn, EndOfParam (*-char *)) & (fn # "") THEN
  385. Skip; f := NIL;
  386. IF append THEN f := Files.Old(fn) END;
  387. IF (f = NIL) THEN f := Files.New(fn); Files.Register(f) END;
  388. IF (f # NIL) THEN
  389. IF append THEN
  390. Files.OpenWriter(fw, f, f.Length());
  391. ELSE
  392. Files.OpenWriter(fw, f, 0);
  393. END;
  394. cmd.context.Init(cmd.context.in, cmd.context.arg, fw, cmd.context.error, SELF);
  395. fw.Update;
  396. ELSE res := ErrFileNotFound (* cannot open output file *)
  397. END
  398. ELSE res := ErrInvalidFilename (* invalid filename *)
  399. END
  400. END
  401. END;
  402. IF (res = 0) THEN
  403. wait := ~Match(ampersand);
  404. WHILE (sym # eoln) & Match(whitespace) DO END;
  405. IF ~Match(eoln) THEN res := ErrEolnExpected END (* end of line expected *)
  406. END;
  407. IF (res # 0) THEN
  408. context.error.String("Error at position "); context.error.Int(pos, 0); context.error.String(": ");
  409. CASE res OF
  410. | ErrFileNotFound: COPY("file not found.", s)
  411. | ErrInvalidFilename: COPY("invalid file name.", s)
  412. | ErrAlreadyPiped: COPY("two input streams.", s)
  413. | ErrPipeAtBeginning: COPY("syntax error.", s)
  414. | ErrInvalidCommand: COPY("invalid command.", s)
  415. | ErrEolnExpected: COPY("too many arguments.", s)
  416. ELSE COPY("unknown error.", s)
  417. END;
  418. context.error.String(s); context.error.Ln; context.error.Update;
  419. command := NIL
  420. END;
  421. RETURN res
  422. END CmdLine;
  423. BEGIN
  424. wait := TRUE;
  425. RETURN CmdLine(cmd)
  426. END Parse;
  427. *)
  428. PROCEDURE ReadAlias(cmd : Command; verbose : BOOLEAN);
  429. VAR s: ARRAY MaxLen OF CHAR; alias, p, q: Alias; i, k: LONGINT; c: CHAR;
  430. BEGIN
  431. IF (cmd.parameters # "") THEN
  432. COPY(cmd.parameters, s);
  433. NEW(alias);
  434. i := 0; c := s[i];
  435. WHILE (c # 0X) & (c # "=") DO alias.alias[i] := c; INC(i); c := s[i] END;
  436. IF (c = "=") THEN
  437. k := 0; INC(i); c := s[i];
  438. WHILE (c # 0X) & (c # " ") & (c # TAB) DO alias.command[k] := c; INC(k); INC(i); c := s[i] END;
  439. END;
  440. IF verbose THEN context.out.String(alias.alias); END;
  441. IF (alias.command # "") THEN (* add an alias *)
  442. WHILE (c # 0X) & ((c = " ") OR (c = TAB)) DO INC(i); c := s[i] END;
  443. k := 0;
  444. WHILE (c # 0X) DO alias.parameters[k] := c; INC(k); INC(i); c := s[i] END;
  445. p := aliases; q := NIL;
  446. WHILE (p # NIL) & (p.alias < alias.alias) DO q := p; p := p.next END;
  447. IF (q = NIL) THEN aliases := alias; aliases.next := p
  448. ELSE q.next := alias; alias.next := p
  449. END;
  450. IF verbose THEN
  451. context.out.String(" = "); context.out.String(alias.command); context.out.Char(" "); context.out.String(alias.parameters);
  452. END;
  453. ELSE (* remove an alias *)
  454. p := aliases; q := NIL;
  455. WHILE (p # NIL) & (p.alias < alias.alias) DO q := p; p := p.next END;
  456. IF (p # NIL) & (p.alias = alias.alias) THEN
  457. IF (q = NIL) THEN aliases := aliases.next
  458. ELSE q.next := p.next
  459. END
  460. END;
  461. IF verbose THEN context.out.String(" removed"); END;
  462. END;
  463. IF verbose THEN context.out.Ln; END;
  464. ELSE (* list aliases *)
  465. p := aliases;
  466. WHILE (p # NIL) DO
  467. IF verbose THEN
  468. context.out.String(p.alias); context.out.String(" = "); context.out.String(p.command); context.out.Char(" ");
  469. context.out.String(p.parameters); context.out.Ln;
  470. END;
  471. p := p.next
  472. END
  473. END
  474. END ReadAlias;
  475. (*
  476. PROCEDURE ReplaceAlias(cmd: Command): BOOLEAN;
  477. VAR a: Alias; d, i: LONGINT;
  478. BEGIN
  479. a := aliases;
  480. WHILE (a # NIL) & (a.alias < cmd.command) DO a := a.next END;
  481. IF (a # NIL) & (a.alias = cmd.command) THEN
  482. COPY(a.command, cmd.command);
  483. IF (a.parameters # "") THEN
  484. IF (cmd.parameters = "") THEN COPY(a.parameters, cmd.parameters)
  485. ELSE
  486. d := Strings.Length(a.parameters) + 1;
  487. FOR i := Strings.Length(cmd.parameters) TO 0 BY -1 DO
  488. cmd.parameters[i+d] := cmd.parameters[i]
  489. END;
  490. FOR i := 0 TO d-2 DO cmd.parameters[i] := a.parameters[i] END;
  491. cmd.parameters[d-1] := " "
  492. END
  493. END;
  494. RETURN TRUE
  495. ELSE
  496. RETURN FALSE
  497. END
  498. END ReplaceAlias;
  499. PROCEDURE ShowHelp;
  500. BEGIN
  501. context.out.String("--- Help --- "); context.out.Ln;
  502. context.out.String("alias: Show list of aliases"); context.out.Ln;
  503. context.out.String("alias 'string'='command': Create alias for command"); context.out.Ln;
  504. context.out.String("alias 'string': Remove alias"); context.out.Ln;
  505. context.out.String("batch: start a new instance of Shell"); context.out.Ln;
  506. context.out.String("clear: Clear screen"); context.out.Ln;
  507. context.out.String("version: Show BimboShell version"); context.out.Ln;
  508. context.out.String("help: Show this help text"); context.out.Ln;
  509. context.out.String("exit: Exit Shell"); context.out.Ln;
  510. context.out.Update;
  511. END ShowHelp;
  512. PROCEDURE Execute(cmd: Command; wait: BOOLEAN; VAR exit: BOOLEAN);
  513. VAR
  514. c: Command; flags: SET;
  515. res : LONGINT; msg: ARRAY MaxLen OF CHAR; oldContext: Commands.Context;
  516. moduleName, commandName : Modules.Name; errormsg : ARRAY 128 OF CHAR;
  517. BEGIN
  518. IF (cmd.command = "alias") THEN
  519. ReadAlias(cmd, TRUE)
  520. ELSIF (cmd.command = "loadalias") THEN
  521. LoadAliasesFromFile(cmd.parameters);
  522. ELSIF (cmd.command = "batch") THEN
  523. context.out.String(Version); context.out.Ln; context.out.Update;
  524. oldContext := context; context := cmd.context;
  525. INC(nestingLevel);
  526. Run;
  527. context := oldContext
  528. ELSIF (cmd.command = "exit") THEN
  529. DEC(nestingLevel);
  530. exit := TRUE
  531. ELSIF (cmd.command = "version") THEN
  532. context.out.String(Version); context.out.Ln; context.out.Update;
  533. ELSIF (cmd.command = "help") THEN
  534. ShowHelp;
  535. ELSIF (cmd.command = "clear") THEN
  536. IF upcall # NIL THEN upcall(Clear); END;
  537. ELSE
  538. c := cmd; res := 0;
  539. WHILE (c # NIL) & (res = 0) DO
  540. IF (c.next = NIL) & wait THEN flags := {Commands.Wait}
  541. ELSE flags := {}
  542. END;
  543. Commands.Split(c.command, moduleName, commandName, res, errormsg);
  544. IF (res # Commands.Ok) THEN
  545. context.error.String(errormsg); context.error.Ln;
  546. ELSE
  547. Commands.Activate(c.command, c.context, flags, res, msg);
  548. (* IF wait & (cmd.pipe # NIL) THEN
  549. KernelLog.String("Pipe closed"); KernelLog.Ln;
  550. cmd.pipe.Close;
  551. END; *)
  552. IF (res # 0) THEN
  553. context.error.String("Error in command: "); context.error.String(cmd.command);
  554. context.error.String(", params: ");
  555. IF c.parameters # "" THEN
  556. context.error.String(c.parameters);
  557. ELSE
  558. context.error.String("None");
  559. END;
  560. context.error.String(", res: "); context.error.Int(res, 0);
  561. context.error.String(" ("); context.error.String(msg); context.error.Char(")");
  562. context.error.Ln
  563. ELSE c := c.next
  564. END;
  565. END;
  566. END
  567. END;
  568. context.out.Update; context.error.Update
  569. END Execute;
  570. *)
  571. PROCEDURE Run;
  572. VAR cmdList: Command; wait, exit: BOOLEAN; i : LONGINT; interpreter: Interpreter.Interpreter; s: Scanner.StringMaker; w: Streams.Writer; r: Streams.Reader;
  573. scanner: Scanner.Scanner; parser: Interpreter.Parser; diagnostics: Diagnostics.StreamDiagnostics; seq: SyntaxTree.StatementSequence;
  574. str: Scanner.StringType; len: LONGINT; container: Interpreter.Container; scope: Interpreter.Scope; e: SyntaxTree.Expression; value: Interpreter.Value;
  575. BEGIN
  576. NEW(s,0);
  577. w := s.GetWriter();
  578. NEW(diagnostics, context.out);
  579. exit := FALSE;
  580. NEW(container);
  581. NEW(scope, Interpreter.global, container);
  582. NEW(interpreter, scope, diagnostics, context);
  583. WHILE ~close & ~exit & (context.in.res = Streams.Ok) DO
  584. IF (prompt # "") THEN
  585. context.out.Ln;
  586. context.out.String(prompt);
  587. FOR i := 0 TO nestingLevel-1 DO
  588. context.out.String(NestingLevelIndicator);
  589. END;
  590. context.out.Update
  591. END;
  592. s.Clear;
  593. ReadCommand(w);w.Update;
  594. context.out.Ln; context.out.String("------------");
  595. context.out.Ln; context.out.Update;
  596. str := s.GetString(len);
  597. NEW(scanner, "", s.GetReader(), 0, diagnostics);
  598. NEW(parser, scanner, NIL); (* silent *)
  599. e := parser.Expression();
  600. interpreter.Reset;
  601. IF ~parser.error & parser.Optional(Scanner.EndOfText) THEN
  602. IF interpreter.GetValue(e,value) THEN
  603. value(InterpreterSymbols.Value).WriteValue(context.out); context.out.Update;
  604. END;
  605. ELSE
  606. str := s.GetString(len);
  607. NEW(scanner, "", s.GetReader(), 0, diagnostics);
  608. NEW(parser, scanner, diagnostics);
  609. seq := parser.StatementSequence(NIL);
  610. IF parser.Mandatory(Scanner.EndOfText) THEN
  611. interpreter.StatementSequence(seq);
  612. IF ~interpreter.error THEN
  613. context.out.String("[ok]");
  614. END;
  615. END;
  616. END;
  617. END;
  618. context.out.Update; context.error.Update
  619. END Run;
  620. PROCEDURE AwaitDeath*;
  621. BEGIN {EXCLUSIVE}
  622. AWAIT(dead)
  623. END AwaitDeath;
  624. PROCEDURE SetUpcall*(proc : NotifyProcedure);
  625. BEGIN
  626. ASSERT((proc # NIL) & (upcall = NIL));
  627. upcall := proc;
  628. END SetUpcall;
  629. PROCEDURE ParseAliases(r : Files.Reader);
  630. VAR cmd : Command;
  631. BEGIN
  632. NEW(cmd);
  633. LOOP
  634. cmd.parameters := "";
  635. r.Ln(cmd.parameters);
  636. IF r.res # Streams.Ok THEN EXIT; END;
  637. ReadAlias(cmd, FALSE);
  638. END;
  639. END ParseAliases;
  640. (* Read aliases from specified file. Returns NIL if file not found or parsing failed. *)
  641. PROCEDURE LoadAliasesFromFile(filename : ARRAY OF CHAR);
  642. VAR in : Files.Reader; f : Files.File;
  643. BEGIN
  644. IF filename = "" THEN COPY(DefaultAliasFile, filename); END;
  645. f := Files.Old(filename);
  646. IF f # NIL THEN
  647. Files.OpenReader(in, f, 0);
  648. IF in # NIL THEN
  649. context.out.String("Loading aliases from "); context.out.String(filename); context.out.String("...");
  650. ParseAliases(in);
  651. context.out.String("done."); context.out.Ln;
  652. END;
  653. ELSE
  654. context.out.String("Loading aliases failed: File "); context.out.String(filename);
  655. context.out.String(" not found."); context.out.Ln;
  656. END;
  657. context.out.Update;
  658. END LoadAliasesFromFile;
  659. BEGIN {ACTIVE, SAFE}
  660. context.out.String(Version); context.out.Ln;
  661. context.out.String("Evaluate statement sequence with SHIFT-ENTER"); context.out.Ln;
  662. context.out.Update;
  663. Run;
  664. IF (upcall # NIL) THEN upcall(ExitShell); END;
  665. BEGIN {EXCLUSIVE} dead := TRUE; END;
  666. END Shell;
  667. END InterpreterShell.
  668. SystemTools.Free Shell ~
  669. WMShell.Open ~