• Re: Postscript in 4tH

    From Kragen Javier Sitaker@3:633/10 to All on Tue Sep 8 22:38:58 2026
    (Please forgive in advance the LLM-like wording of what follows. I
    wrote it entirely by hand, including reading the documents I linked, but
    I?ve been coaxing

    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Tue, 08 Sep 2026 02:09:43 -0300, Kragen Javier Sitaker wrote:
    I think of PostScript as being Lisp, more or less, but with
    quasi-Forth syntax ...

    PostScript is homoiconic, but the resemblance to Lisp ends there. Lisp
    has macros, PostScript doesn?t. Lisp needs macros,

    At the time that PostScript was designed, most Lisps did not have
    macros; they still had fexprs. Some Lisps did have macros, most notably MACLISP, but they had not yet become central to language style the way
    they are today. Remember that PostScript first shipped in 01982, when
    the world was dominated by BASIC, FORTRAN, and COBOL.

    PostScript does in fact have something like readmacros; in particular, readhexstring was the recommended way to handle image data, so that the
    image data didn?t have to be loaded into a PostScript object by way of
    the PostScript parser. But, for the most part, like Smalltalk,
    PostScript uses a lightweight lambda syntax for most of the things you
    would use macros for in Lisp.

    PostScript has several other key similarities to Lisp. Referencing Paul Graham?s ?What Made Lisp Different? <https://paulgraham.com/diff.html>:

    1. Conditionals. Yes, but all languages had conditionals by 01982.

    2. A function type. ?In Lisp, functions are first class objects--
    they?re a data type just like integers, strings, etc, and have a literal representation, can be stored in variables, can be passed as arguments,
    and so on.? Yes; PostScript goes even harder here than Lisp, because
    there?s actually no way to define a named function in PostScript. You
    have to define an anonymous function and pass it as an argument to /bind
    in order to give it a name. This is somewhat related to homoiconicity
    but is not the same thing.

    3. Recursion. Yes, but only FORTRAN lacked recursion by then. COBOL
    and BASIC lacked local variables, though, which generally made recursion impractical.

    4. A new concept of variables. ?In Lisp, all variables are effectively pointers. Values are what have types, not variables, and assigning or
    binding variables means copying pointers, not what they point to.?

    PostScript does work this way, as does Smalltalk (not coincidentally,
    from the same lab, which was also heavily into Lisp). It?s easy to
    forget how unusual this was, because now languages more or less like
    this dominate the scene: Python, JS, Ruby, PHP, Lua, and to some extent,
    even Java and C#. But none of the other popular languages of the day
    worked that way. Not conventional assembly (untyped, and variables are
    memory addresses), not BASIC, not C, not Pascal, not awk, not the Bourne
    shell, not the C shell, not Forth, not BLISS, not FOCAL, not Mesa, not
    ML, not Algol, not PL/I, not Ada.

    Moreover, in both Lisp and in PostScript, this extends to not just
    regular variables, but any item of any data structure. In BASIC, C,
    FORTRAN, or Pascal, you might have arrays of strings, or arrays of
    integers, or in some cases arrays of 3-element arrays of integers, but
    you couldn?t have an array of arbitrary-type values. PostScript arrays
    contain arbitrary-type values, as do the most common type of vectors in
    Common Lisp, and lists in any Lisp.

    5. Garbage-collection. Yes.

    6. Programs composed of expressions. Not really, but closer than any of
    the currently popular languages.

    7. A symbol type. Yes, PostScript distinguishes /names from strings,
    which is arguably the feature of all of these that is most distinctive
    to Lisp (the only other languages that do this are Ruby, Smalltalk, and,
    in a way, JS), and PostScript shares it.

    8. A notation for code using trees of symbols. Yes. The only
    difference is that in conventional Lisp they?re binary trees, while in PostScript they?re N-ary ordered trees.

    9. ?The whole language always available?, i.e., arbitrarily overlapping
    compile time and runtime. Yes.

    Given that PostScript clearly hits 8 out of 9 of these criteria, and
    arguably all 9, while no then-popular language other than Lisp came
    anywhere close, I think it?s pretty clear that it mostly belongs to the
    Lisp family. Its resemblance to Lisp is much deeper than just being homoiconic.

    Forth hits #1, #3, and #9.

    ****

    If you?re not familiar with PostScript, you might be puzzled by #8.
    Let?s define a new function that tells us the sign of a number and bind
    it to the symbol `sign`:

    GS>/sign {dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse} def
    GS>3 sign = 0 sign = -53 sign =
    positive
    zero
    negative

    We can use `load` on the quoted symbol to find the current definition of
    `sign` without executing it:

    GS>/sign load ===
    {dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse}

    It turns out that?s an array of length 3:

    GS>/sign load type =
    arraytype
    GS>/sign load length =
    6

    We can fetch things from it:

    GS>/sign load 0 get === /sign load 3 get ===
    dup
    {pop /positive}

    See, the zeroth thing in the array is `dup`. Which is a ?name?, not a
    string, which PostScript also has:

    GS>/sign load 0 get type = (hello, world) type =
    nametype
    stringtype

    We can even modify the array in place to modify the code, although we
    can?t lengthen it:

    GS>/sign load 0 /pop cvx put
    GS>/sign load ===
    {pop 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse}

    Now it crashes because `pop` drops the operand `gt` wants to compare:

    GS>3 sign =
    Error: /stackunderflow in --gt--

    You can define a regular, non-executable array and turn it into a
    function with regular programming operators:

    GS>/square 2 array def
    GS>square ===
    [null null]
    GS>square 0 /dup cvx put square ===
    [dup null]
    GS>square 1 /mul cvx put square ===
    [dup mul]
    GS>/square square cvx def 3 square ===
    9
    GS>/square load ===
    {dup mul}

    If you?re familiar with old Lisp systems, almost all of this will seem
    very familiar, but the syntax will seem backwards, and you?ll wonder why
    code is stored in arrays instead of lists.

    You can also define a function that uses the PostScript reader `token`
    to consume input, similar to `word` in Forth but much more similar to
    `read` in Lisp:

    GS>/typeof {currentfile token pop type} def
    GS>typeof dup ===
    nametype
    GS>typeof 34 ===
    integertype
    GS>typeof (hello, world) ===
    stringtype

    Where `token` diverges radically from Forth is that it will read an
    entire nested expression (without executing it), not just a token as the
    term is normally understood:

    GS>typeof {dup mul dup add} ===
    arraytype

    (Note that this *doesn't* work for [3 4], because `[` and `]` are
    operators, so [3 4] isn?t an expression!)

    Given that you can parse expressions from the input stream at compile
    time and generate code at runtime, you can obviously implement Lisp-like
    macros in PostScript. It just isn?t an established practice.

    The one very non-Lispy weird thing, which doesn't have a similar feature
    in any other language I know of, is that the executable bit set by `cvx`
    is an attribute of references, not objects. So `{dup mul}` and `[dup
    mul]` can be the same object as seen through two different references.
    That?s why making `square` executable required calling `def` again
    rather than just `cvx`.

    ****

    To get a feel for what 01982 was like, check out the October 01982 issue
    of the CACM <https://dl.acm.org/toc/cacm/1982/25/10> or of Byte <https://archive.org/details/BYTE_Vol_07-10_1982-10_Computers_in_Business>.

    The languages mentioned in the CACM issue are Fortran (in the table of contents); COBOL, PL/I, and APL (in the data administration article);
    Algol 60 and Macsyma but not Lisp (in the ?microanalysis? (profiling)
    paper, which also included some Algol as pseudocode); Lisp as the
    pseudocode, though without naming it (in Kornfield?s paper about ?combinatorially implosive? algorithms); none (in the geometric
    algorithm paper); Fortran (in the gamma-deviate-generation comment); and
    Ada (in the ?ACM Forum? letters column.) It?s a total head trip.

    In Byte, the languages mentioned are 68000 assembler, FORTRAN 77,
    Pascal, BASIC, COBOL, and C (in the Cromemco ad); Visicalc, BASIC,
    Pascal, FORTH, and BASIC again (in the table of contents); Pascal
    (though it?s really talking about the UCSD Pascal OS) and FORTRAN (in
    the editorial); BASIC, COBOL, BASIC again, and LISP (in the Letters);
    Visicalc and something called Microfinesse (in the Visicalc article);
    etc.

    That?s just the first 41 pages; I?ll leave the other 490 pages to you.

    Kragen

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Wed Sep 9 03:05:28 2026
    On Tue, 08 Sep 2026 22:38:58 -0300, Kragen Javier Sitaker wrote:

    PostScript does in fact have something like readmacros; in particular, readhexstring was the recommended way to handle image data, so that the
    image data didn?t have to be loaded into a PostScript object by way of
    the PostScript parser.

    It?s just a function. It?s like saying Python has ?readmacros? because
    the JSON and XML library modules have functions for reading input
    streams and returning language-object representations of those
    structures.

    2. A function type. ?In Lisp, functions are first class objects--
    they?re a data type just like integers, strings, etc, and have a
    literal representation, can be stored in variables, can be passed as arguments, and so on.? Yes; PostScript goes even harder here than
    Lisp, because there?s actually no way to define a named function in PostScript. You have to define an anonymous function and pass it as
    an argument to /bind in order to give it a name. This is somewhat
    related to homoiconicity but is not the same thing.

    Homoiconicity comes in because the function body is stored in an array
    object, which is a PostScript language type, and its contents are also
    objects of various PostScript language types.

    Where PostScript is lacking is in missing support for lexical binding.
    Anybody who has done much PostScript programming knows how awkward it
    is to simulate anything resembling local variables.

    4. A new concept of variables. ?In Lisp, all variables are
    effectively pointers. Values are what have types, not variables, and assigning or binding variables means copying pointers, not what they
    point to.?

    PostScript does work this way, as does Smalltalk (not
    coincidentally, from the same lab, which was also heavily into
    Lisp). It?s easy to forget how unusual this was, because now
    languages more or less like this dominate the scene: Python, JS,
    Ruby, PHP, Lua, and to some extent, even Java and C#. But none of
    the other popular languages of the day worked that way.

    Fair enough.

    5. Garbage-collection. Yes.

    Note that this was only added in PostScript level 2. In the original
    language implementation, memory management had to be done in (I think
    it?s called) mark-release style, using explicit calls to the ?save?
    and ?restore? functions.

    6. Programs composed of expressions. Not really, but closer than any
    of the currently popular languages.

    No distinction between ?statements? and ?expressions?.

    7. A symbol type. Yes, PostScript distinguishes /names from strings,
    which is arguably the feature of all of these that is most
    distinctive to Lisp (the only other languages that do this are Ruby, Smalltalk, and, in a way, JS), and PostScript shares it.

    True enough.

    Java has the option for ?interned? strings, which is a way of having a
    symbol type without having a symbol type, if you like.

    8. A notation for code using trees of symbols. Yes. The only
    difference is that in conventional Lisp they?re binary trees, while
    in PostScript they?re N-ary ordered trees.

    In Lisp, the tree structure is apparent in the static syntax. In
    PostScript, the tree structure (such as it is) gets largely
    constructed dynamically at run time, and need not correspond to static
    syntax at all.

    9. ?The whole language always available?, i.e., arbitrarily
    overlapping compile time and runtime. Yes.

    Yes.

    Given that PostScript clearly hits 8 out of 9 of these criteria, and
    arguably all 9, while no then-popular language other than Lisp came
    anywhere close, I think it?s pretty clear that it mostly belongs to
    the Lisp family.

    Only insofar as every dynamic language ?mostly belongs to the Lisp
    family?.

    Its resemblance to Lisp is much deeper than just being homoiconic.

    As you pointed out, other dynamic languages had their own versions
    of these features.

    Paul Graham?s list is misleading, because it misses out things that
    make Lisp distinctive -- perhaps because they were not so well
    appreciated in the days before other dynamic languages became popular.

    Forth hits #1, #3, and #9.

    Leaves out homoiconicity, which is such a fundamental thing.

    Given that you can parse expressions from the input stream at
    compile time and generate code at runtime, you can obviously
    implement Lisp-like macros in PostScript. It just isn?t an
    established practice.

    Unnecessary, because it just doesn?t buy you much in functionality,
    the way it does in Lisp.

    This should be a clue as to how PostScript is very much different
    from Lisp-like languages.

    The one very non-Lispy weird thing, which doesn't have a similar
    feature in any other language I know of, is that the executable bit
    set by `cvx` is an attribute of references, not objects. So `{dup
    mul}` and `[dup mul]` can be the same object as seen through two
    different references. That?s why making `square` executable required
    calling `def` again rather than just `cvx`.

    The same is true of read and write access bits -- except for
    dictionaries.

    I remember doing some experiments with an Apple LaserWriter and a
    Macintosh II back in the late 1980s. Someone had cracked the
    encryption of the ?eexec? operator, and we used this to discover that
    there was this other thing called ?cexec? (only allowed within
    encrypted ?eexec? execution, which is why you never saw it in public
    code) that let you load MC68000 machine code into the printer and hook
    into an API provided by the PostScript interpreter to manipulate
    PostScript objects and make function calls.

    This effort was triggered by the existence of an app called
    ?LaserTalk?, which did something truly magical and supposedly
    impossible: you could use it to send PostScript code to the printer,
    and get back the high-resolution rendered image as a file.

    The Macintosh compilers already generated MC68000 machine code, and I
    was able to whip up build scripts to link that code into the right
    format, with appropriately-set-up header fields, so that the printer
    would load and execute it.

    All this cexec/eexec stuff went away in PostScript level 2 and later
    -- along with the dependency on Motorola-68K-family processors.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Johann 'Myrkraverk' Oskarsson@3:633/10 to All on Wed Sep 9 15:15:18 2026
    On 09/09/2026 9:38 AM, Kragen Javier Sitaker wrote:
    (Please forgive in advance the LLM-like wording of what follows. I
    wrote it entirely by hand, including reading the documents I linked, but
    I?ve been coaxing


    Thank you for that wonderful exposition of PostScript, I'm almost tempt-
    ed to write my very own P.S. interpreter, but that's going to have to
    wait a better time, as I already have n+1 projects.

    And don't worry about sounding like an L.L.M. After all, they were
    trained on the best of us, and sound like us -- good writers -- more
    than we sound like them. That's how time works.

    I take it you've been coaxing Lawrence, but that's a waste of time, just
    fire away and forget about it; I don't care, and Lawrence doesn't care
    either.


    Best wishes, and happy coaxing Lawrence!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Anton Ertl@3:633/10 to All on Wed Sep 9 07:35:21 2026
    Kragen Javier Sitaker <kragen@canonical.org> writes:
    PostScript has several other key similarities to Lisp. Referencing Paul >Graham's "What Made Lisp Different" <https://paulgraham.com/diff.html>:

    Given your claim below that Forth has only #1, #3, #9, let's look at
    the others:

    2. A function type. "In Lisp, functions are first class objects--
    they're a data type just like integers, strings, etc, and have a literal >representation, can be stored in variables, can be passed as arguments,
    and so on."

    Forth has execution tokens.

    4. A new concept of variables. "In Lisp, all variables are effectively >pointers. Values are what have types, not variables, and assigning or
    binding variables means copying pointers, not what they point to."

    In Forth, neither variables nor values have types that are known to
    the Forth system. The way to use that is that the programmer must
    know the type, and that typically means always storing the same type
    into a variable.

    5. Garbage-collection.

    Not built into Forth.

    6. Programs composed of expressions. Not really, but closer than any of
    the currently popular languages.

    Forth is like Postscript here: Programs composed of words (operators
    in Postscript), which work on values on the stack.

    7. A symbol type. Yes, PostScript distinguishes /names from strings,

    Forth distinguishes name tokens from strings, but they do not play the
    role they play in Lisp or Postscript; in particular, you have to
    create new named words outside a colon definitions, and if you use a
    named word to get a unique value, you tend to use its body address,
    not it's name token.

    8. A notation for code using trees of symbols. Yes. The only
    difference is that in conventional Lisp they're binary trees, while in >PostScript they're N-ary ordered trees.

    As mentioned in <2026Sep9.084205@mips.complang.tuwien.ac.at>, one
    might consider the indirect-threaded code representation and its
    modern equivalents for inlining to be such a notation, but in Forth
    many control structures become branches, whereas in Postscript the
    controlled stuff is a procedure (in Forth terminology, a quotation).

    Forth hits #1, #3, and #9.

    And also #2, and partially #6, #7, and #8.

    Followups reduced to comp.lang.forth. Add some other groups if you
    really have to.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Wed Sep 9 08:10:15 2026
    On Wed, 09 Sep 2026 07:35:21 GMT, Anton Ertl wrote:

    In Forth, neither variables nor values have types that are known to
    the Forth system. The way to use that is that the programmer must
    know the type, and that typically means always storing the same type
    into a variable.

    Forth is not strongly-typed. I?m not aware of any high-level dynamic
    language that is not strongly-typed. At a bare minimum, you need to
    know what value is a pointer and what isn?t, otherwise memory
    management is not going to be a happy business.

    6. Programs composed of expressions. Not really, but closer than any of
    the currently popular languages.

    Forth is like Postscript here: Programs composed of words (operators
    in Postscript), which work on values on the stack.

    Note that procedures are also executable entities in PostScript. They
    exist apart from any name that might be referencing them (aka
    ?lambdas?). And they can be constructed dynamically at run-time.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Paul Rubin@3:633/10 to All on Wed Sep 9 03:01:07 2026
    Kragen Javier Sitaker <kragen@canonical.org> writes:
    At the time that PostScript was designed, most Lisps did not have
    macros; they still had fexprs.... PostScript first shipped in 01982,

    1982 was right in Lisp's heyday and I expect the major implementations
    all had macros (Maclisp, Lisp Machine Lisp and its descendants, Nil, Spicelisp). Scheme was around and didn't have macros but I don't think
    it had fexprs either.

    Given that PostScript clearly hits 8 out of 9 of these criteria, and
    arguably all 9, while no then-popular language other than Lisp came
    anywhere close, I think it?s pretty clear that it mostly belongs to the
    Lisp family.

    I think that list is pretty bogus. The main characteristic of Lisp imho
    is lambda calculus and lambda binding. In order to even have a local
    variable in a PostScript function, you have to literally push a
    dictionary on the evaluation stack and invoke BEGIN, then later END to
    pop the dictionary stack, like Forth wordlists.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Kragen Javier Sitaker@3:633/10 to All on Thu Sep 10 01:06:45 2026
    (Apologies if this sounds like AI slop; I?ve been interacting with
    Claude Opus 5 a lot today, and although I didn?t use any LLM to write
    any of this, its voice may be leaking into mine.)

    Lawrence D?Oliveiro <ldo@nz.invalid> writes:
    On Tue, 08 Sep 2026 22:38:58 -0300, Kragen Javier Sitaker wrote:
    PostScript does in fact have something like readmacros; in particular,
    readhexstring was the recommended way to handle image data, so that the
    image data didn?t have to be loaded into a PostScript object by way of
    the PostScript parser.

    It?s just a function. It?s like saying Python has ?readmacros? because
    the JSON and XML library modules have functions for reading input

    It?s a function that runs while the PostScript source code is being
    read, which means that it can change the interpretation of that source
    code. That?s a way that PostScript is like Lisp and unlike Python.
    Python?s JSON and XML library modules? functions are different from Lisp readmacros because they cannot read from the Python input stream; the
    Python parser finishes running before any Python code runs. There?s no
    way to do the equivalent of this readmacro-like example from my post in
    Python:

    GS>/typeof {currentfile token pop type} def
    GS>typeof dup ===
    nametype

    To sharpen this, note that it doesn?t matter that `dup` is defined:

    GS>typeof oagjijgw ===
    nametype

    You?d have to be able to write something in Python which gave you a
    session like this:

    $ python3
    Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from somemodule import typeof
    >>> typeof(oagjijgw) # or ideally even without the parentheses
    <ast.Name object at 0x7f52bb870130>

    I claim that this `typeof` function is impossible to write in Python,
    because the parser throws a NameError before `typeof` has a chance to
    run, given that I haven?t defined `oagjijgw` previously.

    That is how PostScript?s `token` (and `readstring`, `readhexstring`,
    etc.) are more like Lisp readmacros than they are like any function in
    any possible Python module.

    2. A function type. ?In Lisp, functions are first class objects-- [...]

    Homoiconicity comes in because the function body is stored in an array object, which is a PostScript language type, and its contents are also objects of various PostScript language types.

    The analogous statements were true of most Lisps in 01982, because
    `lambda` was basically implemented as `quote` when it was the argument
    of a function (or subr), but they are true of almost no other
    programming languages except assembly language, so this is another
    similarity between PostScript and Lisp.

    However, the analogous statements are not true of most current Lisps,
    which have adopted the position that lambda-expressions are lists, and functions are not lists. SBCL, for example:

    * (type-of 3)
    (INTEGER 0 4611686018427387903)
    * (type-of (lambda () 3))
    COMPILED-FUNCTION
    * (type-of '(lambda () 3))
    CONS

    Or Racket:

    > (pair? (lambda () 3))
    #f
    > (pair? '(lambda () 3))
    #t
    > (procedure? (lambda () 3))
    #t
    > (procedure? '(lambda () 3))
    #f

    Perhaps you would argue that PostScript is homoiconic and SBCL and
    Racket are not. However, most people who know the word do consider them
    to be homoiconic, because, even though the *runtime representation* of functions does not contain objects of various Lisp language types, the
    *source code* is just a Lisp list, so building Lisp code in Lisp is
    easy:

    > (eval (cons 'lambda (cons '() (cons 3 '()))))
    #<procedure>
    > ((eval (cons 'lambda (cons '() (cons 3 '())))))
    3

    Unfortunately, this definition of homoiconicity suggests that any
    language compiled from sequences of characters is ?homoiconic? unless
    for some reason it lacks the ability to manipulate sequences of
    characters. So ?homoiconic languages? is no longer a logically
    well-defined category; it?s more a sliding scale of what programmers
    find easy or difficult.

    But, look again at PG?s explanation of what he means by ?a function
    type?:

    ?In Lisp, functions are first class objects-- they?re a data type
    just like integers, strings, etc, and have a literal representation,
    can be stored in variables, can be passed as arguments, and so on.?

    The only one of these that plausibly relates to homoiconicity is ?have a literal representation?. In old Lisps you could just print out the
    definition of any function as an S-expression, but, as we see above with Racket, that is not currently the case; we get something like
    `#<FUNCTION (LAMBDA ()) {534960EB}>` or `#<procedure>`. But that?s
    still a string representation! And *most* languages don't have the
    ability to print out, for example, struct literals.

    What makes a ?string literal? or ?integer literal? a *literal* is that
    you can put it in your source code without having to give it a name.

    C since C99 has ?compound literals? for structs and arrays <https://en.cppreference.com/c/language/compound_literal>, which look
    like `(struct foo){3, "hi"}`, and evaluate to a value of that type
    without having to bind it to a name. Older versions of ANSI C treated
    structs the way C still treats functions; instead of writing

    return (struct point){x + dx, y + dy};

    you had to write

    struct point p = {x + dx, y + dy};
    return p;

    Golang struct literals are the same thing
    <https://go.dev/tour/moretypes/5>: ?A struct literal denotes a newly
    allocated struct value by listing the values of its fields.?

    JS has ?array literals? <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Array#array_literal_notation>
    for which the example given is `["Apple", "Banana"]`.

    So the most plausible gloss of PG?s ?has a literal representation? is
    that there are Lisp expressions which create new functions when
    evaluated, which is just as true of modern Common Lisp and Scheme as it
    was of Franz Lisp in 01982. And it?s obviously true of PostScript, but
    not of C, Pascal, BASIC, etc.

    Where PostScript is lacking is in missing support for lexical binding. Anybody who has done much PostScript programming knows how awkward it
    is to simulate anything resembling local variables.

    This is another similarity between PostScript and the then-popular
    Lisps, which almost entirely used dynamic scope like PostScript, rather
    than lexical scope like almost all other programming languages.
    However, it is true that the facility is more convenient to use in Lisp.

    Here?s a slightly reformatted interactive session showing how awkward it
    is to simulate anything resembling local variables, in case anyone is
    curious:

    GS>/! {exch def} def
    GS>/printbuf ( ) def /. {printbuf cvs print} def
    GS>/hanoi {
    4 dict begin
    /n ! /dest ! /storage ! /src !
    n 0 gt {
    src dest storage n 1 sub hanoi
    n . ( from ) print src . ( to ) print dest =
    storage src dest n 1 sub hanoi
    } if
    end
    } def
    GS>/A /B /C 3 hanoi
    1 from A to C
    2 from A to B
    1 from C to B
    3 from A to C
    1 from B to A
    2 from B to C
    1 from A to C

    This is definitely more awkward than in most languages, but it doesn?t
    seem prohibitive to me.

    One major annoyance for interactive use is that, if execution aborts with
    an error, all those local variable scopes are left on the stack. Might
    be useful for debugging, I guess, but you can end up redefining things
    in the wrong dictionary.

    5. Garbage-collection. Yes.

    Note that this was only added in PostScript level 2. In the original
    language implementation, memory management had to be done in (I think
    it?s called) mark-release style, using explicit calls to the ?save?
    and ?restore? functions.

    Really? I had no idea! I always wondered why they thought `save` and `restore` were worth incorporating. So that?s a way in which the
    original PostScript was actually more like Forth, where you use `marker`
    (or `forget`) to deallocate a chunk of the dictionary.

    6. Programs composed of expressions. Not really, but closer than any
    of the currently popular languages.

    No distinction between ?statements? and ?expressions?.

    Yes, that?s true, PostScript doesn?t have a statement/expression
    distinction. But in that case we should award this Lispiness point to
    Forth as well.

    7. A symbol type. [...]

    True enough.

    Java has the option for ?interned? strings, which is a way of having a
    symbol type without having a symbol type, if you like.

    That?s true, and in particular that?s useful if you?re implementing some
    kind of language parser or interpreter in Java. Python has this too.

    8. A notation for code using trees of symbols. Yes. The only
    difference is that in conventional Lisp they?re binary trees, while
    in PostScript they?re N-ary ordered trees.

    In Lisp, the tree structure is apparent in the static syntax. In
    PostScript, the tree structure (such as it is) gets largely
    constructed dynamically at run time, and need not correspond to static
    syntax at all.

    I?m not sure I agree. Here?s my example of tree-structured code in
    PostScript, from the previous post:

    GS>/sign {dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse} def

    This is an executable array of six things, the fourth of which is `{pop /positive}`. To me, that?s *more* apparent from the static syntax than
    the fact that in `(lambda (x y) (sqrt (+ (* x x) (* y y))))` the car of
    the car of the cdr of the cdr is `sqrt`. The Lisp structure is a binary
    tree, but the static syntax is formatted as an N-ary ordered tree. In PostScript, the N-ary ordered tree of the static syntax corresponds to
    the N-ary ordered tree in the PostScript data model.

    (Note that this is another way in which PostScript is like Lisp but
    unlike Forth: in Forths that expose their representation of threaded
    code, it?s an array of xts, not a tree.)

    There are *other* structures that get constructed dynamically at run
    time in PostScript, such as the implicit gsave/grestore tree, the
    call/return tree, the implicit begin/end tree, the implicit save/restore tree???basically anything with a stack could be said to dynamically
    construct a sort of tree structure at run time.

    Given that PostScript clearly hits 8 out of 9 of these criteria, and
    arguably all 9, while no then-popular language other than Lisp came
    anywhere close, I think it?s pretty clear that it mostly belongs to
    the Lisp family.

    Only insofar as every dynamic language ?mostly belongs to the Lisp
    family?.

    PostScript is a lot insofarther, I think. For example, Python, JS, and
    Lua hit #1-5, but not #6-9, although they do have `eval`, so you can
    compile code at runtime. None of them let you run code at compile time
    or at read time. Perl4 is usually considered a dynamic language and
    doesn?t have references at all, so we lose #4; Perl5 has references, but distinguishes between the reference and the thing it refers to, so that
    you can take references to references, so we still lose #4, but we gain
    #9 because you can write things like Perligata. Tcl not only doesn?t
    have references, it doesn?t even have dynamic typing; everything is a
    string, but it sort of has #8 except that it doesn?t have a symbol type.

    [...]
    the executable bit set by `cvx` is an attribute of references, not
    objects.

    The same is true of read and write access bits -- except for
    dictionaries.

    I had no idea!

    I remember doing some experiments with an Apple LaserWriter and a
    Macintosh II back in the late 1980s. Someone had cracked the
    encryption of the ?eexec? operator, and we used this to discover that
    there was this other thing called ?cexec? (only allowed within
    encrypted ?eexec? execution, which is why you never saw it in public
    code) that let you load MC68000 machine code into the printer and hook
    into an API provided by the PostScript interpreter to manipulate
    PostScript objects and make function calls.

    Wow, that?s amazing! I only ever knew that there was some controversy
    about extracting fonts. Maybe this is how it was done?

    [...] The Macintosh compilers already generated MC68000 machine code,
    and I was able to whip up build scripts to link that code into the
    right format, with appropriately-set-up header fields, so that the
    printer would load and execute it.

    What a great feeling!

    Kragen

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Kragen Javier Sitaker@3:633/10 to All on Thu Sep 10 01:11:36 2026
    Johann 'Myrkraverk' Oskarsson <johann@myrkraverk.invalid> writes:
    Thank you for that wonderful exposition of PostScript, I'm almost tempt-
    ed to write my very own P.S. interpreter, but that's going to have to
    wait a better time, as I already have n+1 projects.

    Maybe you can coax an LLM to do it.

    Best wishes, and happy coaxing Lawrence!

    I choose to interpret this as ?Happy coaxing, Lawrence!?, as an aside to
    him in a message to me, hoping that he is successful in coaxing whoever
    he needs to coax.

    Kragen

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Thu Sep 10 07:38:19 2026
    On Thu, 10 Sep 2026 01:06:45 -0300, Kragen Javier Sitaker wrote:

    Lawrence D?Oliveiro <ldo@nz.invalid> writes:

    On Tue, 08 Sep 2026 22:38:58 -0300, Kragen Javier Sitaker wrote:

    PostScript does in fact have something like readmacros; in
    particular, readhexstring was the recommended way to handle image
    data, so that the image data didn?t have to be loaded into a
    PostScript object by way of the PostScript parser. >> >> It?s just
    a function. It?s like saying Python has ?readmacros? because >>
    the JSON and XML library modules have functions for reading input

    It?s a function that runs while the PostScript source code is being
    read, which means that it can change the interpretation of that
    source code.

    You?d have to be able to write something in Python which gave you a
    session like this:

    $ python3
    Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from somemodule import typeof
    >>> typeof(oagjijgw) # or ideally even without the parentheses
    <ast.Name object at 0x7f52bb870130>

    I claim that this `typeof` function is impossible to write in
    Python, because the parser throws a NameError before `typeof` has a
    chance to run, given that I haven?t defined `oagjijgw` previously.

    How about this little program:

    import code
    import ast

    class TypeofExpander(ast.NodeTransformer) :

    def visit_Call(self, node) :
    result = node
    if isinstance(node.func, ast.Name) and node.func.id == "typeof" :
    result = ast.Constant(value = type(node.args[0]).__name__)
    else :
    result = type(node) \
    (
    func = node.func,
    args = list(self.visit(a) for a in node.args),
    keywords = list((a.arg, self.visit(a.value)) for a in node.keywords)
    )
    #end if
    return result
    #end visit_Call

    def visit_Expr(self, node) :
    return type(node)(self.visit(node.value))
    #end visit_Expr

    #end TypeofExpander

    console = code.InteractiveConsole()
    while True :
    line = input("? ")
    syntax_in = ast.parse(line, mode = "single")
    syntax_out = TypeofExpander().visit(syntax_in)
    ast.fix_missing_locations(syntax_out)
    code = compile(syntax_out, filename = "<console>", mode = "single")
    console.runcode(code)
    #end while

    Example run:

    ldo@theon:python_try> ./readmacro_fakeit
    ? 2 + 2
    4
    ? import math
    ? math.log
    <built-in function log>
    ? typeof(oagjijgw)
    'Name'
    ? print("typeof(", oagjijgw, ") is", typeof(oagjijgw))
    Traceback (most recent call last):
    File "<console>", line 1, in <module>
    NameError: name 'oagjijgw' is not defined
    ? print("typeof(oagjijgw) is", typeof(oagjijgw))
    typeof(oagjijgw) is Name
    ? print("typeof(", math.log, ") is", typeof(math.log))
    typeof( <built-in function log> ) is Attribute

    Homoiconicity comes in because the function body is stored in an
    array object, which is a PostScript language type, and its contents
    are also objects of various PostScript language types.

    The analogous statements were true of most Lisps in 01982, because
    `lambda` was basically implemented as `quote` when it was the
    argument of a function (or subr), but they are true of almost no
    other programming languages except assembly language, so this is
    another similarity between PostScript and Lisp.

    Except that ...

    However, the analogous statements are not true of most current
    Lisps, which have adopted the position that lambda-expressions are
    lists, and functions are not lists.

    So this is no longer a similarity between PostScript and Lisp.

    Perhaps you would argue that PostScript is homoiconic and SBCL and
    Racket are not. However, most people who know the word do consider
    them to be homoiconic, because, even though the *runtime
    representation* of functions does not contain objects of various
    Lisp language types, the *source code* is just a Lisp list, so
    building Lisp code in Lisp is easy:

    OK, so the definition of ?homoiconic? becomes ?the AST *can* be
    represented in terms of language objects?, not ?the AST *is*
    represented in terms of language objects?. I can accept that.

    Unfortunately, this definition of homoiconicity suggests that any
    language compiled from sequences of characters is ?homoiconic?
    unless for some reason it lacks the ability to manipulate sequences
    of characters.

    I don?t see why that should be ?unfortunate? for the definition (for
    the language -- that?s another matter). But I think a sequence of text characters is too low-level and unstructured a representation to be
    worthy of the name ?homoiconic?, anyway. It definitely has to involve
    the AST level in some way.

    But, look again at PG?s explanation of what he means by ?a function
    type?:

    ?In Lisp, functions are first class objects-- they?re a data type
    just like integers, strings, etc, and have a literal
    representation, can be stored in variables, can be passed as
    arguments, and so on.?

    The only one of these that plausibly relates to homoiconicity is
    ?have a literal representation?. In old Lisps you could just print
    out the definition of any function as an S-expression, but, as we
    see above with Racket, that is not currently the case; we get
    something like `#<FUNCTION (LAMBDA ()) {534960EB}>` or
    `#<procedure>`. But that?s still a string representation! And *most* languages don't have the ability to print out, for example, struct
    literals.

    Python can. If your class defines a method called ?__repr__?, it will
    be called by the built-in repr() function, and is expected to return
    some string representation that should be usable to reconstruct the object.

    Then there is also the ?__str__? method and and corresponding built-in
    str() function, which is just expected to return some convenient,
    reasonably descriptive string.

    Where PostScript is lacking is in missing support for lexical
    binding. Anybody who has done much PostScript programming knows how
    awkward it is to simulate anything resembling local variables.

    This is another similarity between PostScript and the then-popular
    Lisps, which almost entirely used dynamic scope like PostScript,
    rather than lexical scope like almost all other programming
    languages. However, it is true that the facility is more convenient
    to use in Lisp.

    Here?s a slightly reformatted interactive session showing how
    awkward it is to simulate anything resembling local variables, in
    case anyone is curious:

    ...

    This is definitely more awkward than in most languages, but it
    doesn?t seem prohibitive to me.

    For comparison, here?s an example from my PostScript-alike:

    /Count 99 ddef

    /metatry
    { # provides context for nonlocals
    dup
    /Name exch ldef
    /Count 0 ldef
    { # actual proc
    /Count dup lload 1 add lstore
    /Count dup dload 1 add dstore
    Name =
    (local Count = ) print /Count lload =
    (global Count = ) print /Count dload =
    (whichever Count = ) print Count =
    }
    }
    ddef

    /try1 metatry ddef
    /try2 metatry ddef

    try1
    try2
    try1
    try2

    Note the use of ldef/lload/store for lexical binding, and
    ddef/dload/dstore for dynamic binding, in place of simple
    def/load/store in old PostScript. As a result, functions now actually
    become useful as first-class objects. The output is:

    try1
    local Count = 1
    global Count = 100
    whichever Count = 1
    try2
    local Count = 1
    global Count = 101
    whichever Count = 1
    try1
    local Count = 2
    global Count = 102
    whichever Count = 2
    try2
    local Count = 2
    global Count = 103
    whichever Count = 2

    7. A symbol type. [...]

    True enough.

    Java has the option for ?interned? strings, which is a way of having a
    symbol type without having a symbol type, if you like.

    That?s true, and in particular that?s useful if you?re implementing some
    kind of language parser or interpreter in Java. Python has this too.

    Python doesn?t need it, because simple ?==? equality comparison works
    anyway, whereas it doesn?t in Java.

    8. A notation for code using trees of symbols. Yes. The only
    difference is that in conventional Lisp they?re binary trees, while
    in PostScript they?re N-ary ordered trees.

    In Lisp, the tree structure is apparent in the static syntax. In
    PostScript, the tree structure (such as it is) gets largely
    constructed dynamically at run time, and need not correspond to
    static syntax at all.

    I?m not sure I agree. Here?s my example of tree-structured code in PostScript, from the previous post:

    GS>/sign {dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse} def

    This is an executable array of six things, the fourth of which is `{pop /positive}`. To me, that?s *more* apparent from the static syntax than
    the fact that in `(lambda (x y) (sqrt (+ (* x x) (* y y))))` the car of
    the car of the cdr of the cdr is `sqrt`.

    I said ?need not correspond?, not ?does not correspond?. Just because
    you can write structures statically in PostScript doesn?t mean you
    have to.

    I remember doing some experiments with an Apple LaserWriter and a
    Macintosh II back in the late 1980s. Someone had cracked the
    encryption of the ?eexec? operator, and we used this to discover
    that there was this other thing called ?cexec? (only allowed within
    encrypted ?eexec? execution, which is why you never saw it in
    public code) that let you load MC68000 machine code into the
    printer and hook into an API provided by the PostScript interpreter
    to manipulate PostScript objects and make function calls.

    Wow, that?s amazing! I only ever knew that there was some
    controversy about extracting fonts. Maybe this is how it was done?

    There are PostScript functions called ?charpath? and ?pathforall?. The
    former lets you add a piece of text as outlines to the current path,
    instead of directly rendering it. Then you can create special effects
    with the usual filling and stroking operators. ?pathforall? is a way
    to traverse the current elements of the path, invoking suitable
    callbacks for each. In PostScript level 1, the path was locked against traversal if it contained any outlines generated from Adobe?s
    proprietary Type 1 fonts.

    This restriction was (mostly) lifted in PostScript level 2.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Peter Flass@3:633/10 to All on Thu Sep 10 07:39:22 2026
    On 9/9/26 21:06, Kragen Javier Sitaker wrote:
    (Apologies if this sounds like AI slop; I?ve been interacting with
    Claude Opus 5 a lot today, and although I didn?t use any LLM to write
    any of this, its voice may be leaking into mine.)

    You are being assimilated. Resistance is futile.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Hans Bezemer@3:633/10 to All on Thu Sep 10 17:58:32 2026
    On 09-09-2026 10:10, Lawrence D?Oliveiro wrote:
    Forth is not strongly-typed. I?m not aware of any high-level dynamic
    language that is not strongly-typed. At a bare minimum, you need to
    know what value is a pointer and what isn?t, otherwise memory
    management is not going to be a happy business.

    If you created that pointer -- and then forgot it was a pointer -- man,
    I'd be visiting a doctor.. :-)

    Or at least learn to use another commenting style. Gee, I do OOP without
    any safety net -- and I'm still breathing.

    Hans Bezemer


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)