Commands.Mod 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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*: LONGINT;
  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. (*see StreamUtilities.Mod: reader that can daisychained with another reader that extracts a copy of the data flow to a monitor stream*)
  36. ReaderMonitor* = OBJECT(Streams.Reader)
  37. VAR in: Streams.Reader; tracer: Streams.Writer; pos0: LONGINT; tracedPos: LONGINT;
  38. PROCEDURE &Init(in: Streams.Reader; tracer: Streams.Writer);
  39. BEGIN
  40. SELF.tracer := tracer;
  41. InitReader(Receiver, 1024);
  42. SELF.in := in;
  43. pos0 := in.Pos();
  44. tracedPos := pos0;
  45. END Init;
  46. PROCEDURE Receiver(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
  47. VAR inReader: Streams.Reader;nextPos: LONGINT;
  48. BEGIN
  49. inReader := in;
  50. ASSERT((size > 0) & (min <= size) & (min >= 0));
  51. in.Bytes(buf, ofs, size, len);
  52. (* filter out 0X characters *)
  53. nextPos := tracedPos;
  54. WHILE (nextPos < ofs + len) & (buf[nextPos] # 0X) DO
  55. INC(nextPos);
  56. END;
  57. IF nextPos > tracedPos THEN
  58. tracer.Bytes(buf,tracedPos,nextPos-tracedPos);
  59. END;
  60. tracedPos := nextPos;
  61. res:=in.res
  62. END Receiver;
  63. PROCEDURE CanSetPos(): BOOLEAN;
  64. BEGIN RETURN in.CanSetPos()
  65. END CanSetPos;
  66. PROCEDURE SetPos(pos: LONGINT);
  67. BEGIN Reset; pos0 := pos; in.SetPos(pos)
  68. END SetPos;
  69. PROCEDURE Pos(): LONGINT;
  70. BEGIN RETURN Pos^()+pos0;
  71. END Pos;
  72. END ReaderMonitor;
  73. (* Procedure types that can be called be runner thread *)
  74. CommandProc = PROCEDURE;
  75. CommandContextProc = PROCEDURE(context : Context);
  76. TYPE
  77. Runner = OBJECT
  78. VAR
  79. moduleName, commandName : Modules.Name;
  80. context : Context;
  81. tracer: Streams.Writer; r: ReaderMonitor;
  82. proc : CommandProc;
  83. commandProc : CommandContextProc;
  84. msg : ARRAY 128 OF CHAR; res : LONGINT;
  85. module : Modules.Module;
  86. state : LONGINT;
  87. exception : BOOLEAN;
  88. PROCEDURE &Init*(CONST moduleName, commandName : Modules.Name; context : Context);
  89. BEGIN
  90. SELF.moduleName := moduleName; SELF.commandName := commandName;
  91. IF (context = NIL) THEN NEW(context, NIL, NIL, NIL, NIL, NIL); END;
  92. IF trace THEN
  93. Streams.OpenWriter(tracer, Trace.Send);
  94. NEW(r , context.arg, tracer); context.arg:=r;
  95. tracer.String("Commands.Activate ");
  96. tracer.String(moduleName); tracer.String(Delimiter); tracer.String(commandName); tracer.Char(" ");
  97. END;
  98. SELF.context := context;
  99. res := CommandError; COPY("Error starting command", msg);
  100. exception := FALSE;
  101. state := Started;
  102. END Init;
  103. PROCEDURE Join(this : LONGINT; VAR res : LONGINT; VAR msg : ARRAY OF CHAR);
  104. BEGIN {EXCLUSIVE}
  105. AWAIT(state >= this);
  106. res := SELF.res; COPY(SELF.msg, msg);
  107. END Join;
  108. BEGIN {ACTIVE, SAFE}
  109. IF ~exception THEN
  110. exception := TRUE; (* catch exceptions from now on *)
  111. module := Modules.ThisModule(moduleName, res, msg);
  112. IF (res = Ok) THEN
  113. IF commandName # "" THEN
  114. GETPROCEDURE(moduleName, commandName, proc);
  115. IF (proc = NIL) THEN
  116. GETPROCEDURE(moduleName, commandName, commandProc);
  117. END;
  118. IF (proc = NIL) & (commandProc = NIL) THEN
  119. res := CommandNotFound;
  120. msg := "Command ";
  121. Modules.Append(moduleName, msg); Modules.Append(Delimiter, msg); Modules.Append(commandName, msg);
  122. Modules.Append(" not found", msg);
  123. END;
  124. END;
  125. END;
  126. BEGIN {EXCLUSIVE} state := Loaded; END;
  127. IF (res = Ok) THEN
  128. ASSERT((proc # NIL) OR (commandProc # NIL) OR (commandName = ""));
  129. IF (proc # NIL) THEN
  130. proc();
  131. context.out.Update; context.error.Update;
  132. ELSIF (commandProc # NIL) THEN
  133. ASSERT(context # NIL);
  134. commandProc(context);
  135. context.out.Update; context.error.Update;
  136. res := context.result;
  137. IF res # Ok THEN msg := "Command not successful"; END;
  138. END;
  139. END;
  140. ELSE
  141. res := CommandTrapped; COPY("Exception during command execution", msg);
  142. END;
  143. IF trace THEN
  144. tracer.String(" ~");
  145. tracer.Ln;
  146. tracer.Update
  147. END;
  148. BEGIN {EXCLUSIVE} state := Finished; END;
  149. END Runner;
  150. VAR
  151. emptyString : ARRAY 1 OF CHAR;
  152. silentWriter: Streams.Writer;
  153. trace: BOOLEAN;
  154. defaultContext: Context; (* Fallback. Note that this context would be shared by different users -- may be used for tracing though *)
  155. (* Create a ready on a empty string *)
  156. PROCEDURE GetEmptyReader() : Streams.Reader;
  157. VAR reader : Streams.StringReader;
  158. BEGIN
  159. NEW(reader, 1); reader.SetRaw(emptyString, 0, 1);
  160. RETURN reader;
  161. END GetEmptyReader;
  162. PROCEDURE SendNothing(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
  163. END SendNothing;
  164. (** Splits a command string of the form moduleName.commandProcName into its components. Can be used to check whether a
  165. command string is syntactically correct, i.e. is of the form 'ModuleName "." [ProcedureName]' *)
  166. PROCEDURE Split*(CONST cmdstr : ARRAY OF CHAR; VAR moduleName, procedureName : Modules.Name; VAR res : LONGINT; VAR msg : ARRAY OF CHAR);
  167. VAR i, j : LONGINT; maxlen, cmdlen : LONGINT;
  168. BEGIN
  169. res := CommandParseError;
  170. moduleName := ""; procedureName := ""; msg := "";
  171. maxlen := LEN(moduleName); cmdlen := LEN(cmdstr);
  172. i := 0; WHILE (i < cmdlen) & (i < maxlen-1) & (cmdstr[i] # Delimiter) & (cmdstr[i] # 0X) DO moduleName[i] := cmdstr[i]; INC(i); END;
  173. IF (i >= maxlen) THEN
  174. COPY("Module name too long", msg);
  175. ELSIF (i >= cmdlen) THEN
  176. COPY("Command string not 0X terminated", msg);
  177. ELSIF (cmdstr[i] # Delimiter) THEN
  178. COPY('Expected ModuleName "." [ProcedureName]', msg);
  179. ELSE
  180. (* We allow cmdstr[i] = 0X. That means the module will be loaded but not command procedure will be started *)
  181. moduleName[i] := 0X;
  182. INC(i); (* Skip Delimiter *)
  183. j := 0;
  184. WHILE (i < cmdlen) & (j < maxlen-1) & (cmdstr[i] # 0X) DO procedureName[j] := cmdstr[i]; INC(j); INC(i); END;
  185. IF (i >= cmdlen) THEN
  186. COPY("Command string not 0X terminated", msg);
  187. ELSIF (j >= maxlen-1) THEN
  188. COPY("Command name too long", msg);
  189. ELSE
  190. procedureName[j] := 0X;
  191. res := Ok; COPY("", msg);
  192. END;
  193. END;
  194. END Split;
  195. (** Can be called by a command to retrieve the context associated with its active object. *)
  196. PROCEDURE GetContext*() : Context;
  197. VAR object: ANY;
  198. BEGIN
  199. object := Objects.ActiveObject();
  200. IF (object # NIL) & (object IS Runner) & (object(Runner).state = Loaded) THEN RETURN object(Runner).context;
  201. ELSE RETURN defaultContext
  202. END;
  203. END GetContext;
  204. (** Activate a command in its own active object.
  205. Returns res = Ok if successful, otherwise msg contains error message.
  206. The command can call GetConext() to get its context, which is also passed directly. *)
  207. PROCEDURE Activate*(CONST cmd : ARRAY OF CHAR; context : Context; flags : SET; VAR res : LONGINT; VAR msg : ARRAY OF CHAR);
  208. VAR moduleName, commandName : Modules.Name; run : Runner;
  209. BEGIN
  210. Split(cmd, moduleName, commandName, res, msg);
  211. IF (res = Ok) THEN
  212. NEW(run, moduleName, commandName, context);
  213. run.Join(Loaded, res, msg); (* Avoid race condition described in Modules.Mod *)
  214. IF (res = Ok) & (Wait IN flags) THEN run.Join(Finished, res, msg); END
  215. END;
  216. END Activate;
  217. (** Activate a string of commands, including their parameters.
  218. The string is parsed from left to right and Activate is called for every command.
  219. Parsing stops at the end of the string, or when Activate returns an error.
  220. The flags are applied to every command, i.e., for sequential execution,
  221. use the Wait flag (the caller waits until all commands return).
  222. Syntax:
  223. cmds = [mode " " ] cmd {";" cmd} .
  224. mode = "PAR" | "SEQ" .
  225. cmd = mod ["." proc] [" " params] .
  226. params = {<any character except ";">} .
  227. *)
  228. PROCEDURE Call*(cmds : ARRAY OF CHAR; flags : SET; VAR res : LONGINT; VAR msg : ARRAY OF CHAR);
  229. VAR outer, context : Context; arg : Streams.StringReader; i, j, k : LONGINT; mode : ARRAY 5 OF CHAR;
  230. par : POINTER TO ARRAY OF CHAR;
  231. BEGIN
  232. IF trace THEN Trace.String("Commands.Call "); Trace.String(cmds); Trace.String("~ "); Trace.Ln END;
  233. NEW(par,LEN(cmds));
  234. i := 0; WHILE (i # 4) & (i # LEN(cmds)) DO mode[i] := cmds[i]; INC(i); END;
  235. mode[i] := 0X; (* copy at most first 4 characters *)
  236. IF mode = "PAR " THEN EXCL(flags, Wait);
  237. ELSIF mode = "SEQ " THEN INCL(flags, Wait);
  238. ELSE i := 0; (* reset to start *)
  239. END;
  240. LOOP
  241. k := 0;
  242. 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;
  243. IF k = 0 THEN EXIT; END; (* end of string *)
  244. j := 0;
  245. IF (cmds[i] # ";") & (cmds[i] # 0X) THEN (* parameters *)
  246. INC(i); WHILE (cmds[i] # 0X) & (cmds[i] # ";") DO par[j] := cmds[i]; INC(i); INC(j); END;
  247. END;
  248. IF cmds[i] = ";" THEN INC(i); END;
  249. par[j] := 0X; cmds[k] := 0X;
  250. NEW(arg, j+1); arg.SetRaw(par^, 0, j+1);
  251. IF InheritContext IN flags THEN
  252. outer := GetContext();
  253. NEW(context, outer.in, arg, outer.out, outer.error, outer.caller);
  254. ELSIF Silent IN flags THEN
  255. NEW(context,NIL, arg, silentWriter, silentWriter, NIL);
  256. ELSE
  257. NEW(context, NIL, arg, NIL, NIL, NIL)
  258. END;
  259. Activate(cmds, context, flags, res, msg);
  260. IF (res # Ok) THEN EXIT; END;
  261. END;
  262. END Call;
  263. PROCEDURE Init;
  264. VAR s: ARRAY 4 OF CHAR;
  265. BEGIN
  266. emptyString[0] := 0X;
  267. Machine.GetConfig("TraceCommands", s);
  268. trace := (s[0] = "1");
  269. NEW(silentWriter, SendNothing, 128);
  270. NEW(defaultContext,NIL,NIL,NIL,NIL,NIL);
  271. END Init;
  272. BEGIN
  273. Init;
  274. END Commands.