├── LICENSE ├── README.md └── ed.q /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ed 2 | 3 | Q line editor and full-screen interface editor 4 | 5 | Contains two different workspace-centric function editors, 6 | `ed` and `qed`. `ed` shells out to the OS to invoke an 7 | external editor of choice, and `qed` edits entirely within Q 8 | using a line-mode editor. 9 | 10 | # Usage 11 | 12 | | Name and Syntax | Description | 13 | | --------------- | ----------- | 14 | | `ed name` | Edits the named function using the OS editor specified by the variable `.ed.ED` | 15 | | `qed name` | Edits the named function using a line-mode editor running within the workspace | 16 | 17 | Loading the editor file `ed.q` aliases the global names `ed` and `qed` to `.ws.ed` and `.ws.qed`. 18 | 19 | ## The `ed` Editor 20 | 21 | Define the variable `.ed.ED` to refer to the external editor of choice. Editing occurs entirely 22 | within the context of the chosen editor. By default, under Windows `ed` uses Notepad++; there is 23 | no default editor for Linux. 24 | 25 | To keep changes made in the external editor, save the function in the editor and 26 | exit back to Q. If `ed` cannot define the function in Q (for example, if it has an 27 | expression that can't be parsed), enter any key when 28 | prompted to return to the editor with the changes intact, or `\q` to discard 29 | the changes. 30 | 31 | ## The `qed` Editor 32 | 33 | `qed` is a line-mode editor written entirely in Q. `qed` starts by displaying all lines of the specified function, 34 | and prompting in insert mode at a line before the closing `}`. If the function is defined on a single 35 | line, `qed` prompts after that line. 36 | 37 | Valid `qed` commands are as follows: 38 | 39 | | Command | Description | 40 | | --------| ----------- | 41 | | `[n]` | Moves to line `n`; prepares to insert a new line if line `n` does not exist | 42 | | `[n] text` | Defines (or redefines) line `n` | 43 | | | | 44 | | `[~n1 n2 n3]` | Deletes the specified line or lines | 45 | | `[n~m]` | Deletes from line `n` to `m` inclusively | 46 | | | | 47 | | `[$]` | Displays all lines | 48 | | `[$n]` |Displays from line `n` to the end | 49 | | `[n$]` |Displays line `n` | 50 | | | | 51 | | `[n$m]` | Edits line `n`, setting the cursor to position `m` (see below) | 52 | | | | 53 | | `\w` | Saves the function and exits; stays in the editor on failure | 54 | | `\q` | Quits (discarding changes) | 55 | 56 | Lines in a `qed` session are initially numbered consecutively starting at `[0]`. 57 | Line numbers can be fractional; for example, line 2.5 falls between lines 2 and 3, and line 2.51 falls between lines 2.5 and 2.6. 58 | `qed` attempts to make it difficult to overwrite an existing line when in insert mode, by 59 | choosing line numbers that do not already exist. 60 | 61 | When editing a line, the following commands are supported under the displayed line: 62 | 63 | | Command | Description | 64 | | --------| ----------- | 65 | | `,xxx` | Inserts text (including any trailing spaces) before the character above `,`; redisplays the line and remains in edit mode | 66 | | `.xxx` | Inserts text (including any trailing spaces) before the character above `.`; moves to the next line | 67 | | `/ //` | Deletes the characters immediately above slashes; can be combined with trailing `,xxx` or `.xxx` | 68 | 69 | In edit mode, invalid characters are ignored. Because backspacing over a prompt 70 | is not permitted in Q, the cursor position `m` specified by `[n$m]` should be _at 71 | or before the position of interest_; space or tab over to get to the desired 72 | character position. As a special case, if `m` is 0 the cursor is positioned at 73 | the end of the line and the edit phase is bypassed. 74 | 75 | Any part of a line can be edited, including the line number itself provided that 76 | the cursor position is suitably to the left. If the line number is changed, the 77 | old line remains and the new line either replaces an existing line or is 78 | inserted into the appropriate position in the function. This provides a simple 79 | way to duplicate a line. 80 | 81 | The function name can also be changed. If it is, the new function replaces any 82 | existing function of the same name and the original version is left unchanged. 83 | 84 | ### Example 85 | 86 | This example illustrates the interactive development of a function that generates random trade data. We begin by defining some 87 | global variables and then editing (or in this case, defining) the function `build`. 88 | 89 | ``` 90 | q)n:5000 / Number of trades 91 | q)s:1000 / Number of symbols 92 | q)st:09:30t / Start time 93 | q)et:16t / End time 94 | q)h:14 / Historical days 95 | 96 | q)qed`build 97 | [0] build:{ 98 | [1] } 99 | [0.1] d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 100 | [0.2] usym:neg[s]?`3; / Unique symbols 101 | [0.3] ps:1+s?99f; / Starting prices 102 | [0.4] p:{[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym:n?usym; / Generate trade prices 103 | [0.5] trade::flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100); / Build trade table 104 | [0.6] \w 105 | build defined 106 | ``` 107 | 108 | Let's display what we have defined. 109 | 110 | ``` 111 | q)build 112 | { 113 | d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 114 | usym:neg[s]?`3; / Unique symbols 115 | ps:1+s?99f; / Starting prices 116 | p:{[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym:n?usym; / Generate trade prices 117 | trade::flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100); / Build trade table 118 | } 119 | ``` 120 | 121 | Next, we execute the function to ensure there are no obvious errors. The computed trade data is 122 | available as a global variable. 123 | 124 | ``` 125 | q)build[] 126 | q)count trade 127 | 5000 128 | q)2#trade 129 | date time sym price size 130 | ----------------------------------------- 131 | 2021.05.06 13:46:32.914 ebm 50.90974 9300 132 | 2021.05.04 09:49:34.094 okh 79.07636 8000 133 | ``` 134 | 135 | Let's increase the symbol length from 3 to 4, add a parameter that specifies the number of trades to generate, and return 136 | the trade data as the explicit result. 137 | 138 | ``` 139 | q)qed`build 140 | [0] build:{ 141 | [1] d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 142 | [2] usym:neg[s]?`3; / Unique symbols 143 | [3] ps:1+s?99f; / Starting prices 144 | [4] p:{[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym:n?usym; / Generate trade prices 145 | [5] trade::flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100); / Build trade table 146 | [6] } 147 | [5.1] [2$10] 148 | [2] usym:neg[s]?`3; / Unique symbols 149 | /,4 150 | [2] usym:neg[s]?`4; / Unique symbols 151 | . 152 | [3] [2$] 153 | [2] usym:neg[s]?`4; / Unique symbols 154 | [2] [0$0] 155 | [0] build:{[n] 156 | [1] [5$4] 157 | [5] trade::flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100); / Build trade table 158 | /////// /. 159 | [6] [$] 160 | [0] build:{[n] 161 | [1] d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 162 | [2] usym:neg[s]?`4; / Unique symbols 163 | [3] ps:1+s?99f; / Starting prices 164 | [4] p:{[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym:n?usym; / Generate trade prices 165 | [5] flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100) / Build trade table 166 | [6] } 167 | [7] \w 168 | build defined 169 | q)count trade:build 5000000 170 | 5000000 171 | q)2#trade 172 | date time sym price size 173 | ------------------------------------------ 174 | 2021.05.04 14:10:15.762 mgec 98.52243 6000 175 | 2021.05.10 12:53:31.019 amij 78.0223 9200 176 | q)\ts build 5000000 177 | 3664 302002416 178 | ``` 179 | 180 | Performance seems to be quite poor when a large number of trades is involved. Let's profile the code to see where the problem lies. 181 | 182 | ``` 183 | q)\l prof.q 184 | q).prof.prof`build 185 | q)build 5000000; 186 | q).prof.report[] 187 | Name Line Stmt Count Total Own Pct 188 | -------------------------------------------------------------------------- 189 | build 4 p:{[usym;ps;s]ps[usym?s]+rand 1 00:03.473 00:03.473 95.49% 190 | build 5 flip`date`time`sym`price`size! 1 00:00.163 00:00.163 4.49% 191 | build 0 d@:where 1<(d:.z.D-til h)mod 7 1 00:00.000 00:00.000 0.00% 192 | build 2 usym:neg[s]?`4; 1 00:00.000 00:00.000 0.11% 193 | build 3 ps:1+s?99f; 1 00:00.000 00:00.000 0.00% 194 | ``` 195 | 196 | The problem is line 4, which is consuming over 95% of the time. Let's look a little closer by introducing a breakpoint 197 | and experimenting in the context of the suspended function. 198 | 199 | ``` 200 | q).prof.unprof` 201 | 202 | q)qed`build 203 | [0] build:{[n] 204 | [1] d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 205 | [2] usym:neg[s]?`4; / Unique symbols 206 | [3] ps:1+s?99f; / Starting prices 207 | [4] p:{[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym:n?usym; / Generate trade prices 208 | [5] flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100) / Build trade table 209 | [6] } 210 | [5.1] [3.1]1+`a; / Intentional error 211 | [3.2] \w 212 | build defined 213 | q)build 5000000 214 | type error 215 | {}[4] 1+`a; / Intentional error 216 | ^ 217 | q))count usym 218 | 1000 219 | q))\ts sym:n?usym 220 | 56 100663504 221 | q))\ts {[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym 222 | 3451 180663968 223 | ``` 224 | 225 | We can see that the problem is the repeated look-ups of each nonunique symbol in the unique symbol list. This is simple to improve, by 226 | doing the look-up once. 227 | 228 | ``` 229 | q))\ts ps[usym?sym]+rand 1f 230 | 25 134218144 231 | q))\ 232 | ``` 233 | 234 | Let's edit the function, removing our temporary breakpoint and switching to the vectorized symbol look-up approach. 235 | 236 | ``` 237 | q)qed`build 238 | [0] build:{[n] 239 | [1] d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 240 | [2] usym:neg[s]?`4; / Unique symbols 241 | [3] ps:1+s?99f; / Starting prices 242 | [4] 1+`a; / Intentional error 243 | [5] p:{[usym;ps;s] ps[usym?s]+rand 1f}[usym;ps] each sym:n?usym; / Generate trade prices 244 | [6] flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100) / Build trade table 245 | [7] } 246 | [6.1] [~4] 247 | [5] p:ps[usym?sym:n?usym]+rand 1f; / Generate trade prices 248 | [6] [$] 249 | [0] build:{[n] 250 | [1] d@:where 1<(d:.z.D-til h)mod 7; / Ignore weekends 251 | [2] usym:neg[s]?`4; / Unique symbols 252 | [3] ps:1+s?99f; / Starting prices 253 | [5] p:ps[usym?sym:n?usym]+rand 1f; / Generate trade prices 254 | [6] flip`date`time`sym`price`size!(n?d;st+n?et-st;sym;p;100*1+n?100) / Build trade table 255 | [7] } 256 | [8] \w 257 | build defined 258 | 259 | q)\ts build 5000000 260 | 254 302002416 261 | ``` 262 | 263 | The modified version exhibits much better performance characteristics, with this simple change giving an 264 | improvement of nearly 15 times. 265 | 266 | # Configuration 267 | 268 | The variable `.ed.ED` controls the OS editor invoked by `ed`. By default, under Windows `ed` uses Notepad++; there is 269 | no default editor for Linux. 270 | 271 | The variable `.ed.TABS` controls the width of a tab stop in `qed`. The value of this setting must agree with the OS UI 272 | window interface that you are using. By default, tabs are set to 8. 273 | 274 | # Acknowledgement 275 | 276 | The `qed` line editor is based on the design of the original APL\360 line editor that was implemented as part of 277 | IBM's XM6 Program product, and later enhanced by I.P. Sharp Associates in its Sharp APL product. 278 | 279 | # Author 280 | 281 | Leslie Goldsmith 282 | -------------------------------------------------------------------------------- /ed.q: -------------------------------------------------------------------------------- 1 | / 2 | Q function editor 3 | Copyright (c) 2018 Leslie Goldsmith 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at: 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, 12 | software distributed under the License is distributed on an 13 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 14 | either express or implied. See the License for the specific 15 | language governing permissions and limitations under the 16 | License. 17 | 18 | ---------------- 19 | 20 | Contains two different workspace-centric function editors, 21 | and . shells out to the OS to invoke an 22 | external editor of choice, and edits entirely within Q 23 | using a line-mode editor. 24 | 25 | Usage information appears at the bottom of this file. 26 | 27 | Author: Leslie Goldsmith 28 | \ 29 | 30 | 31 | \d .ed 32 | 33 | ED:$["w"=first string .z.o;"\"C:\\Program Files\\Notepad++\\notepad++.exe\" -multiInst -nosession -notabbar";"Editor_not_specified"] / External editor invocation 34 | TABS:8 / Tab width for ; must agree with OS UI window interface 35 | POW:1 10 100 1000 10000 36 | 37 | enl:enlist 38 | 39 | 40 | // 41 | // @desc Edits a new or existing function using an external editor. 42 | // 43 | // @param x {symbol|string} Specifies the name of the function to edit. If the 44 | // function does not exist, a new one is created. The 45 | // name may be preceded by `:` to force definition (or 46 | // redefinition) in the specified namespace. 47 | // 48 | ed:{ 49 | if[0~v:ncsv x;:()];nm:first v;c:v 1; / Extract name (with possible namespace) and context 50 | `:_ed.tmp 0:enl fn0:ssr[;"\n ";"\n\t"]fn:nm,":",last v; / Write temporary file in canonical format 51 | if[v 2;fn0:0];b:1b; / Kill inceptive defn if new, and set loop terminator 52 | 53 | while[b; 54 | system ED," _ed.tmp"; / Invoke editor 55 | n:name fn:read0`:_ed.tmp; / Grab new defn and name 56 | nm:def[n;ctx[c;nm;n];(1+fn?":")_fn:"\n"sv fn]; / Attempt to define function 57 | if[b:nm~"";-1 "Press any key to re-edit, or \"\\q\" to quit and discard changes";b:not"\\q"~read0 0]]; 58 | 59 | if[count nm;-1 nm,(" defined";" unchanged")fn0~fn]; 60 | } 61 | 62 | 63 | // 64 | // @desc Edits a new or existing function using an internal line-mode editor. 65 | // Editor commands are described at the end of this file. 66 | // 67 | // @param x {symbol|string} Specifies the name of the function to edit. If the 68 | // function does not exist, a new one is created. The 69 | // name may be preceded by `:` to force definition (or 70 | // redefinition) in the specified namespace. 71 | // 72 | qed:{ 73 | if[0~v:ncsv x;:()];nm:first v;c:v 1; / Extract name (with possible namespace) and context 74 | i:0,1+where"\n"=fn0:nm,":",last v; / Find line breaks 75 | Lns::10000*til count Fn::i _fn0,"\n"; / Scaled line numbers and corresponding lines 76 | Cur::0|-2+count Lns; / Current line (= insert point), before closing } 77 | Mode::0b; / Set insert (vs. edit) mode 78 | Ln::-1; / User-specified line number, if any 79 | if[v 2;fn0:0]; / Kill inceptive defn if new 80 | d:system"c";system"c 1000 2000"; / Set display size 81 | p:system"P";system"P 10"; / Set formatting precision 82 | 83 | dl(); / Display all lines 84 | 85 | while[not$[[2 pr:fmtn seln[];"\\w"~s:read0 0]; 86 | [s:"";count nm:def[n;ctx[c;nm;n:name Fn];(1+fn?":")_fn:-1_(,/)Fn]];[if[i:"\\q"~s;nm:""];i]]; / Attempt to define function 87 | r:$[0=count s:ltrim s;Cur; / No change if input empty 88 | [Ln::-1;"["=first s];lcmd s; / Look for edit command 89 | upd pr,s]; / Otherwise, update current line 90 | $[r=-1;-2 "Command error";Mode&::r=Cur::r&-1+count Lns]]; 91 | 92 | if[count nm;-1 nm,(" defined";" unchanged")fn0~fn]; 93 | 94 | system"c ",.Q.s1 d;system"P ",string p; / Restore settings 95 | } 96 | 97 | 98 | // 99 | // @desc Returns the name, context namespace, status, and value of an object. 100 | // 101 | // @param x {symbol|string} Specifies the name of the function to edit. The 102 | // name may be preceded by `:` to force definition (or 103 | // redefinition) in the specified namespace. 104 | // 105 | // @return {list[4]|0} A 4-element array containing the name, context, 106 | // status, and value of the function, or `0` if the 107 | // name is illegal. Status is `1b` if the object 108 | // is new or if an explicit namespace override is 109 | // specified, or `0b` otherwise. 110 | // 111 | ncsv:{ 112 | nm:$[10h=type x;;-11h=type x;string;0#]x; / Convert name to string (empty if illegal) 113 | c:`$$[":."~2#nm;$["."in i:2_nm;(i?".")#i;""];1_string system"d"]; / Get context namespace 114 | v:$[(0=count nm)|" "in nm:sqz nm;0;b:0h=type key x:`$nm:(i:":"=first nm)_nm;(0;0;0;c;"{\n }");100h=type v:value x;value v;0]; / Validate name; hallucinate value if new function 115 | $[v~0;0*-2 "Unable to edit";(nm;$[i;c;first v 3];b|i;last v)] / Name, context, status, value 116 | } 117 | 118 | 119 | // 120 | // Extracts the function name (with possible namespace) from its definition. 121 | // 122 | // @param x {string[]} The function definition, split by lines. The first 123 | // line has the form: `name:{...` . 124 | // 125 | // @return {string} The function name if plausible, or an empty string 126 | // otherwise. Note that the returned name may be still 127 | // be illegal; the caller is expected to accommodate 128 | // this. 129 | // 130 | name:{{$[(first[x]in .Q.n)<":"in x;(x?":")#x;""]}ltrim first x} 131 | 132 | 133 | // 134 | // @desc Computes the context for a function based on its initial and final 135 | // properties. 136 | // 137 | // @param c {symbol} Specifies the context of the original definition. 138 | // @param nm0 {string} Specifies the initial function name (with possible 139 | // namespace). 140 | // @param nm {string} Specifies the final function name (with possible 141 | // namespace). 142 | // 143 | // @return {symbol} The context in which the function should be defined. 144 | // 145 | ctx:{[c;nm0;nm] 146 | i:`${$["."=first y;(y?".")#y:1_y;x]}[1_string system"d"]@/:(nm0;nm); / Extract namespace from names, defaulting to active namespace 147 | $[(=/)i;c;last i] / Use context if no change; else use namespace of final name 148 | } 149 | 150 | 151 | // 152 | // @desc Displays lines to the console. 153 | // 154 | // @param x {long[2|0]} The starting line number and the number of lines to 155 | // display, or an empty vector to display all lines. 156 | // 157 | // @return {long} The line index of the last line displayed. 158 | // 159 | dl:{ 160 | i:first[x]+til last x:2#x,0,count Lns; / Indices of selected lines (all if none specified) 161 | 1"",/((1+5|count each j)$j:"[",/:string[0.0001*Lns i],\:"]"),'Fn i; / Prepend line numbers and display 162 | last count[Lns],i 163 | } 164 | 165 | 166 | // 167 | // @desc Formats a line number for output. 168 | // 169 | // @param x {long} The scaled line number. 170 | // 171 | // @return {string} The decorated string representation of the line number. 172 | // 173 | fmtn:{(1+5|count s)$s:"[",string[0.0001*x],"]"} 174 | 175 | 176 | // 177 | // @desc Selects the line number to display for the current line. 178 | // 179 | // @return {long} The line number to display. 180 | // 181 | seln:{[] $[Mode;Lns Cur;Ln>=0;Ln;$[null i:nextn[];[-2 "No room for insertion";Lns Cur];i]]} 182 | 183 | 184 | // 185 | // @desc Computes the line number for insertion mode. 186 | // 187 | // @return {long} The next line number, or `0N` if there is no room for 188 | // insertion at the current line index. 189 | // 190 | nextn:{[] {x+POW(last where i=(_)i:x%POW)&POW bin -1+y-x}. 2#Cur _Lns,0W} 191 | 192 | 193 | // 194 | // @desc Processes a line command (beginning with `[`). Valid command formats are 195 | // described at the end of this file. 196 | // 197 | // @param s {string} The input line command, starting with the leading bracket. 198 | // 199 | // @return {long} The new line index if the operation is successful, or `-1` 200 | // if an error occurred. 201 | // 202 | lcmd:{[s] 203 | if[not"]"in s;:-1]; / Must be matched 204 | (del;edt;upd)[("~$"in(s?"]")#s)?1b]s / Invoke appropriate routine 205 | } 206 | 207 | 208 | // 209 | // @desc Deletes one or more lines. 210 | // 211 | // @param s {string} The input line command, starting with the leading bracket. 212 | // Valid formats are `[~n1 n2 n3]` and `[n~m]`. 213 | // 214 | // @return {long} The new line index if the operation is successful, or `-1` 215 | // if an error occurred. 216 | // 217 | del:{[s] 218 | if[1b in" "<>(1+count i:(s?"]")#s)_s:ltrim 1_s;:-1]; / Must be no line residual 219 | 220 | i:$["~"=first i; / Distinguish case 221 | [if[-1~n:getnv 1_i;:-1];Lns in n]; / Monadic (vector) form 222 | [if[-1=n:getn(j:i?"~")#i;:-1];if[n>m:getn(j+1)_i;:-1];Lns within n:n,m]]; / Dyadic form 223 | 224 | if[1b in i;Lns@:i:where not i;Fn@:i]; / Remove affected lines 225 | Mode::count[Lns]>p:Lns binr last n; / Compute new line index and adjust line mode 226 | p 227 | } 228 | 229 | 230 | // 231 | // @desc Displays one or more lines, or edits a line. 232 | // 233 | // @param s {string} The input line command, starting with the leading bracket. 234 | // Valid formats are `[$]`, `[$n]`, `[n$]`, and `[n$m]`. 235 | // 236 | // @return {long} The new line index if the operation is successful, or `-1` 237 | // if an error occurred. 238 | // 239 | edt:{[s] 240 | if[1b in" "<>(1+count i:(s?"]")#s)_s:ltrim 1_s;:-1]; / Must be no line residual 241 | 242 | if["$"=first i;$[0=count ltrim 1_i;[Mode::0b;:dl()]; / Display entire function 243 | [if[-1=n:getn 1_i;:-1];Mode::0b;:dl p,count[Lns]-p:Lns binr n]]]; / Display from specified line to end 244 | 245 | if[-1=n:getn(j:i?"$")#i;:-1]; / Extract line number 246 | p:Lns binr n; / Where this line is (or goes) 247 | if[0=count ltrim(j+1)_i;:$[Mode::n=Lns p;dl p,1;[Ln::n;p]]]; / Display specified line 248 | 249 | if[0>m:"J"$(j+1)_i;:-1]; / Reject illegal or negative position values 250 | $[Mode::n=Lns p;upd edln[fmtn[n],-1_Fn p;m];[Ln::n;p]] / Edit specified line (note line number may be modified as well) 251 | } 252 | 253 | 254 | // 255 | // @desc Sets or updates a line. 256 | // 257 | // @param s {string} The input line command, starting with the leading bracket. 258 | // Format is `[n]` optionally followed by line text. 259 | // 260 | // @return {long} The new line index if the operation is successful, or `-1` 261 | // if an error occurred. 262 | // 263 | upd:{[s] 264 | if[-1=n:getn(j:s?"]")#s:ltrim 1_s;:-1]; / Extract line number 265 | s:ltrim(j+1)_s; / Text to insert 266 | Mode::n=Lns p:Lns binr n; / Where this line is (or goes) 267 | 268 | if[i:count s;$[Mode;[Fn[p]:s,"\n";p+:1]; / Update existing line 269 | [Lns::(p#Lns),n,p _Lns;Fn::(p#Fn),enl[s,"\n"],p _Fn]]]; / Insert new line 270 | 271 | if[not Mode+i;Ln::n]; / Retain user line number if insert mode with no change 272 | p 273 | } 274 | 275 | 276 | // 277 | // @desc Gets a line number from an input string. 278 | // 279 | // @param s {string} The character line number, which may be nonintegral. 280 | // 281 | // @return {long} The line number (scaled to integer) if the operation is 282 | // successful, or `-1` if an error occurred. 283 | // 284 | getn:{[s] 285 | if[0n=n:10000*"F"$s;:-1]; / Interpret and scale number 286 | if[(n<0)|0.0001a i;0;count[a]-(+/)k]]; / Position to right of insertion, or at end of line 331 | } 332 | 333 | 334 | // 335 | // @desc Expands tabs in a character string. 336 | // 337 | // @param s {string} Specifies the string to process. 338 | // @param t {int} Specifies the tab spacing. 339 | // 340 | // @return {string} The input string with tabs replaced by spaces. 341 | // 342 | exptb:{[s;t] 343 | if[not 1b in i:s="\t";:s]; / Quick exit if no tabs 344 | j:-1+-1-':k:where i; / Lengths of segments between tabs 345 | p:0|t-j mod t; / Number of blanks to replace tabs 346 | @[s;k;:;" "]where 1+@["j"$i;k;:;p-1] / Replace tabs with blanks and replicate to fill 347 | } 348 | 349 | 350 | // 351 | // @desc Defines a function within its associated namespace. 352 | // 353 | // @param nm {string} Specifies the fully-qualified name of the function to define. 354 | // @param c {symbol} Specifies the context in which to define the function. 355 | // @param f {string} Specifies the definition of the function. 356 | // 357 | // @return {string} The name of the object defined if the operation is successful, 358 | // or an empty string if an error occurred. 359 | // 360 | def:{[nm;c;f] 361 | d:system "d";system "d .",string c; / Set namespace 362 | if[b:(nm~"")|" "in nm:sqz nm;f:"name"]; / Prepare to reject illegal names 363 | r:@[$[b;{'x};string(`$nm)set value@];f;{-2 "Unable to define: ",x," error";""}]; / Define object (reification performed by must be under proper ns) 364 | system "d ",string d; / Restore previous namespace 365 | r 366 | } 367 | 368 | 369 | // 370 | // @desc Removes leading, trailing, and multiple internal blanks from a string. 371 | // 372 | // @param x {string} Specifies the string to be trimmed. 373 | // 374 | // @return {string} A character string with extraneous blanks removed. 375 | // 376 | sqz:{-1_x where i|-1_0b,i:" "<>x," "} 377 | 378 | 379 | \d . 380 | 381 | ed:.ed.ed 382 | qed:.ed.qed 383 | 384 | \ 385 | 386 | and edit new or existing functions in the Q workspace. The function 387 | name can be specified either as a symbol or a string, and may have an optional 388 | namespace prefix. 389 | 390 | By default, an existing function is saved back to its namespace with the same 391 | prevailing context in which it was previously defined. A new function is saved 392 | to the specified namespace (or the active one if the name is unqualified) using 393 | the context in effect when the editor was invoked. This behavio[u]r can be 394 | overridden by prefixing the function name with colon (:) in the editor argument. 395 | This causes the namespace specified in the argument to be used to define both 396 | the function name and the context. 397 | 398 | If the name of the function is changed during the editing session, the new 399 | function is saved (possibly replacing an earlier definition of the same name) 400 | and the original version is untouched. If the namespace is changed, the new 401 | namespace is also taken to be the new context. 402 | 403 | usage: 404 | 405 | Define the variable <.ed.ED> to refer to the external editor of choice. 406 | 407 | To keep changes made in the external editor, save the function in the editor and 408 | exit back to Q. If cannot define the function in Q, enter any key when 409 | prompted to return to the editor (with the changes intact), or "\q" to discard 410 | the changes. 411 | 412 | usage: 413 | 414 | starts by displaying all lines of the function and prompting in insert 415 | mode at a line before the closing "}". If the function is defined on a single 416 | line, prompts after that line. 417 | 418 | Valid commands are as follows: 419 | 420 | [n] Move to specified line; prepare to insert line if new 421 | [n] text Define (or redefine) specified line 422 | 423 | [~n1 n2 n3] Delete specified line or lines 424 | [n~m] Delete from line to inclusive 425 | 426 | [$] Display all lines 427 | [$n] Display from specified line to end 428 | [n$] Display specified line 429 | 430 | [n$m] Edit line , setting cursor to position (see below) 431 | 432 | \w Save function and exit; stay in editor on failure 433 | \q Quit (discard changes) 434 | 435 | Lines in a session are initially numbered consecutively starting at [0]. 436 | Line numbers can be fractional, and attempts to make it difficult to 437 | overwrite an existing line when in insert mode by choosing line numbers that 438 | do not already exist. 439 | 440 | When editing a line, the following commands are supported: 441 | 442 | ,xxx Insert text (including any trailing spaces) before character 443 | above ","; redisplay line and remain in edit mode 444 | .xxx Insert text (including any trailing spaces) before character 445 | above "."; move to next line 446 | / // Delete characters above slashes; can be combined with 447 | trailing ",xxx" or ".xxx" 448 | 449 | In edit mode, invalid characters are ignored. Because backspacing over a prompt 450 | is not permitted in Q, the cursor position specified by [n$m] should be at 451 | or before the position of interest; space or tab over to get to the desired 452 | character position. As a special case, if is 0 the cursor is positioned at 453 | the end of the line and the edit phase is bypassed. 454 | 455 | Any part of a line can be edited, including the line number itself provided that 456 | the cursor position is suitably to the left. If the line number is changed, the 457 | old line remains and the new line either replaces an existing line or is 458 | inserted into the appropriate position in the function. This provides a simple 459 | way to duplicate a line. 460 | 461 | The function name can also be changed. If it is, the new function replaces any 462 | existing function of the same name and the original version is left unchanged. 463 | 464 | 465 | Globals (ed): 466 | 467 | .ed.ED - External editor command line prefix (name to edit is appended); assign to change 468 | .ed.TABS - Tab width for ; assign to change in agreement with OS UI window interface 469 | 470 | Globals (qed): 471 | 472 | .ed.Fn - Function lines 473 | .ed.Lns - Line numbers, scaled by 10000 474 | .ed.Cur - Index in of current line 475 | .ed.Ln - User-specified explicit line number, or -1 476 | .ed.Mode - Mode flag (0b = input, 1b = edit) --------------------------------------------------------------------------------