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.
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:
nud: a lexeme function
without a left expression — null denotation.
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.
Parallax Animation
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
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.