Plan to throw one away
garethrees.org
garethrees.org
Tcl is now about as fast as a typical scripting language and its implementation is regarded as a model of high-quality readable code. I wonder if John Ousterhout (the creator of Tcl) knew he was building a rough first system that he would need to rewrite later, or he really thought it was the right way to do things at the time.
(To be fair, Tcl is a whole lot simpler to parse than JavaScript and Ousterhout is a good coder, so even the pre-8.0 versions were fast enough to be practical for everyday scripting tasks.)
Whatever he believed he would have convinced you he was right :-)
Of course, the language has matured and now it's also usable for building rich and complex apps top to bottom, just like any modern scripting language.
Fun fact: most modern JavaScript engines in browsers work this way today, though at a different level of granularity. Parsing slows down application startup, so many JS engines don't parse a function body until it's first called. This means you can have a syntax error in a function body and won't know if it's never used.
That's literally all it does. It tokenizes and counts braces.
In 1995 Netscape had the Netscape Enterprise Server[0] that ran javascript for server-side scripting. Actually, the two books I used, back in the day, to learn Javascript was the client- and the server-side javascript guides published by Netscape.
[0] https://en.wikipedia.org/wiki/Netscape_Enterprise_Server
Those two things aren't necessarily in conflict. Netscape's server wasn't exactly a roaring success. What really made Javascript-in-the-server work was a Javascript JIT engine (v8) that actually made Javascript very competitive compared to traditional server side scripting languages. So given that there did not exist a Javascript JIT engine back then, maybe it was too soon.
The early js-on-the-server systems had miserable ecosystems.
Rees never learned anything new from the first system (what could he learn anyway, what he described sounds like a nightmare), but proceeded to implement it from scratch.
Funny thing, I'm reading 'The Mythical Man-Month' right now :D
Ultimately it's a question of ego. Does the CTO welcome the new hire who knows more about this than he does, or does he insist he knows better? Does he feel threatened or slighted?
I best 9 out of 10 - this would of ended up badly.
1. Yes, you can ruin every metaphor if you over-analyze it enough.
2. No, software has its own "rust" scenarios, like not staying compatible with other ecosystem elements, or simply being different than the current best-practice.
The generated code is not so great if you have an event driven environment: I mean I want to push 50 bytes into the compiler, instead of having it request the next input character. This means it's not so great for a repl, unless some other pre-parser gives complete translation units to lex/yacc.
I think Yacc has a pretty primitive conflict resolution strategy: (pick shift unless otherwise indicated by precedence declarations). If code could resolve the conflict then you could handle a lot of newer syntax: for example, in "a-b" vs. "a -b", you could use the whitespace to distinguish between prefix (reduce) or infix (shift).
It would be nice (I know someone who has done this..) if there was an option to degenerate into using backtracking to resolve conflicts. It means try all possibilities (split the stack at each conflict) and keep only those results which produce an error free full parse. If there is more than one full result, only then do you have a real conflict.
I'm not sure if other parser generators are any better, but there are improvements which could be made.
This actually works quite well in [Happy](https://www.haskell.org/happy/), the Yacc-alike for Haskell, so it is not a fundamental limitation of Yacc-style tools. In Happy, it works by generating monadic code and using a monadic action for token reading, which you can then make event-driven.
For exmaple, Both Bison and Berkeley Yacc (the new one maintained by T. E. Dickey) support reentrant parsing, which works hand-in-glove with likewise support in GNU Flex.
A parser that whacks around global variables (like parser generated by classic Yacc) is going to be a nonstarter in any modern language which gives programs run-time and compile-time access to the parser (code can execute while parsing and call yyparse). Not to mention if there are threads.
Bison has support for "push parsers" which are state machines called for each token, rather than functions that retain control until they parse an entire unit:
http://www.gnu.org/software/bison/manual/html_node/Push-Decl...
Did you mean "free software"?
If you are looking for something more exotic or a very specific use-case, I'd say use something like Ragel[0].
It has a particularly sublime output mode for generating Graphviz dot files however.
Since JS is single-threaded, you can write a simple stop-the-world mark-sweep collector as a first iteration. (Simple reference counting probably isn't a good idea since cycles are common in JS.) Then you can move to incremental and/or generational if that's giving you annoying hitches. Only after that do you need to put concurrent on the table.
I believe even world class JS engines have only relatively recently started doing GC in another thread.
There are some things that they don't do so well. It takes hard work to get good error messages out of Yacc, and anything that you might prefer to solve using feedback between the parser and the lexer (such as JavaScript's use of newline to terminate a statement, but only if it makes syntactic sense) is awkward to do because of Lex's lookahead — the token you want to suppress has already been produced by the time you know whether you want to suppress it.
But its important not to get stuck worrying about minor issues like these when the critical task is to make something that works. You can always plan to throw away the Yacc-built parser and replace it with something better when you have time.
But for something even more fun, I suggest you check out Marpa (https://jeffreykegler.github.io/Marpa-web-site/). It is, by far, the nicest parsing tool I have ever worked with. Being able to parse ANY BNF is huge. And the state tables for debugging! Knowing what possibilities you have makes things way easier. Especially for getting stuck in an ambiguous spot of your grammer. Finally, using ruby slippers to get out of a tight spot is hugely useful.
My one complaint is that the only implementation is in Perl. The core engine is written in C though, so someday I would like to convert the Perl into Python. I started several months ago, and haven't been able to get back to it. (https://github.com/srathbun/pyMarpa)
"If you plan to throw one away, you will throw away two." -- Craig Zerouni
I can highly recommend mpc[1] if you do, it's a really amazing library for parser combinators in C. I was never much of a fan of yacc/lex (perhaps out of my ignorance of BNF). With yacc/lex I always felt like I had to have my grammar all planned out (which if you implement JS I suppose isn't a problem) before I could start developing and it was very hard to work incrementally. mpc is very flexible and since it's just a C library I find it a lot easier to iterate with.
Ha! They must have learned how to write an interpreter from Herbert Schildt:
http://www.drdobbs.com/cpp/building-your-own-c-interpreter/1... [1989]
In this piece of amusement, the Little C program is a giant null-terminated string, and the "instruction pointer" is of type char * . You get the picture.
> When I did come to write a garbage collector I used the mark-and-sweep algorithm. But something puzzled me, and I couldn’t find an answer in any of the textbooks I looked at: how was I supposed to schedule the collections? In a classic description of a garbage collector, you wait until memory runs out and then you collect the world. But this leads to bad behaviour on modern systems, because of swapping, and because other processes need memory too. You need to schedule collections well before memory is full. But when exactly? I still don’t know of a comprehensive solution to this problem.
In a nutshell, you let your run-time pretend that it's running in a small machine, until it is too close to huffing and puffing too hard and then you say "hey I lied, you're actually in a bigger machine: have some breathing room". This rubbery constraint keeps it behaving reasonably nicely, rather than "Wee, I have 4GB of free RAM to stomp over with strings and cons cells before ever calling GC!"
What you have to do is pick some heap size (that is typically substantially smaller than the amount of RAM). You let the GC whack against this artificial threshold, and if that gets too excessive, according to some measure, you increase it. E.g. if after a full GC you have less than some fudge threshold free, call the OS for more memory to integrate into the object heap.
The threshold is calculated in some way that the image doesn't have to execute numerous frequent GC's before it triggers the request for more space (it doesn't have to whack too hard and wastefully against the artificial limit).
Also, ephemeral GC will help, and ephemeral GC can have its own threshold against frequent ephemeral GC's. When not enough space is liberated by ephemeral, you schedule a full. Then if that doesn't liberate enough space, add more. Since ephemeral is fast (doesn't scan the full heap), you can work a bit closer to the heap limit (since you can suffer frequent ephemeral GC's better than frequent full GC's).
And, of course, the parameters controlling these behaviors are exposed in some way so users can tweak them. Command line arguments, env vars, local config file, system config file, run time global variable/API, ...
But then again, shell script is in a different class of language I'd say.
#!/bin/bash
COUNTER=0
addmore() {
echo hi $COUNTER
((COUNTER++))
echo "addmore" >>$0
sleep 1
}
addmoreHowever, the stream pointer isn't its instruction pointer in the script. A backwards branch (such as the end of a while loop, or a call to a function defined earlier) does not rewind the stream to that piece of text.
> But then again, shell script is in a different class of language I'd say.
Not much different in this regard from how, say, Common Lisp (load "file.lisp") processes the top-level forms in the file.
(defvar *counter* 0)
(defun addmore ()
(format t "hi ~s~%" (incf *counter*))
(with-open-file (f "addmore.lisp" :direction :output :if-exists :append)
(write-line "(addmore)" f)))
$ clisp addmore.lisp
hi 1
WARNING: OPEN: #<INPUT BUFFERED FILE-STREAM CHARACTER #P"addmore.lisp" @8>
already points to file "/home/kaz/test/addmore.lisp", opening the
file again for :OUTPUT may produce unexpected results
Open the file anyway
hi 2
WARNING: OPEN: #<INPUT BUFFERED FILE-STREAM CHARACTER #P"addmore.lisp" @9>
already points to file "/home/kaz/test/addmore.lisp", opening the
file again for :OUTPUT may produce unexpected results
Open the file anyway
hi 3
WARNING: OPEN: #<INPUT BUFFERED FILE-STREAM CHARACTER #P"addmore.lisp" @10>
already points to file "/home/kaz/test/addmore.lisp", opening the
file again for :OUTPUT may produce unexpected results
Open the file anyway
hi 4
WARNING: OPEN: #<INPUT BUFFERED FILE-STREAM CHARACTER #P"addmore.lisp" @11>
already points to file "/home/kaz/test/addmore.lisp", opening the
file again for :OUTPUT may produce unexpected results
Open the file anyway
hi 5Cheat: use Hans Bhoem's conservative collector for C/C++: http://www.hboehm.info/gc/
In other words, no.
If the company was properly managed they also wouldn't have been in a situation where there's only two weeks left and a critical component is in a not well understood state.
In practice though these kinds of things constantly happen in even well managed companies. If the author weren't around they'd probably just have shipped a slow Javascript interpreter that couldn't do prototypes very well.
Just curious, what kind of programming technique renders the idea of this article obsolete?
https://en.wikipedia.org/wiki/Ship_of_Theseus
You can replace all the parts of your crappy v1.0 code and still call it the same code base. That's what the smart ones go around doing quietly.
The author is already screwed before he even has a chance to start.
Please don't be uncharitably dismissive. The author didn't throw away the parser because it was recursive descent—in fact he explicitly excludes that as a reason.
(The rest of your comment is fine.)
To me his justification sounded pretty convincing, though not to you, which is totally fair. As far as HN commenting goes it's just a matter of explaining your view in a substantive way.
(I haven't used the above personally, but the Golang parser does.)
Personally I have used PEG parsers and I was able to provide great error messages, by using the "cut" operator in the appropriate places in the grammar (see comments earlier in the thread I linked above).