shades of the IOCCC and the original Bourne-shell a-spectering here.
It was just a bit of fun. I've never used it.
I thought the original pipe operators were '^'.
I'm very interested in Unix history, I read about this in the past week or so. It was intended I suppose, to work exactly like all other shell direction. Except the >
on the right would indicate "write to stdout" or the equivalent, since otherwise the previous >
would imply "write to this file" instead of "write to stdin on this program". All context was based on the extra >
.
Speaking of which, from what I have seen of your language, it's looks like strictly left-associative with no precedence. Am I right?
Completely.
Not at all, I'll grumble, but, I'll go along with what the language mandates. awk is 1-indexed, for instance.
I hadn't considered that, but yes I suppose it is! My LISPer friend even compared it to awk (he's far too kind).
However, having your language be 1-indexed and the underlying extension language 0-indexed is, to me, a very jagged split. It's something I would avoid.
That's sound design advice. But the language is designed about doing what is considered "optimal" at least by the project's philosophy first, affecting at most about 100 features-- I mean 100 is arbitrary but it's also a 10x10 grid. I used to have a big poster with the entire language on it, just for fun.
Python for example, does split
and join
in a way that I find really obnoxious (I'm confident it wasn't arbitrary) but here, it works this way:
p "Hello there everybody" | split p " " | join p " "
I'm really not a fan of the way that Python reverses one of these. So I fixed it to show it could be done. I have a strong preference as well. But I don't take making "improvements" like that lightly, and I do try to avoid them.
It is assumed that most users will not bother using Python, but if they do they will have to learn how it works. I think it's a useful lesson to learn about 0-vs-1 indexing, but a lesson that can be safely put off until after the early stuff.
The goal isn't to teach everything in the language, it's to teach the concepts and then if someone wants to play with the language, actually have a non-useless set of features. Full mastery is considered possible for someone without previous experience.
Another thing is the 100 or so "commands" (like PSET?) you mention. My preference, here also, tends towards minimalism: Few keywords, rest pushed out into libraries.
Yes, though this was modeled on BASIC which has such commands up front. Strictly speaking, there are only two graphics commands in this language which actually produce graphics; line and pset.
Interfacing with libraries is a useful feature (to say the least) but not a core feature. Why make libraries for a language with only 100 commands, when you've already added a way to interface with Python libraries?
I mean any subset of those libraries is going to be vastly inferior to the existing set, and any "native" way of bringing import
into the language is only going to save the first step; that native feature would then be several times more complex than any other feature, which defeats the purpose of making it "native" at all. import
truly might as well be done with inline Python then.
My feeling is that if you're ready for import or anything like it-- you're probably ready for Python, and have fun. By then they may not wish to use the extension at all. By then they may wish to simply put it aside and learn Python, not use it as an extension, but we are nice enough to offer a choice. By that time they may have found that the shell provides whatever they need.
Think of it like the first stage in a rocket. You can have a single-stage rocket, but most people are going to drop the first stage and move forward.
It's intended to make the ride along the first stage as smooth as possible. There are (deliberately) few instances of it straying far from the implementation language design, changing indexing or parameter order or any naughty things like that. They're only done when it's considered worth the inconsistency. Obviously that comes down to opinion-- as a rule, I would say yours is the good one.
On the contrary, unless you're a language guru and can hand-roll recursive descent parsers correctly , parser-generators are the only guarantee that you're not shooting yourself in the foot.
If I do manage to hit my foot here, it's only with a nerf dart.
I don't dispute the advantages that you get from doing it the way you (and indeed the industry) considers "correct"-- it is less error prone, it is more testable from a comp-sci perspective, I mean if you want proofs, if you want to chase efficiency, the advantages you named are indeed features of doing it "correctly".
But if anybody ever extended the syntax of the language considerably (at which point the smart thing to do would be to rewrite the parser "correctly") then you would have every right to say "I told you so". In that context, you would be 100% right. Perhaps the thing to compare this to is the "Seasoned professional" here: http://www.ariel.com.au/jokes/The_Evolution_of_a_Programmer.html
I'm deliberately taking the "First year in college" approach. I have reasons for doing this other than laziness, and in a way my justification is just as circular as "you should do it correctly so that it's correct".
I wanted to make a language that was so ridiculously simple, someone inexperienced could learn to write a parser for it (and understand the most fundamental ideas behind parsing text) even before they ever cared (or truly need to care) about parser generators.
As a result, someone has already (hand)written a better parser, but they use it in their own implementation of their own very simple language. Perhaps I'd feel bad, except that for that sort of hobby project, handwritten parsers are extremely common. I'm not lowering the bar, I'm simply not raising it.
Otherwise, you could end up with a parser which cannot parse valid inputs as defined in their own grammar. This is bad not only for the user, but, the guy who implemented the language as well.
The biggest problem I've found with it (there's a workaround, and it wasn't a problem in the design originally) is that I bolted on syntax checking later in the game. I'm really pretty happy with it, except for the part that checks for references to unused (otherwise non-existent) identifiers.
This is imperfectly (but effectively) solved with a simple workaround, it's a single line with two tokens when you want to call a function that hasn't yet been defined.
It could also be solved more automatically with more granular checking (ignore errors for UDFs) or it could be perfectly and ideally solved with another transpiler pass (There are two; it could possibly be integrated in the first pass). It simply isn't enough of a bother to fix it so far. I would call it the biggest flaw so far, but it was introduced relatively recently. And it is fixable pretty easily without changing the language:
- function a is defined
- function a calls function b <- error happens here
- function b is defined
- function b calls function a <- no problem
Compiling that may give you an error. Alright:
- b = 0
- function a is defined
- function a calls function b <- no problem
- function b is defined
- function b calls function a <- no problem
So, is it:
- Elegant? No.
- Simple? Yes.
- Entirely and elegantly fixable? Yes!
- Worth it? Ehh... updates are very infreqent.
- Less trouble to fix than writing a more formal parser? Yes! Depending on how often you do that already.
Would making the parser more formal make this easier to fix? Not necessarily, but either way it's more trouble.
I likely can't formally prove the parser works because it isn't formally specified. But such formality befits a language with so many more users, not a simple toy used to do what is essentially tutoring.
It's not even recursive. I mean you can have recursion in the program you're writing for it, but the parser doesn't need it. It's very, very simple.
The other problem I have with bespoke languages is their lack of diagnostics. The most they do is, you know, "syntax error at line n; foo unexpected". Producing proper diagnostics, (nay, suggestions for improvement, even, nowadays) are a heck of a lot of work in itself.
If a truly serious debugging tool is needed, it's probably better to run it on the Python code that it outputs.
I know I risk sounding arrogant, but you're making arguments for formality and precision; I'm only trying to tell you that it would be overengineering. If one of us is foolish about this, we both know it's not you. But I am arguing why it's neither-- which is to say I'm defending the choice of a sub-par parser.
I could actually get someone to replace it with a better (more formal) one. It simply wouldn't change anything in real terms. At best, it would be like adding a third (inline) wheel to a bicycle.
You can see why I'm no language designer, though I've written plenty of parsers.
And I am confident your parsers are better, I only dispute that a better parser is truly necessary for this application. I don't dispute that they are as good as necessary in a typical application. For now, I will insist that there are applications where there is no serious or practical need for a formally defined parser, and this is almost certainly one of those.
I picked up the tarball from sunsite.unc.edu--if you remember that site.
It comes up in things I've read, it seems to redirect to ibiblio.org now.
It's always possible I'm wrong. It's also worth mentioning that I've thought about these sorts of questions for years, and I thought about them even before other people brought them up as feedback. Which isn't to say I haven't gotten anything from the feedback, but a lot of it is stuff I expected to hear when I was making these decisions.
They were very deliberate, they were thought over at length, they considered the practicality of typical and formal approaches, which I could have gone to the effort of if necessary.
There were iterations of this, but not as many as are even typical for this sort of thing. I'd done toy languages and toy parsers before, but this is the first one that was useful enough for me to actually do real work with. Only incidentally, because if it didn't I would have simply used something else.
All it does is compile to Python. You can't run that code without instantly gaining all the features Python has, for better or worse. The worst thing it can do is output broken Python code, but a user can do that anyway if that's the goal. Maybe that sounds like passing the buck, though I think of it more like having Python as a second line of defence.