My name is Jared Clarke.

I'm a former in-home caregiver turned self-taught software developer and graphic designer, fluent in HTML, CSS, SVG, and JavaScript. Literate in Go and Scheme. This website is my portfolio, resumé, and blog.

You can find more of my work on social media:


Calculator

output
input
operators
operands

Tools:

  • Pratt Parser
  • Document Object Model (DOM)

Project:

December , I resolved to teach myself how to program. This calculator — which I built with plain HTML, CSS, and JavaScript — was the culmination of careful study. September , I rewrote this application to better reflect my improving skillset.

Both calculators are built on top of expression parsers. My first parser was rudimentary — easily misled by unexpected character combinations and misplaced whitespace. Anticipating malformed user input is complex, and I had shifted much of this complexity over to a tangle of regular expressions, which were cryptic and hard to debug. These too would fail.

My new calculator's core is a top-down operator precedence parser modeled on ideas pioneered by the computer scientist Vaughan R. Pratt. A full-featured parser, it handles infix and prefix notation, left and right associativity, nested and mismatched parenthetical groupings, multiple levels of precedence and all types of whitespace. Errors are made explicit through robust error handling and a formatted user interface.

A version of this calculator application, complete with extensive inline documentation, can be found on GitHub. I also implemented a version of my calculator's core parser with a complementary big-number library. Its high precision calculations proved too computationally expensive and its output size too unwieldy for my calculator UI. This also can be found on Github.

Notes

Top-down operator precedence parsing, as imagined by Vaughan Pratt, combines lexical semantics with functions. Each lexeme is assigned a function — its semantic code. To parse a string of lexemes is to execute the semantic code of each lexeme in turn from left to right.

There are two types of semantic code:

  1. nud: a lexeme function without a left expression — null denotation.
  2. led: a lexeme function with a left expression — left denotation.

The engine of Pratt's technique, parse_expression drives the parser, calling the semantic code of each lexeme in turn from left to right. For every level of precedence — dictated by position and binding power — there is a call to parse_expression either through the nud or led parser of the associated lexeme. The resolution of parse_expression is to return either an evaluated expression or an error token.

function parse_expression(rbp) {
    const token = state.next();
    const [nud, ok] = table.get_parser(token.type, "nud");
    if (!ok) {
        token.message += constants.NOT_PREFIX;
        return [null, token];
    }
    let [x, error] = nud(token);
    if (error !== null) {
        return [null, error];
    }
    while (rbp < table.get_binding(state.peek())) {
        const token = state.next();
        const [led, ok] = table.get_parser(token.type, "led");
        if (!ok) {
            token.message += constants.NOT_INFIX;
            return [null, token];
        }
        [x, error] = led(x, token);
        if (error !== null) {
            return [null, error];
        }
    }
    return [x, null];
}
The engine of Pratt's parsing technique.

Parallax Animation

A landscape painting of Mount Rainier. It is comprised of three PNG layers and one JPEG layer. Each can be animated to create a parallax effect.

Tools:

  • CSS animations
  • CSS perspective
  • CSS grid
  • Intersection Observer API

Project:

Summer , I discovered a short film about the Walt Disney Company's pioneering work with the multi-plane camera — a device used to create parallax backgrounds in animated films. I was inspired to recreate this technology in CSS and JavaScript.

I painted a mountain landscape in my raster editor and then saved its four layers as separate image files. I saved the three foreground layers — one tree layer and two hill layers — as PNGs to preserve transparency. I saved the opaque background image, Mount Rainier, as a JPEG. Noisy images, like digital paintings, compress better as lossy images.

I used grid-template-areas in CSS grid to layer my images, and I used CSS perspective to insert a z-index of 50 pixels within their containing element. I applied CSS animations to move my layers independently through this 50-pixel space. The movement of layers at different speeds creates the illusion of depth — just like a multi-plane camera.

Notes:

I attached an Intersection Observer to my animation. If the user either forgets or chooses not to turn off the animation before scrolling to the next section in my portfolio, the observer will switch it off for them. Although CSS animations usually run on a separate thread, I don't want to waste any browser resources.


Code: Luka.js

// Produces unary functions.
function unary(operation) {
    return Object.freeze(function (x) {
        return operation(x);
    });
}

// Produces binary functions.
function binary(operation) {
    return Object.freeze(function (x, y) {
        return operation(x, y);
    });
}

// Produces functions that fold a set into a summary value.
function foldable(operation) {
    return Object.freeze(function (...xs) {
        return xs.reduce(
            (total, x) => operation(total, x),
        );
    });
}

// "op" acts as namespace for arithmetic functions.
// Its null prototype prevents namespace pollution
// from inherited objects.
const op = Object.create(null);

// Unary Operations
op.neg = unary((x) => 0 - x);

// Binary Operations
op.add = foldable((x, y) => x + y);
op.sub = foldable((x, y) => x - y);
op.mul = foldable((x, y) => x * y);
op.div = foldable((x, y) => x / y);
op.exp = binary((x, y) => Math.pow(x, y));
op.rem = binary((x, y) => x % y);

// Make exported namespace immutable.
export default Object.freeze(op);
Luka: a JavaScript module that provides functional replacements for a handful of arithmetic operators.

Tools:

  • Array.prototype.reduce()
  • Object.create()
  • Object.freeze()

A binary operation is a rule combining two elements of a set to create a third. In arithmetic the most common way to represent binary operations is infix notation, where the operator sits between its two operands. But infix notation is ambiguous. It's unclear whether 1 + 2 * 3 evaluates (1 + 2) * 3 or 1 + (2 * 3), so mathematicians use a set of conventions, the order of operations, to resolve this confusion.

The order of operations, however, is no fixed thing. It's unclear whether unary negation precedes exponentiation, whether implied multiplication precedes explicit division. Almost every programming language has their own precedence table. Each is subtly — or drastically — different from the other.

I created the JavaScript library Luka.js to sidestep this confusion over binary operations. Luka.js provides functional replacements for a handful of arithmetic operators: [+, -, *, /, **, %]. Unlike its operators, JavaScript functions are straightforward. They are parenthesized and strictly applied, meaning they evaluate inward to outward precisely when they are called. The ambiguous 1 + 2 * 3 becomes the explicit add(1, mul(2, 3)). Also: JavaScript functions can have variable arity. While + can input only two arguments, add can input 1 through n arguments.

Notes:

  • The entire Luka.js API is contained within a null-inheriting, immutable object literal. The object keeps the API simple. All functions are kept within a single, unchangeable namespace. No pollution from the prototypal chain can obscure them.
  • JavaScript functions are also objects. By default, properties — even other functions — can be attached to a function object at anytime during the life of a program, potentially changing its ouptut. My unary, binary, and foldable factory functions produce immutable function objects to prevent this behavior from altering my API.