ECMAchine
Lisp-based in-browser toy operating system
Install / Use
/learn @AlexNisnevich/ECMAchineREADME
ECMAchine
ECMAchine is an in-browser Scheme REPL that is also a toy operating system. It has a virtual filesystem that is accessed through Unix-like commands, as well as a rudimentary process management system.
Why? I made ECMAchine for a few reasons. For one, it's an interesting experiment in what a filesystem based around S-expressions could look like. It was also a good exercise in writing an interpreter and finally applying all those things I learned in SICP. Most importantly, though, ECMAchine is simply a lot of fun. I've spent countless hours playing around with it and coming up with little programs for it, and hopefully by the end of this tutorial you, the reader, will be able to get a sense for how fun it is to work with ECMAchine.
This tutorial will walk you through the file and process management features of ECMAchine, and then show a bunch of examples of cool things that you can do with them. It is aimed at people with at least a little bit of experience with Scheme or another Lisp dialect.
Table of Contents
1. The REPL
2. Introspection
3. The File System
3.1. Folders
3.2. Files
3.3. Other File System Commands
4. Review: Higher-Order Functions
4.1. Map
4.2. Filter
5. A Few More Functions
5.1. js-apply
5.2. length
5.3. sort
5.4. time
5.5. newline
6. Scripts
6.1. Special Types of Scripts
7. Processes
7.1. Overlays
7.2. Example: A Simple Clock
7.3. Process Management
7.4. Process Performance
8. Recipes
8.1. File System Recipes
8.1.1. Directory Cleanup
8.1.2. File/Folder Size
8.1.3. File Search
8.2. Process Manipulation Recipes
8.2.1. Process Cleanup
8.2.2. A Simple Task Manager
8.3. Miscellaneous Recipes
8.3.1. Analog Clock
8.3.2. Other Ideas
9. ed, a Simple Text Editor
9.1. Introducing ed
9.2. Design Overview
9.3. Helper Functions
9.4. The Skeleton of ed
9.5. Displaying the Editor Contents
9.6. File Commands
9.7. Edit Commands
9.8. Cool Things
9.8.1. Map
9.8.2. Run
9.8.3. And more
9.9. Putting It All Together
10. What's Next?
11. Acknowledgements
<a name="therepl"></a>
1. The REPL
ECMAchine supports the expected Scheme arithmetic commands, list-processing commands, and control structures:
ecmachine:/ guest$ (+ 1 2 3 4)
10
ecmachine:/ guest$ (- (/ 12 3) 2)
2
ecmachine:/ guest$ (cons 'a '(b c))
(a b c)
ecmachine:/ guest$ (list 1 2 (list 3 4))
(1 2 (3 4))
ecmachine:/ guest$ (car '(1 2 3))
1
ecmachine:/ guest$ (cdr '(1 2 3))
(2 3)
ecmachine:/ guest$ (if (= 5 3) 'a 'b)
b
ecmachine:/ guest$ (cond
.. ((> 5 0) 'positive)
.. ((< 5 0) 'negative)
.. (#t 'zero))
positive
Of course there are closures, definitions, and binding constructs:
ecmachine:/ guest$ (lambda (arg) (+ arg 1))
(λ (arg) (+ arg 1))
ecmachine:/ guest$ ((lambda (arg) (+ arg 1)) 5)
6
ecmachine:/ guest$ (define (square x) (* x x))
ecmachine:/ guest$ (square 5)
25
ecmachine:/ guest$ (begin
.. (define x 5)
.. x)
5
ecmachine:/ guest$ (let ((x 2) (y 3))
.. (* x y))
6
And we can do fun things like recursively computing Fibonacci numbers:
ecmachine:/ guest$ (define fib
.. (lambda (n)
.. (cond
.. ((or (= n 0) (= n 1)) 1)
.. (#t (+ (fib (- n 1)) (fib (- n 2)))))))
ecmachine:/ guest$ (fib 10)
89
But we already know about Lisp. What makes ECMAchine different?
<a name="introspection"></a>
2. Introspection
You can view all of currently defined variables in the global environment with the environment command.
ecmachine:/ guest$ (environment)
(!= * + - / < <= = > >= __fileSystem abs and append cadr car cd cdr cons cp dir? do-nothing else environment exec fact file? filter help inspect-primitive intersperse js-apply kill kill-all length
list ls map math mkdir mv new newline nil not null? or overlay path peek perfmon.header perfmon.perfInfo performance processes read rm save size smile sort start sum time)
Let's take a look at the absolute value function abs in order to see the three levels of functions in ECMAchine:
ecmachine:/ guest$ abs
(λ (x) (cond ((> x 0) x) (#t (- x))))
ecmachine:/ guest$ -
#<Function ->
ecmachine:/ guest$ (inspect-primitive -)
function (args) {
if (args.length == 1) {
return (- args[0]);
} else {
return (args[0] - args[1]);
}
}
ecmachine:/ guest$ cond
[USER]: Eval Error: Unbound variable cond
When we type in abs into the evaluator we get to see the underlying Scheme representation, so we know that abs is defined as a Scheme function. (It's actually defined in /startup/math.lsp and thus loaded at startup, but more on that later.) We can see that (abs x) simply outputs x if x > 0 and -x otherwise, but then let's say we wanted to dig a little deeper and figure out how some of the functions used in this definition are themselves implemented.
When we type - into the evaluator we get #<Function ->, which means that - is a primitive function for ECMAchine. We can then use the inspect-primitive function to view the (read-only) underlying JavaScript representation of -. Finally, when we type cond into the evaluator we get an Unbound variable error, signifying that cond is not actually a function at all but a language construct.
On the topic of introspection, we can even examine the contents of running processes with the peek command (more on processes later). For instance, here we're examining the source code of a currently-running clock application:
ecmachine:/ guest$ (processes)
((-1 Terminal) (0 clock.app) (1 processmonitor.app) (2 memorymonitor.app))
ecmachine:/ guest$ (peek 0)
(overlay (time (list 'h ': 'm ': 's)) -30 -30 'clock)
<a name="thefilesystem"></a>
3. The File System
<a name="folders"></a>
3.1. Folders
The file system is navigated using the cd and ls functions. The preferred method for concatenating file/directory names into paths is with the path function, as shown below:
ecmachine:/ guest$ (ls)
(apps cleanup.s readme.txt startup usr)
ecmachine:/ guest$ (ls 'apps)
(clock.app memorymonitor.app processmonitor.app unixclock.app virushunter.app)
ecmachine:/ guest$ (cd 'usr)
ecmachine:/usr guest$ (ls)
()
ecmachine:/usr guest$ (mkdir 'usr2)
(Directory /usr/usr2 created)
ecmachine:/usr guest$ (ls)
(usr2)
ecmachine:/usr guest$ (cd '..)
ecmachine:/ guest$ (path 'usr 'usr2)
usr/usr2
ecmachine:/ guest$ (cd (path 'usr 'usr2))
ecmachine:/usr/usr2 guest$
<a name="files"></a>
3.2. Files
The read function is used to read the contents of a file, while save and append create a new file and append data to an existing file, respectively.
ecmachine:/ guest$ (ls)
(apps cleanup.s readme.txt startup usr)
ecmachine:/ guest$ (read 'readme.txt)
ECMAchine by Alex Nisnevich. Thanks to Jakub Jankiewicz for his jQuery Terminal plugin (distributed under LGPL).For more information check out the git repo at https://github.com/AlexNisnevich/ECM
Achine
ecmachine:/ guest$ (save 'blah.txt '(I am using ECMachine!))
(Saved file /blah.txt)
ecmachine:/ guest$ (read 'blah.txt)
(I am using ECMachine!)
ecmachine:/ guest$ (append 'blah.txt '(Hooray!))
(Updated file /blah.txt)
ecmachine:/ guest$ (read 'blah.txt)
(I am using ECMachine!)
(Hooray!)
<a name="otherfilesystemcommands"></a>
3.3. Other File System Commands
You can move, copy, and delete files and directories with the mv, cp, and rm commands, respectively.
The predicates file? and dir? can be used to find if a path points to a file, directory, or neither.
<a name="review:higher-orderfunctions"></a>
4. Review: Higher-Order Functions
Before we continue into scripts and processes, let's define two functions that we'll be using a lot later: map and filter. (In fact, these functions are so important that in ECMAchine, they're both defined in /startup/mapreduce.lsp and loaded at startup.)
<a name="map"></a>
4.1. Map
map takes another function and maps it to a list, executing it for every element of the list and combining the results into a new list:
ecmachine:/ guest$ (define (map proc items)
.. (if (null? items)
.. nil
.. (cons (proc (car items))
.. (map proc (cdr items)))))
ecmachine:/ guest$ (map abs '(1 -3 5 -6 0))
(1 3 5 6 0)
ecmachine:/ guest$ (map length '(hello lisp))
(5 4)
<a name="filter"></a>
4.2. Filter
filter takes a predicate (that is, a function that returns a boolean value) and filters all the elements of a list that satisfy the predicate:
ecmachine:/ guest$ (define (filter pred seq)
.. (cond ((null? seq) nil)
.. ((pred (car seq))
.. (cons (car seq)
.. (filter pred (cdr seq))))
.. (#t (filter pred (cdr seq)))))
ecmachine:/ guest$ (filter (lambda (x) (> x 5)) '(3 7 2 0 9 15))
(7 9 15)
ecmachine:/ guest$ (filter (lambda (x) (= (length x) 3)) '(the blue cat))
(the cat)
<a name="afewmorefunctions"></a>
5. A Few More Functions
We're almost at the fun part of the tutorial, but before we get there, I should b
