2026-06-17 01:23:16 if you define / as /MOD DROP then doing /MOD DROP inline is faster, because you dont need to do the call 2026-06-17 01:23:45 but then again you could easily build automatic inlining into your forth 2026-06-17 02:55:12 xentrac: amby: thanks for the notes. 2026-06-17 02:56:17 Is there a loop like DO..LOOP that puts "I" onto the stack for each iteration? It would be much nicer to write, "90 65 do emit loop" than, "90 65 do i emit loop". 2026-06-17 02:58:48 have to admit I had to look up what decimal ASCII 90 and 65 were 2026-06-17 02:59:05 "'Z' 'A' do i emit loop", maybe? ^^; 2026-06-17 02:59:54 lofty: Oh, the issue is that I don't want to have to explicitly put 'i' onto the stack each iteration. 2026-06-17 03:00:50 yeah, I understood what you were asking. 2026-06-17 03:01:11 anyway, no, I don't know of any such loop construct. 2026-06-17 03:01:16 Then I'm confused about your suggestion. @_@ 2026-06-17 03:04:19 Gratefully, I can make it myself: ": iter postpone do postpone i ; immediate" >:) 2026-06-17 03:05:26 oak: excellent! 2026-06-17 03:05:46 I believe that is correct, though I'm no Forth expert 2026-06-17 03:06:06 xentrac: Thanks! It seems to work. 2026-06-17 03:06:58 xentrac: By the way, your advice about avoiding SWAP/ROT/etc. has been really impactful. It's making me think about how the data "wants to flow" -- it's been making things feel fun again. 2026-06-17 03:07:46 When I try to write Forth code the way I'd write it in "C", things start to feel unpleasant again. 2026-06-17 03:08:39 I suspect we may have taken the same advice and interpreted it differently :p 2026-06-17 03:09:11 What was your read, lofty? 2026-06-17 03:10:38 "if you need to manipulate the stack with something more complex than dup/drop, pull the expression out into a variable" 2026-06-17 03:13:46 ACTION nods 2026-06-17 03:13:51 That's fair, too. 2026-06-17 03:14:10 I think both are good. 2026-06-17 03:20:54 I have a function which is like "0 over func ! 0 swap !", which I rewrote to use a variable (call it "x" for demonstration) "0 x @ func ! 0 x @ !" 2026-06-17 03:21:17 and I think I find the second more readable 2026-06-17 03:37:41 xentrac: I do note you mentioned both "variable"s and "value"s; these seem to have a lot of overlap, so which should be preferred? 2026-06-17 04:25:44 oak: I'm delighted to hear it! 2026-06-17 04:26:37 I think avoiding anything more than dup/drop is a reasonable starting point, and reliably avoids the usual newcomer Forth pitfall of spending all your time debugging stack effects 2026-06-17 04:27:16 once you're comfortable with that, a bit of r> >r over swap can sometimes make things better instead of worse 2026-06-17 04:27:42 but if you find they're making things worse, maybe cut back on the dosage of stack manipulation 2026-06-17 04:28:37 it's nice when you can design things so that the data wants to flow nicely, so that you don't need either variables/values *or* stack manipulation 2026-06-17 04:29:13 but when you don't manage that, you can always resort to variables/values 2026-06-17 04:29:31 I think I personally prefer value over variable most of the time 2026-06-17 04:32:09 to x is not really any longer than x !, and x is certainly shorter than x @ and less error-prone. also variables are usually read in more places than they are written, so the simplification can be substantial 2026-06-17 04:33:40 but nothing is perfect; standard Forth doesn't really have an equivalent of C's unary & address-of operator, and 2value is poorly supported, unlike 2variable, perhaps due to the rather horrifying requirement that to work for both values and 2values 2026-06-17 04:34:06 so variable is more general in some ways 2026-06-17 04:35:33 there also unfortunately isn't an equivalent of +! for values (the reasonably common C += operator) 2026-06-17 04:42:40 perhaps there should be a forth word called r&r 2026-06-17 04:42:44 ... it does nothing. 2026-06-17 04:44:32 there are also local variables which only exist within the scope of a word. some people like them and some dont 2026-06-17 04:45:39 you can also mark a variable name as hidden once youre done with a word so you can reuse X in your example over and over again. it will take up space in the dictionary which is probably a deal breaker on embedded but fine if you're on a PC 2026-06-17 05:15:06 Thanks for the notes, xentrac. 2026-06-17 07:11:24 sure, I'm happy to share my perspective; just don't take it as being the perspective of an expert 2026-06-17 07:12:21 MrMobius: you can reuse X over and over again, in the sense of creating new variables called X, without having to mark anything as hidden 2026-06-17 07:18:57 did you mean something different? 2026-06-17 09:22:46 My issues with globals in most languages is they're used to create interfaces, when functions are a much better way to define and maintain interfaces. 2026-06-17 09:23:18 For example I've worked with code where a global is used to store some control flags that influences some feature run in e.g. another thread 2026-06-17 09:23:31 And invariably this behavior isn't that well documented or thought-out 2026-06-17 09:24:12 Even though a function would just be hiding the global flags, it's better to define the interface with functions so that you are explicit about what's supported etc 2026-06-17 09:26:11 In Forth an example would be BASE , what words rely on BASE? You have to check the word's def to find out or if there's a glossary or it's mentioned in the spec 2026-06-17 09:26:49 How do you set the base temporarily? You need to get/set/restore the BASE around the operation you do, which turns out to add a lot of clutter to usage 2026-06-17 09:28:09 So what's better then? Have words that allow you to choose base for one operation, e.g. BASE. for outputting in arbitrary base, and then add some specialised words for specific bases like . X. O. for decimal/hex/octal 2026-06-17 09:29:56 And decimal can be : . 10 BASE. ; 2026-06-17 09:30:27 But then e.g. hex can be optimised (as it's faster to use just shifting for hex on some computers) 2026-06-17 09:31:17 And this is a pain point in Forth because in practice, having all your options on the stack is hard to manage 2026-06-17 09:32:08 It didn't escape me when writing a Z80 Forth that I often opted to write words in assembly because it was easier to manage the multiple variables in assembly than Forth 2026-06-17 09:32:52 And so I'll never fault anyone who wants to use locals or some other method to avoid using the stack, because I think it probably produces more maintainable and easier to write code, there's just the risk you'll do it where it doesn't make sense 2026-06-17 09:35:15 Because e.g. : TWICE DUP + ; is easier to read/write/maintain and faster than : TWICE {: x :} x x + ; 2026-06-17 09:35:29 But real code is a mix of simpler and more complicated words 2026-06-17 19:36:38 veltas: That makes sense to me. I do think also that what xentrac was describing was more akin to having a "static int x" inside of a function, at least functionally. 2026-06-17 19:37:14 Where it's "global" technically, but used in a way that only that function accesses it. 2026-06-17 19:37:22 Also, good morning all! 2026-06-17 19:37:35 good evening :p 2026-06-17 19:59:14 oak: 'namespacing' the term you're looking for? 2026-06-17 20:01:03 naming is a hard problem. I feel like I have less Forth "words" and more Forth "run-on sentences" 2026-06-17 21:18:06 I have been thinking that it's better to sometimes use more variables if it avoids creating short yet arbitrary words that aren't that logical and are just there for refactoring 2026-06-17 22:33:27 veltas: I agree, and I think a set-base word that changes base would be much better 2026-06-17 22:34:06 although that makes setting the base temporarily harder rather than easier 2026-06-17 22:36:21 in PostScript there are a lot of variables that you might want to set temporarily for some operation: the current color, the current font, the current point, the current path, the current line width, the current transformation matrix, etc. 2026-06-17 22:37:25 these are all part of the graphics state, and one of PostScript's many stacks is the "graphics state stack", which you push onto with "gsave" and pop off of with "grestore" 2026-06-17 22:38:55 so you can do something like "gsave 10 setlinewidth 1 1 0 0 setcmykcolor stroke grestore" to draw a 10-point-wide blue line (IIRC) without causing later lines to be 10 points wide or later graphics to also be blue 2026-06-17 22:41:18 you could conceivably also provide an interface like this with variables instead of set* procedures, and then you could use PostScript's dynamic scoping (the dictionary stack) to save and restore them, instead of having a separate graphics state stack. this is how it's often done in Lisps 2026-06-17 22:44:31 I've got a mostly written article where I've got a good demo of how BASE could work better 2026-06-17 22:44:50 So hopefully publish tomorrow 2026-06-17 22:45:01 then instead of "10 setlinewidth" you would say something like "/linewidth 10 def" to reassign the existing variable until the end of the current dynamic scope. In Lisp you spell this (let ((line-width 10)) ...) and it only applies until the end of the let form, and this is used reasonably often in dynamically scoped Lisps like Emacs Lisp; for example there's a deactivate-mark variable I preserve in 2026-06-17 22:45:07 this way, although that's sort of backwards. I can think of some advantages to PostScript using a separate mechanism for this kind of thing but I don't know which ones were actually the motivators 2026-06-17 22:46:01 I say deactivate-mark is backwards because it provides data flow up the stack, not down the stack 2026-06-17 22:47:08 another example in Emacs is case-fold-search: if you set it to nil it makes your searches case-sensitive, but normally you only want to do that temporarily 2026-06-17 22:47:54 so it's normally just set to nil (or occasionally t) temporarily with a let so that it gets restored to whatever its previous value was when the function returns 2026-06-17 22:47:57 (is there a way to get "gforth" to shut up about redefining things? the elegance of using "variable" is somewhat diminished by seeing gforth telling me I am redefining a bunch of things) 2026-06-17 22:48:09 you could define them in different wordlists 2026-06-17 22:49:41 lofty: WARNINGS OFF 2026-06-17 22:50:26 off and on are two other things that work better with variable than with value or some kind of function-based interface 2026-06-17 22:50:31 veltas: excellent, thank you 2026-06-17 22:52:47 xentrac you already wrote something for automatic save/restore of vars I seem to remember? 2026-06-17 22:56:35 yeah, I was trying to remember where I wrote it up and can't find it, but here's the implementation: 2026-06-17 22:56:38 \ Dynamic scoping implementation 2026-06-17 22:56:40 \ F83 doesn't have a built-in 2r>. And it calls `char` `ascii` 2026-06-17 22:56:43 : 2r> r> r> r> rot >r swap ; 2026-06-17 22:56:45 : (let!) dup @ over swap 2r> rot >r rot >r >r >r ! ; 2026-06-17 22:56:48 : let! (let!) 2r> ! ; 2026-06-17 22:56:50 \ This tests the dynamic scoping implementation. It should 2026-06-17 22:56:51 I tested it in F-83 and GForth IIRC 2026-06-17 22:56:53 \ print `53 35` and an empty stack indicator. 2026-06-17 22:56:56 decimal : dec. 10 base let! . ; hex 35 dup dec. . .s 2026-06-17 22:57:29 so 10 base let! sets base to 10 until the word it's in returns, at which point it restores base 2026-06-17 23:00:06 in my example http://canonical.org/~kragen/sw/dev3/hanoi.blk I also used it to implement a Towers of Hanoi solver 2026-06-17 23:01:04 this code is very confusing because it doesn't execute in the order it is written at all 2026-06-17 23:06:02 let's see if I can reconstruct it. what happens here is that when let! calls (let!), the latter pops off the return stack both its own return address into let! and the return address of let! into its caller, such as dec.. It stores the return address in let! on the return stack with the variable's address and its saved value there, and then pushes let!'s return address into its caller on 2026-06-17 23:06:08 the return stack on *top* of that 2026-06-17 23:06:55 so that when (let!) returns, it returns not into let! as you would normally expect, but into the *caller* of let!, such as dec.. also before it does this it sets the variable to the requested value 2026-06-17 23:07:48 then when that caller (such as dec.) returns, instead of returning to its real caller (in this example the text interpreter), it returns into the tail end of let!, which proceeds to restore the old value of the variable before returning to the real caller 2026-06-17 23:10:15 And I ran this in my Forth and it crashed because my return stack wasn't deep enough 2026-06-17 23:10:27 But it worked when I added more space for it 2026-06-17 23:10:55 I don't think this is a good way to implement dynamic scoping. it's very confusing and also inefficient. but it does seem to work, and it was astounding to me that not only did it work at all, but it worked in all of PFE, current Gforth, F-83, and your Z-80 Forth 2026-06-17 23:11:13 It doesn't need a huge amount of reuturn stack space, just more than I had allocated 2026-06-17 23:11:21 I think doubling it from like 64 cells to 128 worked 2026-06-17 23:11:48 right, it's just pushing three more cells on the return stack for each saved variable 2026-06-17 23:12:00 I think maybe the dec. example there worked for you but not the Towers of Hanoi example 2026-06-17 23:12:15 It's surely an underrated feature of Forth that you can write classic Forth and run it in gforth on a modern PC and likewise on a super old machine 2026-06-17 23:12:17 which saves four variables per level of recursion 2026-06-17 23:12:34 I did have to implement 2r> for F-83 2026-06-17 23:12:44 which was a lot harder than I expected 2026-06-17 23:13:09 There is something very cool to me about being able to write the same code on the ZX Spectrum as I do on my Core 2 Duo laptop 2026-06-17 23:13:12 xentrac: implemented how, in asm or in 4th? 2026-06-17 23:14:03 bjorkintosh: can you clarify your question? 2026-06-17 23:14:15 I did have to implement 2r> for F-83 <-- 2026-06-17 23:14:32 it was in response to that. 2026-06-17 23:14:33 bjorkintosh: I implemented it as : 2r> r> r> r> rot >r swap ; 2026-06-17 23:14:43 ah. 2026-06-17 23:14:44 part of my pastebomb above 2026-06-17 23:14:54 oh I see. 2026-06-17 23:15:01 PB;DR. 2026-06-17 23:15:06 haha 2026-06-17 23:16:24 veltas: in theory I'm more interested in being able to test the same code on my quad-core Celeron laptop and on a CH32V003, STM32, or PMS150G, but I don't actually have any of those set up right now