Deutsch English Français Italiano |
<vue615$2b0tt$1@dont-email.me> View for Bookmarking (what is this?) Look up another Usenet article |
Path: ...!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!eternal-september.org!.POSTED!not-for-mail From: BGB <cr88192@gmail.com> Newsgroups: comp.arch Subject: Misc (semi OT): Well, distractions... Date: Thu, 24 Apr 2025 15:10:29 -0500 Organization: A noiseless patient Spider Lines: 474 Message-ID: <vue615$2b0tt$1@dont-email.me> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Injection-Date: Thu, 24 Apr 2025 22:13:26 +0200 (CEST) Injection-Info: dont-email.me; posting-host="b27e0e55457513abd1cd51bd79dfe873"; logging-data="2458557"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX18jBwp6m6eGYUYyW+s9uxT5/2jL0WByClc=" User-Agent: Mozilla Thunderbird Cancel-Lock: sha1:5g7JNaZz7hzsku8BnIT73ZJQwes= Content-Language: en-US Bytes: 21460 So, recently, have been distracted on other stuff. My ISA project has mostly stabilized, and the main things remaining going on here are basically going right up the side of a mountain in terms of difficulty curve. Like, as quickly as "low hanging fruit" tasks get done, whatever is left becomes progressively more difficult (and to push into "more than just a toy" areas will likely require far more work than I can reasonably pull off in a reasonable timeframe). I was also starting to miss tinkering around with 3D engine stuff some, so, yeah... But, a recent line of fiddling has gone in an odd direction. I felt a need for a basic script language for some tasks; I wanted to keep code footprint low; My past script interpreters had a lot more code footprint. I didn't want any external dependencies. Using something like Lua would mean a dependency on a library. Despite claims of being "small", Lua is still quite large... Like, it is "small" if compared with Python or V8 or something. But, still like around twice as much code as the Doom engine... So, I went for a BASIC variant: It is possible to implement a BASIC interpreter with fairly little code. Or, at least, for an 80s style dialect. There was some stuff in early BASIC's that did not make sense for use in a 3D engine, so I left it out. The core dialect was a fairly limited one: i=0 label: i=i+1 if i<10 goto label //THEN is optional with GOTO So, no real loops or other block-structured control-flow, as these would essentially require a more complex interpreter. I ended up making 'THEN' the optional keyword here, rather than 'GOTO' as "IF cond GOTO label" is unambiguous, whereas "IF cond THEN label" requires figuring out whether it is a label or a command. For now, if one wants a block structured IF/THEN/ELSE/ENDIF, they need to build it themselves with GOTO. There is GOSUB/RETURN, but this was essentially a GOTO that remembers the return path on a small internal stack. So RETURN would effectively go to the line following the location where GOSUB was called. General interpreter structure works by reading in the code, and breaking it into lines and tokens. Interpreter then walks code line-by-line, directly driving logic based on the tokens it encounters. Not really efficient, but smaller code footprint. I was torn between "tokenize then interpret", or "leave code as big blob of ASCII text and then parse each line one at a time". I ended up going with pre-tokenization as it added a few perks, is slightly less slow, and possibly even helped overall regarding code footprint (while the initial loading step is more code, the rest of the parsing logic is less code). Initially, I was using global-only scoping, but it doesn't take long to realize that only having global variables raises problems: Every piece of code needs to use its own variable names to not stomp code elsewhere; Recursive code is essentially impossible. Most lazy solution to this: Add dynamic scoping. TEMP i=3 Creates 'i' only within the scope of the current GOSUB. RETURN happens, variable goes away. Inner scopes mask outer scopes, without interfering; This allows recursion, and some amount of sanity regarding variables. Dynamic scoping is, however, not the direction that the other BASIC's went. For example, QBASIC had went over to block-structuring and lexical scoping. However, for these, would likely need a "real" parser, and not just something that works by reading lines one at a time with an option of GOTO. The other option would have been a LOCAL keyword being used for dynamic variables. But, then I realized I wanted return values in some cases, so: x = GOSUB label ... label: ... RETURN expr Works, but is unorthodox (also breaks the function/subroutine distinction that is present in most other BASICs; but mostly absent in C family languages). However, the GOSUB here is not a true expression, so: x = (GOSUB label) + 1 Is not valid. Well, and then another bit of wonk: t = GOSUB label x=3, y=4 Which works by creating x and y within the callee's frame, so can pass arguments. Also: was the laziest option given the existing implementation. But, is kinda weird... Namely, caller doesn't need to fetch the callee function or know anything about its argument list, as it is merely binding each variable within the callee's dynamic environment frame. Dynamic scoping was less code in effect because it was merely a stack, and call/return marks off the stack position (along with the internal line number and similar). Well, and added vectors: v0 = (vec 1,2,3) Which, possibly, don't really fit in with BASIC. But, interpreter is still around 1500 lines of C, and would likely have been bigger if I did this stuff "properly". Though, now I am starting to second guess myself and wonder if it might have ended up being less code at this point to implement a small version of a language similar to Emacs Lisp. Well, since at some point "parse tokens into S-Expressions and then walk the S-Expression lists" becomes less code than "read line, break into tokens, and walk over tokens and dispatch logic based on said tokens" once one moves past a certain level of triviality. And, with "vec", the difference between "(vec 1,2,3)" and (vec 1 2 3) is not exactly large. I had also been half tempted to use [1,2,3] syntax, as it was going the route (like in OpenSCAD) that arrays and vectors are basically the same thing. In this case, interpreter infers if you do: vec3=vec1+vec2 And, vec1 and vec2 are arrays of the same length, then to do a vector operation. .... Well, and I end up trying to implement an 80s style BASIC and within a week already starts mutating into some sort of weird hybrid of 80s style BASIC and Emacs Lisp... Well, and to add to this, I had also considered adding CSG operations, to allow using it in a vaguely similar way to OpenSCAD, for 3D modeling uses. box1=(csgaabb (vec -10,-10,0),(vec 10,10,4)) box2=(csgaabb (vec -1,-1,4),(vec 1,1,40)) base1=(csgunion box1,box2) ... But, again, this wouldn't be that different from, say: (let box1 (csgaabb (vec -10 -10 0) (vec 10 10 4))) (let box2 (csgaabb (vec -1 -1 4) (vec 1 1 40))) (let base1 (csgunion box1 box2)) Though, with a Lisp variant, would have likely ended up with more proper functions by this point: (defun func (x y) (+ x y)) .... But, errm... Though, can note that in G-Code, there was a trick that can be used to encode block-structuring without the need to break from line-by-line ========== REMAINDER OF ARTICLE TRUNCATED ==========