Commands.Mod 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. (* Aos, Copyright 2001, Pieter Muller, ETH Zurich *)
  2. MODULE Commands; (** AUTHOR "pjm"; PURPOSE "Commands and parameters"; *)
  3. IMPORT Objects, Modules, Streams, KernelLog, Trace, Machine;
  4. CONST
  5. (** Activate flags. *)
  6. Wait* = 0; (** Wait until the activated command returns. *)
  7. InheritContext*= 1; (** Inherit context (as far as not overwritten) of the caller *)
  8. Silent*= 2;
  9. Ok* = 0;
  10. CommandNotFound* = 3901;
  11. CommandError* = 3902;
  12. CommandParseError* = 3903;
  13. CommandTrapped* = 3904;
  14. (* Separates module name from procedure name *)
  15. Delimiter* = ".";
  16. (* Runner states *)
  17. Started = 0; Loaded = 1; Finished = 2;
  18. TYPE
  19. Context* = OBJECT
  20. VAR
  21. in-, arg- : Streams.Reader;
  22. out-, error- : Streams.Writer;
  23. caller-: OBJECT;
  24. result*: WORD;
  25. PROCEDURE &Init*(in, arg : Streams.Reader; out, error : Streams.Writer; caller: OBJECT);
  26. BEGIN
  27. IF (in = NIL) THEN in := GetEmptyReader(); END;
  28. IF (arg = NIL) THEN arg := GetEmptyReader()END;
  29. IF (out = NIL) THEN NEW(out, KernelLog.Send, 128); END;
  30. IF (error = NIL) THEN NEW(error, KernelLog.Send, 128); END;
  31. SELF.in := in; SELF.arg := arg; SELF.out := out; SELF.error := error; SELF.caller := caller; SELF.result := Ok;
  32. ASSERT((in # NIL) & (arg # NIL) & (out # NIL) & (error # NIL));
  33. END Init;
  34. END Context;
  35. (* Procedure types that can be called be runner thread *)
  36. CommandProc = PROCEDURE;
  37. CommandContextProc = PROCEDURE(context : Context);
  38. TYPE
  39. Runner = OBJECT
  40. VAR
  41. moduleName, commandName : Modules.Name;
  42. context : Context;
  43. proc : CommandProc;
  44. commandProc : CommandContextProc;
  45. msg : ARRAY 128 OF CHAR; res : WORD;
  46. module : Modules.Module;
  47. state : WORD;
  48. exception : BOOLEAN;
  49. PROCEDURE &Init*(CONST moduleName, commandName : Modules.Name; context : Context);
  50. VAR origin: Streams.Position; char: CHAR;
  51. BEGIN
  52. SELF.moduleName := moduleName; SELF.commandName := commandName;
  53. IF (context = NIL) THEN NEW(context, NIL, NIL, NIL, NIL, NIL); END;
  54. IF trace THEN
  55. Trace.String("Commands.Activate ");
  56. Trace.String(moduleName); Trace.String(Delimiter); Trace.String(commandName);
  57. IF context.arg.CanSetPos () THEN
  58. origin := context.arg.Pos ();
  59. LOOP context.arg.Char (char); IF context.arg.res # Streams.Ok THEN EXIT END; Trace.Char (char); END;
  60. context.arg.SetPos (origin);
  61. END;
  62. Trace.Char ("~"); Trace.Ln;
  63. END;
  64. SELF.context := context;
  65. res := CommandError; COPY("Error starting command", msg);
  66. exception := FALSE;
  67. state := Started;
  68. END Init;
  69. PROCEDURE Join(this : WORD; VAR res : WORD; VAR msg : ARRAY OF CHAR);
  70. BEGIN {EXCLUSIVE}
  71. AWAIT(state >= this);
  72. res := SELF.res; COPY(SELF.msg, msg);
  73. END Join;
  74. BEGIN {ACTIVE, SAFE}
  75. Objects.SetContext(context);
  76. IF ~exception THEN
  77. exception := TRUE; (* catch exceptions from now on *)
  78. module := Modules.ThisModule(moduleName, res, msg);
  79. IF (res = Ok) THEN
  80. IF commandName # "" THEN
  81. GETPROCEDURE(moduleName, commandName, proc);
  82. IF (proc = NIL) THEN
  83. GETPROCEDURE(moduleName, commandName, commandProc);
  84. END;
  85. IF (proc = NIL) & (commandProc = NIL) THEN
  86. res := CommandNotFound;
  87. msg := "Command ";
  88. Modules.Append(moduleName, msg); Modules.Append(Delimiter, msg); Modules.Append(commandName, msg);
  89. Modules.Append(" not found", msg);
  90. END;
  91. END;
  92. END;
  93. BEGIN {EXCLUSIVE} state := Loaded; END;
  94. IF (res = Ok) THEN
  95. ASSERT((proc # NIL) OR (commandProc # NIL) OR (commandName = ""));
  96. IF (proc # NIL) THEN
  97. proc();
  98. context.out.Update; context.error.Update;
  99. ELSIF (commandProc # NIL) THEN
  100. ASSERT(context # NIL);
  101. commandProc(context);
  102. context.out.Update; context.error.Update;
  103. res := context.result;
  104. IF res # Ok THEN msg := "Command not successful"; END;
  105. END;
  106. END;
  107. ELSE
  108. res := CommandTrapped; COPY("Exception during command execution", msg);
  109. END;
  110. BEGIN {EXCLUSIVE} state := Finished; END;
  111. END Runner;
  112. VAR
  113. emptyString : ARRAY 1 OF CHAR;
  114. silentWriter: Streams.Writer;
  115. trace: BOOLEAN;
  116. defaultContext: Context; (* Fallback. Note that this context would be shared by different users -- may be used for tracing though *)
  117. (* Create a ready on a empty string *)
  118. PROCEDURE GetEmptyReader() : Streams.Reader;
  119. VAR reader : Streams.StringReader;
  120. BEGIN
  121. NEW(reader, 1); reader.SetRaw(emptyString, 0, 1);
  122. RETURN reader;
  123. END GetEmptyReader;
  124. PROCEDURE SendNothing(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: WORD);
  125. END SendNothing;
  126. (** Splits a command string of the form moduleName.commandProcName into its components. Can be used to check whether a
  127. command string is syntactically correct, i.e. is of the form 'ModuleName "." [ProcedureName]' *)
  128. PROCEDURE Split*(CONST cmdstr : ARRAY OF CHAR; VAR moduleName, procedureName : Modules.Name; VAR res : WORD; VAR msg : ARRAY OF CHAR);
  129. VAR i, j, maxlen, cmdlen : SIZE;
  130. BEGIN
  131. res := CommandParseError;
  132. moduleName := ""; procedureName := ""; msg := "";
  133. maxlen := LEN(moduleName); cmdlen := LEN(cmdstr);
  134. i := 0; WHILE (i < cmdlen) & (i < maxlen-1) & (cmdstr[i] # Delimiter) & (cmdstr[i] # 0X) DO moduleName[i] := cmdstr[i]; INC(i); END;
  135. IF (i >= maxlen) THEN
  136. COPY("Module name too long", msg);
  137. ELSIF (i >= cmdlen) THEN
  138. COPY("Command string not 0X terminated", msg);
  139. ELSIF (cmdstr[i] # Delimiter) THEN
  140. COPY('Expected ModuleName "." [ProcedureName]', msg);
  141. ELSE
  142. (* We allow cmdstr[i] = 0X. That means the module will be loaded but not command procedure will be started *)
  143. moduleName[i] := 0X;
  144. INC(i); (* Skip Delimiter *)
  145. j := 0;
  146. WHILE (i < cmdlen) & (j < maxlen-1) & (cmdstr[i] # 0X) DO procedureName[j] := cmdstr[i]; INC(j); INC(i); END;
  147. IF (i >= cmdlen) THEN
  148. COPY("Command string not 0X terminated", msg);
  149. ELSIF (j >= maxlen-1) THEN
  150. COPY("Command name too long", msg);
  151. ELSE
  152. procedureName[j] := 0X;
  153. res := Ok; COPY("", msg);
  154. END;
  155. END;
  156. END Split;
  157. (** Can be called by a command to retrieve the context associated with its active object. *)
  158. PROCEDURE GetContext*() : Context;
  159. VAR context: ANY;
  160. BEGIN
  161. context := Objects.CurrentContext();
  162. IF (context # NIL) & (context IS Context) THEN RETURN context(Context)
  163. ELSE RETURN defaultContext
  164. END;
  165. END GetContext;
  166. (** Activate a command in its own active object.
  167. Returns res = Ok if successful, otherwise msg contains error message.
  168. The command can call GetConext() to get its context, which is also passed directly. *)
  169. PROCEDURE Activate*(CONST cmd : ARRAY OF CHAR; context : Context; flags : SET; VAR res : WORD; VAR msg : ARRAY OF CHAR);
  170. VAR moduleName, commandName : Modules.Name; run : Runner;
  171. BEGIN
  172. Split(cmd, moduleName, commandName, res, msg);
  173. IF (res = Ok) THEN
  174. NEW(run, moduleName, commandName, context);
  175. run.Join(Loaded, res, msg); (* Avoid race condition described in Modules.Mod *)
  176. IF (res = Ok) & (Wait IN flags) THEN run.Join(Finished, res, msg); END
  177. END;
  178. END Activate;
  179. (** Activate a string of commands, including their parameters.
  180. The string is parsed from left to right and Activate is called for every command.
  181. Parsing stops at the end of the string, or when Activate returns an error.
  182. The flags are applied to every command, i.e., for sequential execution,
  183. use the Wait flag (the caller waits until all commands return).
  184. Syntax:
  185. cmds = [mode " " ] cmd {";" cmd} .
  186. mode = "PAR" | "SEQ" .
  187. cmd = mod ["." proc] [" " params] .
  188. params = {<any character except ";">} .
  189. *)
  190. PROCEDURE Call*(cmds : ARRAY OF CHAR; flags : SET; VAR res : WORD; VAR msg : ARRAY OF CHAR);
  191. VAR outer, context : Context; arg : Streams.StringReader; i, j, k : LONGINT; mode : ARRAY 5 OF CHAR;
  192. par : POINTER TO ARRAY OF CHAR;
  193. BEGIN
  194. IF trace THEN Trace.String("Commands.Call "); Trace.String(cmds); Trace.String("~"); Trace.Ln END;
  195. NEW(par,LEN(cmds));
  196. i := 0; WHILE (i # 4) & (i # LEN(cmds)) DO mode[i] := cmds[i]; INC(i); END;
  197. mode[i] := 0X; (* copy at most first 4 characters *)
  198. IF mode = "PAR " THEN EXCL(flags, Wait);
  199. ELSIF mode = "SEQ " THEN INCL(flags, Wait);
  200. ELSE i := 0; (* reset to start *)
  201. END;
  202. LOOP
  203. k := 0;
  204. WHILE (cmds[i] # " ") & (cmds[i] # 09X) & (cmds[i] # 0DX) & (cmds[i] # 0AX) & (cmds[i] # 0X) & (cmds[i] # ";") DO cmds[k] := cmds[i]; INC(k); INC(i); END;
  205. IF k = 0 THEN EXIT; END; (* end of string *)
  206. j := 0;
  207. IF (cmds[i] # ";") & (cmds[i] # 0X) THEN (* parameters *)
  208. INC(i); WHILE (cmds[i] # 0X) & (cmds[i] # ";") DO par[j] := cmds[i]; INC(i); INC(j); END;
  209. END;
  210. IF cmds[i] = ";" THEN INC(i); END;
  211. par[j] := 0X; cmds[k] := 0X;
  212. NEW(arg, j+1); arg.SetRaw(par^, 0, j+1);
  213. IF InheritContext IN flags THEN
  214. outer := GetContext();
  215. NEW(context, outer.in, arg, outer.out, outer.error, outer.caller);
  216. ELSIF Silent IN flags THEN
  217. NEW(context,NIL, arg, silentWriter, silentWriter, NIL);
  218. ELSE
  219. NEW(context, NIL, arg, NIL, NIL, NIL)
  220. END;
  221. Activate(cmds, context, flags, res, msg);
  222. IF (res # Ok) THEN EXIT; END;
  223. END;
  224. END Call;
  225. PROCEDURE Init;
  226. VAR s: ARRAY 4 OF CHAR;
  227. BEGIN
  228. emptyString[0] := 0X;
  229. Machine.GetConfig("TraceCommands", s);
  230. trace := (s[0] = "1");
  231. NEW(silentWriter, SendNothing, 128);
  232. NEW(defaultContext,NIL,NIL,NIL,NIL,NIL);
  233. END Init;
  234. BEGIN
  235. Init;
  236. END Commands.