poslin0.4.0-0.2.1-1 is up. The additional number at the end has been added because this is the first distribution which contains more than just the standard library. It will increment when some of the supplemental libraries change for a new release and none of the other version numbers change. It will be reset to 1 every time one of the previous version numbers changes.
Or rather, it contains more parts of the standard library than is loaded by default with poslin1. Yes, that's right, packages have been implemented!
The libraries in the distribution have been moved to their own subfolder: `lib`.
If you want to use packages, either run
./poslin1 ./lib/package.poslin
or
./poslin2
The latter later also will load other supplemental libraries, as soon as they are implemented, which might be undesirable for some.
I have to give special thanks to my brother here, who gave me invaluable criticism of some of the things I was going to do. Neither `import` nor `@` would exist without him and the syntax for defining and loading packages would have been mashed together into one big horrible mess instead of being neatly separated.
I would never have guessed how damn complex this would be. The code is clunky and inefficient, but it does what I want it to do and as this stuff should be running at compilation time only and only sparsely then this shouldn't be too much of a problem. I think. Hopefully later optimization stuff will help to make it faster.
To the package system!
If you work in almost any modern programming language there is some way to avoid name clutter. This is normally called something like "modules" or "namespaces" or in the case of Common Lisp "packages" (although it also has namespaces, which are the division between different kinds of things that may be accessed via symbols, like functions and variables, which inhabit different namespaces, but I diverge).
Poslin has namespaces for avoiding naming conflicts between different types of objects (such as operations, variables and class names) already with its slots. This doesn't help with avoiding naming conflicts between different operations. That's what packages are for.
They give you, uhm,
loadable units of stuff that belongs together. They have some content (stuff which is defined inside them) and some exports (stuff which can be easily accessed from outside the package).
To define a package, you use this syntax:
my-package
[ [ OP sum product foo ]
[ IMM foo ]
| std
]pkg
The first line is the name of the package, the second and third line describe which definitions in which slots are exported and the fourth line contains all the packages this package uses, meaning that it can access everything the mentioned packages export. So, if another package should use `my-package` later, it would have access to the operations `sum`, `product` and `foo` and would also recognize `foo` as immediate if it also is defined to be immediate in `my-package` itself (this behavior may change later, so that an operation that is exported as immediate is just taken to be immediate).
To actually fill a package with content you need to open it, define the content
and then close it.
The new content will not be in the package until the package is closed again. I have no idea whether embedded packages are a thing currently. I suspect they are not and it is too late in the day to check now.
You can do it like this:
my-package
p( sum
[ 0 + ~ fold-stack &
]o
product
[ 1 * ~ fold-stack &
]o
foo ;[ not that this operation makes much sense, but it's an example, what do you expect? ];
[ dup-here & sum & << product & + &
]i
)p
Packages can also be added to the use list of the current package with `import`.
I personally don't recommend using it, as it may hide package uses somewhere they are not expected but I assume such an operation could have its uses and some programmers may prefer it that way.
another-package
[ [ OP bar ]
| std ;[ Not importing `std` will lead to a completely empty environment in the defined package, meaning that you can do nothing it it.
That means you also cannot leave the package anymore.
Always include `std` into this list unless you want to import something else here which provides a replacement for the absolute essentials.
];
]pkg
another-package
p( my-package import
...
)p
Is exactly equivalent to
another-package
[ [ OP bar ]
| std my-package
]pkg
another-package
p( ...
)p
Some people may prefer to not use packages at all (for whatever strange reason). These people still will need some way of accessing what packages of other people export. For them there's the immediate `@` operation. It basically does an import of the provided package's exports into the current dictionary on the path.
So, if you want to use what `my-package` exports without using packages, just do
my-package @
Note that `import` wouldn't work here, as it would try to add `my-package` to the uses-list of the current package, which would fail horribly when there is no current package in the first place. You are
not in the `std` package after loading the package library.
Next up on the library are modules (or load systems), I think. Always having to provide complete paths in the code being written instead of having some other system to do that kind of bookkeeping for you can be tedious, especially when your file organization is different from someone who wrote a library you want to use.
Modules would provide a way of giving a set of poslin files a name under which they can be found.
Next up on the interpreter front is not the programmable reader (that one can wait, especially since I'll need to rewrite the standard library again by including the definition for reading strings in there…) but bytes, byte streams and conversion of some object types to bytes.
I definitely want Poslin to be Unicode compatible, so UTF-8 and ASCII conversion or characters will be a requirement of poslin0.