1. 53

    1. 4

      That IS pretty impressive for a late-1980’s compiler, though I guess Modula-2 in theory had most of those things in the early 80’s. I wonder how compilation times and memory usage fared?

    2. 4

      GCC also has case ranges: https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html

      case 'a' ... 'z':

      I’ve not used it much, but apparently:

      • Only the inclusive kind: Two dots doesn’t compile.
      • No disjunction syntax. Fallthrough it is.

      Quiz: Which range is this?

      case 'A' ... 'Z' | ' ':
      1. 2

        Quiz: Which range is this?

        All the uppercase and lowercase letters and six pieces of punctuation, assuming ASCII. 'Z' | ' ' == 'z'.

        1. 1


      2. 1

        Be careful: Write spaces around the …, for otherwise it may be parsed wrong when you use it with integer values.

        This sounds scary :-D

        1. 2

          C is scary.

        2. 2

          Ah yes, the dreaded C-and-a-half ’C’.5

    3. 3

      Pascal’s nested functions actually did/do work as (non-escaping) closures. The function-pointer type is fat, carrying a context pointer as well as the code address. Regular function pointers ignore the context, but for a nested function the context points back to the outer function’s stack frame.

      I don’t know if this was true of all Pascals, but IIRC both HP’s and Apple’s worked this way.

      1. 3

        Apple’s blocks extension made block pointers a single word, but that pointed to a struct that had an invoke field containing the function pointer. This was nicer in the type system because you knew whether it was safe to capture (blocks must be explicitly ‘copied’, which either promoted them to the heap or incremented their reference count). Making things that are global and always safe and things that are on the stack and require promotion to copy the same type is a big source of bugs (the fact that C/C++ don’t make this distinction for arbitrary pointers has caused a lot of security problems and is probably the biggest single win for Rust).

        As I recall, blocks didn’t properly support attribute cleanup, so was impossible to use safely in C for any non-trivial captures (fine for C++ or Objective-C objects with destructors), though I think that was fixed a couple of years ago.

    4. 2

      This reminded me of some Open Watcom extensions I found interesting / useful. One of them is syntax for declaring a custom calling convention, which enables interop with wonky DOS-era assembly code that rolls its own calling convention(s).

      1. 3

        That’s interesting, how did it work? I only ever came across C and Pascal conventions back in those days, so I never thought about needing to support custom weird stuff.

        1. 4

          You can see the documentation here: https://open-watcom.github.io/open-watcom-v2-wikidocs/cguide.html#16Mbit__Auxiliary_Pragmas.

          It looks like there’s a declarative DSL to specify calling conventions, and the well-known ones are expressed in terms of it. I can’t say anything about the compiler architecture implications of supporting such a feature; I wonder how such a thing would look in LLVM, whose ABI lowering is notoriously difficult and spread out.

    5. 0

      A function nested in a generator can capture the yield operation from the outer generator, and the nested function can call itself recursively to traverse a tree or other recursive data structure, yield-ing at each level to produce values for the generator. I don’t think you can do that in Python or in many other mainstream languages with generator coroutines.

      You can do that in Python(since 3.3), here’s (roughly) equivalent code

      def each_node(node):
          if n is None:
          yield from each_node(n.left)
          yield n
          yield from each_node(n.right)

      Actually doesn’t need nested functions in this case (but I guess they did it to show that you can?).