notes-computer-programming-programmingLanguageDesign-prosAndCons-infernoShell

http://debu.gs/entries/inferno-part-1-shell

" (cur i) = $i splits the first item out of the list $i, assigning it to $cur, and then assigns the rest to $i. I don’t know of an equivalent in bash that is as simple, but it behaves roughly like the following assignment in Ruby: cur, *i = i. "

--

"

apply is roughly equivalent to for, but without an explicit variable and with the block preceding the arguments to apply the block to. You can use one to simulate the other, in fact:

for i in 1 2 3 { {echo $1} $i } apply { i = $1; {echo $i} } 1 2 3 "

--

" The looping constructs (for, while, and apply) act as expected:

; for i in `{ns} {echo $i

; i = Hello, World!; while {! ~ $#i 0} {(cur i) = $i; echo $cur} ; apply {man $1 intro} 1 2 3 4 5 6 7 8 9 10
sed 's/.* '}sort uniq

"

--

" In the for example, the backtick followed by braces (`{}) does what a pair of backticks or $() do in bash: substitutes the output of a command into the arguments for another command. "{} is similar, but does no parsing of tokens; that is, the output is substituted as a single string. Try changing the backtick to a double quote and you will see what I mean. "

--

"

; fn greet { name = $1 if {~ $#name 0} { name = World } echo Hello, $name! } ; greet Hello, World! ; greet everybody Hello, everybody! ; whatis greet load std; fn greet {name=$1;if {~ $#name 0} {name=World};echo Hello, $name^!}

You can, like any other command, capture its output:

; greeting = "{greet} ; echo The greeting is '"'^$greeting^'"' The greeting is "Hello, World! "

The carats (^) are a join operator of sorts; they are used to separate tokens without inserting a space. The only quoting operator for literal strings is the humble tick (or “single-quote”, but anyway it’s this guy: '). Since the double-quote is used elsewhere in the syntax of the shell, you’ll need to escape it where it is used.

But that’s a little clunky if the function’s main purpose is to generate a string for you to keep around, and this is where substitution functions come in. Substitution functions are declared similarly to functions but are called differently (with ${}) and their purpose is to be substituted into a command rather than to act as standalone commands:

; subfn greeting { name=$1 and {~ $#name 0} {name=World} result = Hello, $name! } ; echo ${greeting} Hello, World! ; echo ${greeting Charles Forsyth} Hello, Charles! ; echo ${greeting 'Charles Forsyth'} Hello, Charles Forsyth!

There are a few substitution functions built into the shell and several that come from the std library.

Functions and substitution functions are stored as environment variables and are available to subshells as a result. Functions get the “fn-” prefix, and substitution functions get “sfn-”. And since /env is a filesystem:

; cat /env/fn-greet '{name=$1;if {~ $#name 0} {name=World};echo Hello, $name^!}' "

--

" The small bug: n is an environment variable, so primestr and isprime will be sharing the same n! Only by luck have we avoided a bug here. Ouch. Luckily, blocks can have local variables:

; n = 5; { n = 6 }; echo $n 6

n = 5; { n
= 6 }; echo $n 5 "

--

" ; fn isprime { n = $1 rescue composite {status false} { i = 2 while {ntest ${expr $i $n '<'}} { ! ntest ${expr $n $i %} && raise composite i = ${expr $i 1 +} } } } ; subfn primestr { n = $1 result = $n is composite isprime $n && result = $n is prime } ; echo ${primestr 20} 20 is composite ; echo ${primestr 23} 23 is prime "

--

"

Mathematical expressions in the shell are taken care of by means of the expr(1) and mpexpr(1)‌ libraries for 64-bit integer math and infinite-precision integer math, respectively. They both provide the same interface: a substitution builtin ${expr ...} and a regular builtin function ntest which returns false for zero and true otherwise. ${expr} gives you a simple, stack-based calculator that anyone familiar with dc(1)‌ or Forth or HP calculators should be comfortable with. It misses some stack manipulation operations but is otherwise a standard RPN math language: ..

; subfn factorial { (n r) := $1 1 and {! ~ $n 0} {r = ${expr ${factorial ${expr $n 1 -}} $n '*'}} result = $r } ; echo ${factorial 15} 1307674368000 ; echo ${factorial 21} -4249290049419214848

Oops. 21! is a 66-bit number. But no problem!

; unload expr ; load mpexpr ; echo ${factorial 21} 51090942171709440000 "

--

"

; cr = "{os awk 'BEGIN{printf("\r")}'}

You could also write a brief Limbo program that prints a single \r if you don’t want to use os(1)‌ or start the window manager. os, as you may have suspected (or read if you clicked the link to the man page), runs a command on the host OS. There are some caveats but it does roughly what you expect for the most part. "

--

--

--

upvote

rogpeppe 499 days ago

link

all inferno modules are written in limbo, which has a safe memory model, so modules can't access data they're not given.

check out some of the inferno papers, and the limbo language in particular, which is an elegant predecessor of Go, including some things that Go still does not have - the dynamic module loading being the most marked example, and the main reason i haven't yet ported the inferno shell to Go.


upvote

1337p337 498 days ago

link

I'd rather see Go ported to Inferno. :)


upvote

rogpeppe1 497 days ago

link

To make Inferno run Go efficiently, you'd probably need to add instructions to the Dis VM to do interfaces, channel closing, slice capacities and likely other things I haven't thought of.

To make Go run well under Inferno, you'd need to change the language to allow dynamically loaded modules (can we use the type compatibility model when we can't link everything together at once?).

These issues are doable, but even if you've done that, the performance trade-offs when writing a program to run in a VM are quite different from running natively - in particular, much idiomatic Go code would probably be dog slow under Inferno, because we're not steaming along at native code rates.

All that assumes we'd translate Go to Dis though. There may be alternative routes.

--

upvote

1337p337 499 days ago

link

I'm planning a couple of more articles on this topic, so your point is of interest to me. Since it's all content I have absorbed by using the shell, some things that seem obvious to me might have slipped through the cracks when I wrote the post. It seems from your comment like #1 was answered (how it differs from existing shells). The problems with existing shells (byzantine quoting rules, general opacity in terms of being able to use them from the outside, and crufty, non-uniform syntax, lack of simple FD manipulation, lack of modern facilities like blocks, scoping, etc.) were sort of taken as a given, and the examples should have shown how they are obviated. What is it you'd like to see explained?

--

upvote

rogpeppe 499 days ago

link

"This article says that the shell uses lists rather than strings for some things. Great, does that mean this shell deals with spaces and newlines in filenames better than bash?"

absolutely. that was rc's main innovation IMO.

--