CINXE.COM
Making a Brainfuck interpreter in JavaScript - Part 1 - 30 seconds of code
<!DOCTYPE html><html lang="en"> <head><title>Making a Brainfuck interpreter in JavaScript - Part 1 - 30 seconds of code</title><meta name="description" content="Continuing on the code interpretation path, I'm attempting to build a Brainfuck interpreter, using an AST to represent and execute the code."><meta name="viewport" content="width=device-width, initial-scale=1"><meta charset="utf-8"><meta property="og:title" content="Making a Brainfuck interpreter in JavaScript - Part 1 - 30 seconds of code"><meta property="og:description" content="Continuing on the code interpretation path, I'm attempting to build a Brainfuck interpreter, using an AST to represent and execute the code."><meta property="og:type" content="article"><meta property="og:image" content="https://www.30secondsofcode.org/assets/cover/lake-bench-800.webp"><meta name="twitter:card" content="summary_large_image"><script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","url":"https://www.30secondsofcode.org/js/s/brainfuck-interpreter-part-1","mainEntityOfPage":{"@type":"WebPage","@id":"https://www.30secondsofcode.org/js/s/brainfuck-interpreter-part-1"},"name":"Making a Brainfuck interpreter in JavaScript - Part 1","headline":"Making a Brainfuck interpreter in JavaScript - Part 1","description":"Continuing on the code interpretation path, I'm attempting to build a Brainfuck interpreter, using an AST to represent and execute the code.","image":"https://www.30secondsofcode.org/assets/cover/lake-bench-400.webp","datePublished":"2025-03-01","dateModified":"2025-03-01","publisher":{"@type":"Person","name":"Angelos Chalaris","url":"https://chalarangelo.me"}}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"https://www.30secondsofcode.org/","name":"Home"}},{"@type":"ListItem","position":2,"item":{"@id":"https://www.30secondsofcode.org/js/p/1","name":"JavaScript"}},{"@type":"ListItem","position":3,"item":{"@id":"https://www.30secondsofcode.org/js/algorithm/p/1","name":"Algorithms"}},{"@type":"ListItem","position":4,"item":{"@id":"https://www.30secondsofcode.org/js/s/brainfuck-interpreter-part-1","name":"Brainfuck interpreter - Part 1"}}]}</script><link rel="sitemap" href="/sitemap.xml" type="application/xml"><link rel="alternate" href="/feed" type="application/rss+xml" title="30secondsofcode.org"><link rel="preload" type="font/woff2" href="/assets/Inter.var.woff2" as="font" crossorigin><link rel="preload" type="font/woff2" href="/assets/RobotoMono-Regular.woff2" as="font" crossorigin><link rel="icon" href="/assets/icons/favicon-32x32.png?v=30swp20231218115417" type="image/png"><link rel="stylesheet" href="/assets/print.css?v=30swp20231218115417" media="print"><link rel="manifest" href="/manifest.webmanifest" crossorigin><meta name="theme-color" content="#07071c"><link rel="icon" sizes="192x192" href="/assets/icons/icon-192x192.png?v=30swp20231218115417"><link rel="apple-touch-icon" href="/assets/icons/icon-180x180.png?v=30swp20231218115417"><link rel="canonical" href="https://www.30secondsofcode.org/js/s/brainfuck-interpreter-part-1"><script>(function(){const timestamp = "195e32c3a0c"; })();</script><style>:root{--color-code-highlight-white: hsl(210, 17%, 82%);--color-code-highlight-red: hsl(4, 87%, 68%);--color-code-highlight-gray: hsl(212, 9%, 58%);--color-code-highlight-green: hsl(125, 69%, 70%);--color-code-highlight-blue: hsl(208, 100%, 74%);--color-code-highlight-indigo: hsl(207, 100%, 82%);--color-code-highlight-purple: hsl(269, 100%, 83%);--color-code-highlight-brown: hsl(28, 100%, 67%)}.token:is(.comment,.prolog,.cdata){color:var(--color-code-highlight-gray)}.token:is(.doctype,.punctuation,.entity){color:var(--color-code-highlight-white)}.token:is(.attr-name,.class-name,.boolean,.constant,.number,.atrule){color:var(--color-code-highlight-blue)}.token:is(.keyword){color:var(--color-code-highlight-indigo)}.token:is(.property,.tag,.symbol,.deleted,.important){color:var(--color-code-highlight-green)}.token:is(.selector,.string,.char,.builtin,.inserted,.regex,.attr-value),.token.attr-value>.token.punctuation{color:var(--color-code-highlight-blue)}.token:is(.variable,.operator,.function){color:var(--color-code-highlight-indigo)}.token.url{color:var(--color-code-highlight-green)}[data-code-language]{color:var(--color-code-highlight-white)}.language-html .token.doctype>.token.doctype-tag{color:var(--color-code-highlight-green)}.language-html .token.doctype>.token.name{color:var(--color-code-highlight-purple)}.language-html .token.tag>.token.attr-name{color:var(--color-code-highlight-purple)}.language-html .token.attr-value>.token.punctuation.attr-equals,.language-html .token.special-attr>.token.attr-value>.token.value.css{color:var(--color-code-highlight-white)}.language-html .token.entity{color:var(--color-code-highlight-blue)}.language-css .token.atrule>.token.rule{color:var(--color-code-highlight-red)}.language-css .token:is(.unit,.important){color:var(--color-code-highlight-red)}.language-css .token.selector{color:var(--color-code-highlight-green)}.language-css .token.selector>.token:is(.class,.pseudo-class,.pseudo-element){color:var(--color-code-highlight-purple)}.language-css .token.selector>.token.id{color:var(--color-code-highlight-indigo)}.language-css .token.selector>.token.attribute>.token.operator{color:var(--color-code-highlight-red)}.language-css .token.property{color:var(--color-code-highlight-blue)}.language-css .token.variable{color:var(--color-code-highlight-brown)}.language-css .token.hexcode.color[style*=--hex-color]:before{display:inline-block;content:"";height:var(--font-xs);width:var(--font-xs);border-radius:var(--border-radius-medium);border:var(--border-width-hairline) solid var(--color-border-light);background-color:var(--hex-color);line-height:0;transform:translate(-2px,1px)}:is(.language-js,.language-jsx) .token.keyword{color:var(--color-code-highlight-red)}:is(.language-js,.language-jsx) .token:is(.operator,.constant,.boolean,.number,.atrule){color:var(--color-code-highlight-blue)}:is(.language-js,.language-jsx) .token:is(.function,.function-variable){color:var(--color-code-highlight-purple)}:is(.language-js,.language-jsx) .token:is(.attr-name,.class-name,.parameter){color:var(--color-code-highlight-brown)}:is(.language-js,.language-jsx) .token.regex>.token.regex-delimiter{color:var(--color-code-highlight-indigo)}:is(.language-js,.language-jsx) .token.regex>.token.regex-flags{color:var(--color-code-highlight-red)}:is(.language-js,.language-jsx) .token.template-string{color:var(--color-code-highlight-indigo)}:is(.language-js,.language-jsx) .token.interpolation>.token.interpolation-punctuation{color:var(--color-code-highlight-red)}:is(.language-js,.language-jsx) .token.keyword.nil{color:var(--color-code-highlight-blue)}.language-jsx .token.punctuation{color:var(--color-code-highlight-blue)}.language-jsx .token.attr-name{color:var(--color-code-highlight-purple)}.language-jsx .token.class-name{color:var(--color-code-highlight-green)}.language-jsx .token.string{color:var(--color-code-highlight-indigo)}.language-jsx .token.attr-value>.token.punctuation.attr-equals{color:var(--color-code-highlight-red)}.language-jsx .token.script>.token.script-punctuation{color:var(--color-code-highlight-red)}.language-json .token.operator{color:var(--color-code-highlight-white)}.language-json .token.null.keyword{color:var(--color-code-highlight-blue)}.language-py .token.keyword{color:var(--color-code-highlight-red)}.language-py .token:is(.function,.decorator){color:var(--color-code-highlight-purple)}.language-py .token.class-name{color:var(--color-code-highlight-white)}.language-shell .token.keyword{color:var(--color-code-highlight-red)}.language-shell .token.variable{color:var(--color-code-highlight-white)}.language-shell .token:is(.function,.parameter){color:var(--color-code-highlight-purple)}.language-editorconfig .token.section{color:var(--color-code-highlight-green)}.language-editorconfig .token.attr-name{color:var(--color-code-highlight-red)}.language-editorconfig .token.attr-value>.token.punctuation{color:var(--color-code-highlight-white)}.language-diff .token.coord{color:var(--color-code-highlight-purple);font-weight:var(--font-weight-medium)}.language-diff .token.diff{color:var(--color-code-highlight-brown)}.language-diff .token.deleted{color:var(--color-code-highlight-red)}.language-diff .token.inserted{color:var(--color-code-highlight-green)} @charset "UTF-8";@layer framework{@font-face{font-family:Inter;font-weight:100 1000;font-display:swap;src:local("Inter Var"),local("Inter Variable"),local("Inter-Variable"),url(/assets/Inter.var.woff2) format("woff2 supports variations"),url(/assets/Inter.var.woff2) format("woff2-variations")}@font-face{font-family:Roboto Mono;font-style:normal;font-weight:400;font-display:swap;src:local("Roboto Mono"),local("RobotoMono-Regular"),url(/assets/RobotoMono-Regular.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Roboto Mono;font-style:italic;font-weight:400;font-display:swap;src:local("Roboto Mono Italic"),local("RobotoMono-Italic"),url(/assets/RobotoMono-Italic.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Roboto Mono;font-style:normal;font-weight:500;src:local("Roboto Mono Medium"),local("RobotoMono-Medium"),url(/assets/RobotoMono-Medium.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;font-display:swap}:root{--color-background: hsl(240, 61%, 7%);--color-background-light: hsl(240, 39%, 14%);--color-text: hsl(240, 10%, 90%);--color-text-light: hsl(240, 10%, 80%);--color-text-lighter: hsl(240, 10%, 70%);--color-primary: hsl(217, 98%, 66%);--color-primary-light: hsl(217, 98%, 77%);--color-warning: hsl(29, 91%, 53%);--color-caution: hsl(0, 98%, 64%);--color-code-background: hsl(225, 42%, 16%);--color-border: hsl(235, 25%, 27%);--color-border-light: hsl(235, 15%, 41%);--color-scrollbar-knob: hsl(240, 35%, 17%);--color-scrollbar-knob-active: hsl(240, 30%, 23%);--color-selection-background: hsl(230, 98%, 58%);--font-xs: .8125rem;--font-sm: 1rem;--font-md: 1.125rem;--font-lg: 1.3125rem;--font-xl: 1.625rem;--font-x2: 2rem;--font-x3: 2.25rem;--font-weight-normal: 400;--font-weight-medium: 500;--font-weight-strong: 600;--line-height-loose: 1.75;--line-height-normal: 1.5;--line-height-tight: 1.25;--spacing-1: 2px;--spacing-2: 4px;--spacing-4: 8px;--spacing-6: 12px;--spacing-8: 16px;--spacing-10: 20px;--spacing-12: 24px;--spacing-16: 32px;--spacing-20: 40px;--spacing-24: 48px;--border-radius-small: 2px;--border-radius-medium: 4px;--border-radius-large: 8px;--border-width-hairline: .5px;--border-width-thin: 1px;--border-width-medium: 2px;--border-width-thick: 4px;--animation-duration-short: .2s;--animation-duration-medium: .3s;--animation-duration-long: .45s}@media (prefers-reduced-motion: reduce){:root{--animation-duration-short: .15s;--animation-duration-medium: .15s;--animation-duration-long: .15s}}*,*:before,*:after{box-sizing:border-box;border-width:0;border-style:solid}*{margin:0}html,body{height:100%}html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}body{margin:0;background:var(--color-background);color:var(--color-text);-webkit-font-smoothing:antialiased}[hidden]{display:none}:focus-visible{outline-color:var(--color-primary)}::selection{background-color:var(--color-selection-background);color:var(--color-text)}::backdrop{--color-dialog-backdrop: hsla(0, 0%, 0%, .6);background:var(--color-dialog-backdrop)}html{font-family:Inter,Helvetica,sans-serif}body{line-height:var(--line-height-loose);font-weight:var(--font-weight-normal)}strong{font-weight:var(--font-weight-strong)}em{font-variation-settings:"slnt" -10;font-style:normal}h1,h2,h3,h4{font-weight:var(--font-weight-strong);overflow-wrap:break-word;hyphens:auto;text-wrap:pretty}h1,h2,h3{line-height:var(--line-height-tight)}h4,h5,h6{line-height:var(--line-height-normal)}h1{font-size:clamp(var(--font-x2),3vw + 1rem,var(--font-x3))}h2{font-size:clamp(var(--font-xl),2vw + 1rem,var(--font-x2))}h3{font-size:clamp(var(--font-lg),1vw + 1rem,var(--font-xl))}h4{font-size:var(--font-lg)}h5,h6{font-size:var(--font-md)}main>article>:is(h2,h3,h4){margin-block:calc(1.5 * var(--layout-row-spacing));margin-block-end:calc(.5 * var(--layout-row-spacing))}small{font-size:80%}sub{bottom:-.25em}sup{top:-.5em}sup,sub,code,kbd{line-height:0;position:relative;vertical-align:baseline}p,ul,ol,table,blockquote{font-size:var(--font-md)}p,li{text-wrap:pretty}ol,ul{list-style:none;padding:0}main>article :is(ol,ul){list-style:revert;padding-inline-start:var(--spacing-10)}main>article :is(ol,ul) li{padding-block:0 var(--spacing-2);margin-block:var(--spacing-2)}blockquote{background:var(--color-background-light);padding-inline-start:calc(var(--layout-bleed-width) - var(--border-width-thick));padding-inline-end:var(--layout-bleed-width);padding-block:var(--spacing-6);border-inline-start:var(--border-width-thick) solid var(--color-primary);font-variation-settings:"slnt" -5;display:flex;flex-direction:column;row-gap:calc(var(--layout-row-spacing) / 3)}figure.admonition{background:var(--color-background-light);padding-inline-start:calc(var(--layout-bleed-width) - var(--border-width-thick));padding-inline-end:var(--layout-bleed-width);padding-block:var(--spacing-6);border-inline-start:var(--border-width-thick) solid var(--admonition_-accent-color);display:flex;flex-direction:column;row-gap:calc(var(--layout-row-spacing) / 3)}figure.admonition[data-admonition-type=note],figure.admonition[data-admonition-type=tip]{--admonition_-accent-color: var(--color-primary-light)}figure.admonition[data-admonition-type=important]{--admonition_-accent-color: var(--color-primary)}figure.admonition[data-admonition-type=warning]{--admonition_-accent-color: var(--color-warning)}figure.admonition[data-admonition-type=caution]{--admonition_-accent-color: var(--color-caution)}figure.admonition figcaption{color:var(--admonition_-accent-color);font-weight:var(--font-weight-medium);font-size:var(--font-md);white-space:pre-wrap}dfn{font-style:normal;text-decoration:underline var(--color-border-light) dotted;text-underline-offset:4.5px}:not(:is(code,kbd,pre)){font-feature-settings:"frac","cv05","cv11","calt","kern"}hr{height:0;color:var(--color-border-light);border-block-start-width:var(--border-width-thin)}a{--link_color-underline: var(--color-primary);box-shadow:0 1px 0 var(--link_color-underline);transition:box-shadow var(--animation-duration-medium) ease}a:any-link{text-decoration:none;color:inherit}@media (hover: hover){a:is(:hover,:focus){box-shadow:0 2px 0 var(--link_color-underline)}}a:has(>code){-webkit-box-decoration-break:clone;box-decoration-break:clone}a[data-code-reference=true]{border-radius:var(--border-radius-small)}:is(h2,h3,h4)>a[id]{--link_color-underline: transparent;--link_hash_opacity: 0;position:relative}:is(h2,h3,h4)>a[id]:before{position:absolute;content:"📎";font-size:.5em;text-align:center;font-weight:var(--font-weight-medium);top:.25px;height:100%;line-height:2.5em;left:calc(-1 * var(--layout-bleed-width));width:var(--layout-bleed-width);color:var(--color-primary-light);opacity:var(--link_hash_opacity);transition:opacity var(--animation-duration-short) ease-out}@media (hover: hover){:is(h2,h3,h4)>a[id]:is(:hover,:focus){--link_hash_opacity: 1}}:is(h2,h3,h4)>a[id]:target{--link_hash_opacity: 1}a[data-skip-link]{--link_color-underline: transparent;position:absolute;overflow:hidden;clip-path:polygon(0 0,0 0,0 0,0 0);height:1px;width:1px;margin:-1px;padding:0;border:0}a[data-skip-link]:focus{clip-path:none;padding:var(--spacing-6) var(--spacing-8);font-size:var(--font-md);z-index:1000;width:auto;height:auto;left:50%;transform:translate(-50%)}:is(pre,code,kbd){font-family:Roboto Mono,Menlo,Consolas,monospace;font-size:var(--font-sm);background:var(--color-code-background);letter-spacing:-.205px}:is(pre,code,kbd) *{font-family:inherit}code,kbd{-webkit-box-decoration-break:clone;box-decoration-break:clone;padding:var(--spacing-1) var(--spacing-2);border-radius:var(--border-radius-small)}:not(:is(h1,h2,h3,h4))>a>code{padding-block-end:0;border-bottom-left-radius:0;border-bottom-right-radius:0}:is(h1,h2,h3,h4) code{font-size:.875em;font-weight:var(--font-weight-medium);padding-inline:var(--spacing-4);padding-block:0}pre{position:relative;padding:var(--spacing-12) var(--layout-bleed-width);overflow:auto;white-space:pre-wrap;-webkit-hyphens:none;hyphens:none;tab-size:2;line-height:var(--line-height-normal)}pre::-webkit-scrollbar{display:none}pre[data-code-language],pre[data-code-title]{padding-block-start:var(--spacing-24)}pre[data-code-language]:before,pre[data-code-title]:before{position:absolute;display:block;top:var(--spacing-8);left:var(--layout-bleed-width);font-family:Inter,Helvetica,sans-serif;font-size:var(--font-sm);font-weight:var(--font-weight-strong);content:attr(data-code-language)}pre[data-code-language][data-code-title]:before{content:attr(data-code-title) " (" attr(data-code-language) ")"}pre:not(data-code-language)[data-code-title]:before{content:attr(data-code-title)}main>article>pre:has(+pre){border-bottom-left-radius:0;border-bottom-right-radius:0}main>article>pre+pre{margin-block-start:calc(-1 * var(--layout-row-spacing, 0));border-top-left-radius:0;border-top-right-radius:0}button,input,select,textarea{font-family:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;padding:0}button{-webkit-appearance:button;background-color:transparent;background-image:none;cursor:pointer}:disabled{cursor:default}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}[type=search]::-webkit-input-placeholder{font-family:Inter,Helvetica,sans-serif}[type=search]::-moz-placeholder{font-family:Inter,Helvetica,sans-serif}input,input::placeholder,textarea::placeholder{color:inherit}textarea{resize:vertical}label{cursor:pointer}table{border-spacing:0;border-collapse:collapse;width:100%}th{font-weight:var(--font-weight-strong)}td,th{text-align:center;border-width:var(--border-width-thin);border-color:var(--color-border-light);padding:var(--table_spacing, var(--spacing-2))}.table-wrapper{width:100%;overflow-x:auto;overflow-y:hidden;--scrollbar_size: 8px;--table_spacing: var(--spacing-2);border-width:var(--border-width-medium);border-color:var(--color-border-light)}.table-wrapper table{clip-path:polygon(var(--table_spacing) var(--table_spacing),calc(100% - var(--table_spacing)) var(--table_spacing),calc(100% - var(--table_spacing)) calc(100% - var(--table_spacing)),var(--table_spacing) calc(100% - var(--table_spacing)))}::-webkit-scrollbar{background-color:transparent;width:var(--scrollbar_size, 8px);height:var(--scrollbar_size, 8px)}::-webkit-scrollbar-track{margin:var(--scrollbar_margin, 0)}::-webkit-scrollbar-thumb{background-color:var(--scrollbar_color-knob, var(--color-scrollbar-knob));border-radius:var(--scrollbar_size);border:var(--scrollbar_border, none)}@media (hover: hover){::-webkit-scrollbar-thumb:hover{background-color:var(-scrollbar_color-knob-active, var(--color-scrollbar-knob-active))}}body{scrollbar-gutter:stable both-edges;--scrollbar_size: 12px;--scrollbar_border: 2px solid var(--color-background)}body[data-scroll-lock=true]{overflow-y:hidden}img,picture,video,canvas,svg{display:block;max-width:100%;height:auto}main>article>h1+img{width:100%;aspect-ratio:2;object-fit:cover}@media (hover: hover) and (prefers-reduced-motion: no-preference){main>article>h1+img{transition:filter 1.5s ease;transition-delay:1.5s}main>article>h1+img:hover{filter:saturate(200%)}}html{--layout-main-column-width: 800px;--layout-bleed-width: 24px;--layout-row-spacing: var(--spacing-12);--layout-area-spacing: calc(2 * var(--layout-row-spacing));--layout-border-radius: var(--border-radius-medium);scroll-padding-top:var(--layout-row-spacing)}@media (max-width: 50rem){html{--layout-border-radius: 0;--layout-row-spacing: var(--spacing-8)}}body{display:grid;grid-template-columns:minmax(0,1fr) minmax(auto,var(--layout-main-column-width)) minmax(0,1fr);grid-template-areas:"header header header" "left center right" "footer footer footer";row-gap:var(--layout-area-spacing)}main{grid-area:center}main>article{display:grid;grid-template-columns:var(--layout-bleed-width) 1fr var(--layout-bleed-width);row-gap:var(--layout-row-spacing)}main>article>*{grid-column:2}main>article>pre,main>article>blockquote,main>article>img,main>article>figure,main>article>details{grid-column:1/-1;border-radius:var(--layout-border-radius)}[data-area-gap]{padding-block-end:var(--layout-area-spacing)}.codepen-wrapper{width:100%}.codepen-wrapper>p{background:var(--color-background-light);padding-inline:var(--layout-bleed-width);padding-block:var(--spacing-6);border-radius:var(--layout-border-radius);font-variation-settings:"slnt" -5}.codepen-wrapper>div{height:clamp(300px,50vh,600px)}.codepen-wrapper>div,.codepen-wrapper>div>iframe{border-radius:var(--layout-border-radius)}.sparkles{display:inline-block;position:relative}@media (prefers-reduced-motion: no-preference){.sparkles:before,.sparkles:after{content:"✨";position:absolute;top:.5em;font-size:.5em;line-height:1;animation:flicker 2.5s 3 forwards ease-in-out,travel-vertical 5s 3 forwards ease-in-out}.sparkles:before{left:-.5em}.sparkles:after{right:-.5em;animation-delay:1s,3.75s;opacity:0}}@keyframes flicker{0%{transform:scale(.95);opacity:0}50%{transform:scale(1);opacity:.95}to{transform:scale(.95);opacity:0}}@keyframes travel-vertical{0%{transform:translateY(1.5em)}49%{transform:translateY(1.5em)}50%{transform:translateY(0)}99%{transform:translateY(0)}to{transform:translateY(1.5em)}}.wave{cursor:default;display:inline-block;transform-origin:75% 80%;will-change:transform}@media (hover: hover) and (prefers-reduced-motion: no-preference){.wave:is(:hover,:focus){animation:wave var(--animation-duration-long) infinite alternate ease-in-out}}@keyframes wave{0%{transform:rotate(-5deg)}to{transform:rotate(15deg)}}nav[aria-label=Collections]{display:grid;grid-template-columns:var(--layout-bleed-width) 1fr var(--layout-bleed-width);margin-block-end:var(--layout-area-spacing)}nav[aria-label=Collections] ul{grid-column:2;gap:var(--spacing-8);display:flex;flex-wrap:wrap}@media (max-width: 35rem){nav[aria-label=Collections] ul{overflow-x:auto;flex-wrap:nowrap;grid-column:1/-1;padding-inline:var(--layout-bleed-width);padding-block-end:var(--spacing-4);--scrollbar_size: 2.5px;--scrollbar_border: none;--scrollbar_margin: 0 var(--spacing-24)}}nav[aria-label=Collections] li{flex:none}nav[aria-label=Collections] a{--link_color-underline: transparent;display:inline-flex;column-gap:var(--spacing-2);padding:var(--spacing-6) var(--spacing-8);border-radius:var(--border-radius-medium);background:var(--color-background-light);line-height:var(--line-height-tight);font-weight:var(--font-weight-medium);will-change:transform;transition:color var(--animation-duration-medium) ease,transform var(--animation-duration-medium) ease}nav[aria-label=Collections] a:is([data-selected=true]){color:var(--color-primary)}@media (hover: hover){nav[aria-label=Collections] a:not(:is([data-selected=true])):is(:hover,:focus){color:var(--color-primary)}}@media (hover: hover) and (prefers-reduced-motion: no-preference){nav[aria-label=Collections] a:not(:is([data-selected=true])):is(:hover,:focus){transform:translateY(-2px);box-shadow:0 2px 0 transparent}nav[aria-label=Collections] a:not(:is([data-selected=true])):is(:hover,:focus) svg{animation:arrow-pull .75s ease 2}}@keyframes arrow-pull{0%{transform:translate(0)}50%{transform:translate(2px)}to{transform:translate(0)}}header{grid-area:header;display:grid;grid-template-columns:minmax(var(--layout-bleed-width),1fr) repeat(2,min(var(--layout-main-column-width) / 2,50% - var(--layout-bleed-width))) minmax(var(--layout-bleed-width),1fr);padding-block:var(--spacing-10)}header h1{grid-column:2;justify-self:start}header h1 img{width:8.75rem}header nav{grid-column:3;display:flex;align-items:center;justify-content:flex-end;color:var(--color-text-lighter)}header nav :is(a,button){--link_color-underline: transparent;display:inline-block;flex-shrink:0;padding:var(--spacing-6);border-radius:var(--border-radius-small);transition:color var(--animation-duration-medium) ease}header nav :is(a,button) svg{transition:scale var(--animation-duration-short) ease;transition-delay:.1s}@media (hover: hover){header nav :is(a,button):is(:hover,:focus){color:var(--color-text)}}@media (hover: hover) and (prefers-reduced-motion: no-preference){header nav :is(a,button):is(:hover,:focus) svg{scale:1.2;transition-delay:0ms}}@media (max-width: 25rem){header nav a{position:absolute;overflow:hidden;clip-path:polygon(0 0,0 0,0 0,0 0);height:1px;width:1px;margin:-1px;padding:0;border:0}}header nav[data-nav-action-container]{--nav-action-opacity: 0;--nav-action-transform-y: 70%}header nav [data-nav-action]:before{display:block;position:absolute;content:var(--nav-action-hotkey);opacity:var(--nav-action-opacity);font-family:Roboto Mono,Menlo,Consolas,monospace;font-size:var(--font-xs);background:var(--color-code-background);border-radius:var(--border-radius-small);width:1rem;height:1rem;line-height:1rem;text-align:center;border:var(--border-width-hairline) solid var(--color-border-light);border-bottom-width:var(--border-width-thin);transform:translate(10%,var(--nav-action-transform-y));transition:opacity var(--animation-duration-short) ease,transform var(--animation-duration-short) ease}header nav [data-nav-action]:hover:before{--nav-action-opacity: 0;--nav-action-transform-y: 150%}header nav [data-nav-action=home]{--nav-action-hotkey: "H"}header nav [data-nav-action=collections]{--nav-action-hotkey: "J"}header nav [data-nav-action=search]{--nav-action-hotkey: "K"}@media (pointer: fine) and (scripting: enabled) and (hover: hover){header nav[data-nav-action-container][data-nav-actions-shown]{--nav-action-opacity: 1;--nav-action-transform-y: 85%}}footer{grid-area:footer;display:grid;grid-template-columns:minmax(var(--layout-bleed-width),1fr) min((var(--layout-main-column-width)),100% - 2 * var(--layout-bleed-width)) minmax(var(--layout-bleed-width),1fr);padding-block:var(--spacing-8) var(--spacing-20)}footer div{grid-column:2;display:flex;flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:var(--spacing-16) var(--spacing-24);padding-block-start:var(--spacing-12);border-block-start:var(--border-width-hairline) solid var(--color-border)}footer p{flex-basis:20rem;flex-grow:1;max-width:25rem;text-wrap:pretty}footer nav{display:flex;flex-direction:column;row-gap:var(--spacing-6);justify-content:space-between;flex-grow:.25;--footer_sitemap-column-count: 2}@media (max-width: 40rem){footer nav{--footer_sitemap-column-count: 3;flex-grow:0}}footer p,footer ul{font-size:var(--font-sm)}footer ul{columns:var(--footer_sitemap-column-count);column-gap:var(--spacing-12)}footer a{--link_color-underline: transparent;--link_color-hover: var(--color-primary);--link_animation-duration: var(--animation-duration-medium);transition:color var(--link_animation-duration) ease}@media (hover: hover){footer a:is(:hover,:focus){color:var(--link_color-hover)}}footer small{flex-basis:100%;font-size:var(--font-xs);color:var(--color-text-lighter);text-align:center}footer small a{--link_color-hover: var(--color-text);--link_animation-duration: var(--animation-duration-short)}nav[aria-label=Breadcrumb]{display:grid;grid-template-columns:var(--layout-bleed-width) 1fr var(--layout-bleed-width);--breadcrumb_column-gap: var(--spacing-2)}nav[aria-label=Breadcrumb] ol{grid-column:2;display:flex;color:var(--color-text-light);column-gap:var(--breadcrumb_column-gap);font-size:var(--font-sm);flex-wrap:wrap;margin-block-end:var(--spacing-8)}@media (hover: hover){nav[aria-label=Breadcrumb] ol:has(a:is(:hover,:focus)){color:var(--color-text-lighter)}}nav[aria-label=Breadcrumb] li{display:flex;column-gap:var(--breadcrumb_column-gap)}nav[aria-label=Breadcrumb] li:has([aria-current=page]){position:absolute;overflow:hidden;clip-path:polygon(0 0,0 0,0 0,0 0);height:1px;width:1px;margin:-1px;padding:0;border:0}@media (max-width: 25rem){nav[aria-label=Breadcrumb] li:first-child{position:absolute;overflow:hidden;clip-path:polygon(0 0,0 0,0 0,0 0);height:1px;width:1px;margin:-1px;padding:0;border:0}}nav[aria-label=Breadcrumb] svg{transition:color var(--animation-duration-medium) ease}nav[aria-label=Breadcrumb] a{--link_color-underline: transparent;transition:color var(--animation-duration-medium) ease}@media (hover: hover){nav[aria-label=Breadcrumb] a:is(:hover,:focus){color:var(--color-text)}}.hero{display:flex;column-gap:var(--spacing-20);row-gap:var(--spacing-12);align-items:center;padding-inline:var(--layout-bleed-width);margin-block-end:var(--layout-area-spacing)}.hero h1,.hero h2{margin-block-end:var(--spacing-8)}.hero p{text-wrap:balance}.hero p+p{margin-block-start:var(--spacing-4)}.hero img{border-radius:var(--border-radius-medium);height:240px;aspect-ratio:1;object-fit:cover}@media (hover: hover) and (prefers-reduced-motion: no-preference){.hero img{transition:filter 1.5s ease;transition-delay:1.5s}.hero img:hover{filter:saturate(200%)}}.hero footer{display:block;padding-block:var(--spacing-8) 0;color:var(--color-text-lighter);font-size:var(--font-sm)}.hero footer>a{--link_color-underline: transparent;display:inline-block;transition:color var(--animation-duration-short) ease}@media (hover: hover){.hero footer>a:is(:hover,:focus){color:var(--color-text)}}@media (max-width: 40rem){.hero{flex-direction:column}.hero img{order:-1}.hero h1,.hero h2,.hero p{text-align:center}}.preview-list{display:grid;grid-template-columns:var(--layout-bleed-width) 1fr var(--layout-bleed-width);margin-block-start:var(--layout-row-spacing)}.preview-list>*{grid-column:2}.preview-list h2{margin-block-end:var(--spacing-16)}.preview-list ul{display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));align-items:start;column-gap:var(--spacing-16);row-gap:var(--spacing-24)}.preview-list:has(nav)>ul{margin-block-end:var(--layout-area-spacing)}.preview-list li{position:relative;container:preview/inline-size;display:flex;flex-wrap:wrap;row-gap:var(--spacing-8);column-gap:var(--spacing-12);align-items:center}.preview-list li a:before{content:"";position:absolute;inset:0}@media (hover: hover){.preview-list li:has(a:is(:hover,:focus)) h3{color:var(--color-primary-light)}}@media (hover: hover) and (prefers-reduced-motion: no-preference){.preview-list li:has(a:is(:hover,:focus)) img{filter:brightness(110%) saturate(125%);transform:scale(1.01)}}.preview-list article{display:flex;flex-direction:column;row-gap:var(--spacing-4)}.preview-list img{border-radius:var(--border-radius-medium);aspect-ratio:2;object-fit:cover;will-change:transform;transition:filter var(--animation-duration-long) ease,transform var(--animation-duration-medium) ease}.preview-list[data-large-images=true] img{aspect-ratio:1.5}.preview-list[data-single-column=true] ul{grid-template-columns:1fr}.preview-list a{--link_color-underline: transparent}.preview-list small{color:var(--color-text-lighter)}.preview-list h3{font-weight:var(--font-weight-medium);transition:color var(--animation-duration-medium) ease}@container preview (min-width: 23rem){.preview-list img{width:30cqi;height:30cqi;aspect-ratio:1}.preview-list article{flex:1 1 0}.preview-list p{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden}}main>article>footer{grid-row:auto;display:block;padding:0;color:var(--color-text-lighter);margin-block-end:var(--layout-area-spacing)}main>article>footer p{font-size:var(--font-sm);text-align:right;max-width:unset}main>article>footer p time{display:inline-block}main>article>footer p>*:is(a,span){display:block}@media (min-width: 28rem){main>article>footer p>a{display:inline}main>article>footer p>a:before{content:"·";padding-inline-end:var(--spacing-2)}}@media (min-width: 38rem){main>article>footer p>span{display:inline}main>article>footer p>span:before{content:"·"}}main>article>footer a{--link_color-underline: transparent;display:inline-block;transition:color var(--animation-duration-short) ease}@media (hover: hover){main>article>footer a:is(:hover,:focus){color:var(--color-text)}}nav[aria-label=Pagination],figcaption[aria-label="Replay steps"]{--pagination_middle-column-width: 1fr;display:grid;grid-template-columns:1fr var(--pagination_middle-column-width) 1fr;gap:var(--spacing-8)}nav[aria-label=Pagination]>a,nav[aria-label=Pagination]>button,figcaption[aria-label="Replay steps"]>a,figcaption[aria-label="Replay steps"]>button{--link_color-underline: transparent;min-width:120px;border-radius:var(--border-radius-medium);font-weight:var(--font-weight-medium);padding:var(--spacing-6);line-height:1;background:var(--color-background-light);display:inline-flex;align-items:center;column-gap:var(--spacing-2);justify-content:center;justify-self:center;will-change:transform;transition:color var(--animation-duration-medium) ease}@media (hover: hover){nav[aria-label=Pagination]>a:is(:hover,:focus),nav[aria-label=Pagination]>button:is(:hover,:focus),figcaption[aria-label="Replay steps"]>a:is(:hover,:focus),figcaption[aria-label="Replay steps"]>button:is(:hover,:focus){color:var(--color-primary)}}@media (hover: hover) and (prefers-reduced-motion: no-preference){nav[aria-label=Pagination]>a:is(:hover,:focus)[rel=next] svg,nav[aria-label=Pagination]>button:is(:hover,:focus)[rel=next] svg,figcaption[aria-label="Replay steps"]>a:is(:hover,:focus)[rel=next] svg,figcaption[aria-label="Replay steps"]>button:is(:hover,:focus)[rel=next] svg{transform:translate(2px)}nav[aria-label=Pagination]>a:is(:hover,:focus)[rel=prev] svg,nav[aria-label=Pagination]>button:is(:hover,:focus)[rel=prev] svg,figcaption[aria-label="Replay steps"]>a:is(:hover,:focus)[rel=prev] svg,figcaption[aria-label="Replay steps"]>button:is(:hover,:focus)[rel=prev] svg{transform:translate(-2px)}}nav[aria-label=Pagination]>a[aria-disabled=true],nav[aria-label=Pagination]>button[aria-disabled=true],figcaption[aria-label="Replay steps"]>a[aria-disabled=true],figcaption[aria-label="Replay steps"]>button[aria-disabled=true]{color:var(--color-text-lighter);pointer-events:none}nav[aria-label=Pagination]>a[aria-disabled=true] svg,nav[aria-label=Pagination]>button[aria-disabled=true] svg,figcaption[aria-label="Replay steps"]>a[aria-disabled=true] svg,figcaption[aria-label="Replay steps"]>button[aria-disabled=true] svg{display:none}nav[aria-label=Pagination]>a[rel=prev],nav[aria-label=Pagination]>button[rel=prev],figcaption[aria-label="Replay steps"]>a[rel=prev],figcaption[aria-label="Replay steps"]>button[rel=prev]{grid-column:1}nav[aria-label=Pagination]>a[rel=prev]:not([aria-disabled=true]),nav[aria-label=Pagination]>button[rel=prev]:not([aria-disabled=true]),figcaption[aria-label="Replay steps"]>a[rel=prev]:not([aria-disabled=true]),figcaption[aria-label="Replay steps"]>button[rel=prev]:not([aria-disabled=true]){padding-inline-end:var(--spacing-10)}nav[aria-label=Pagination]>a[rel=next],nav[aria-label=Pagination]>button[rel=next],figcaption[aria-label="Replay steps"]>a[rel=next],figcaption[aria-label="Replay steps"]>button[rel=next]{grid-column:-2}nav[aria-label=Pagination] svg.icon,figcaption[aria-label="Replay steps"] svg.icon{display:inline;flex-shrink:0;transition:transform var(--animation-duration-medium) ease}nav[aria-label=Pagination] p,figcaption[aria-label="Replay steps"] p{font-size:var(--font-xs);color:var(--color-text-light);grid-column:2;grid-row:1;text-align:center;align-self:center;line-height:var(--line-height-normal)}nav[aria-label=Pagination] p a,figcaption[aria-label="Replay steps"] p a{--link_color-underline: transparent;transition:color var(--animation-duration-short) ease}@media (hover: hover){nav[aria-label=Pagination] p a:is(:hover,:focus),figcaption[aria-label="Replay steps"] p a:is(:hover,:focus){color:var(--color-text)}}nav[aria-label=Pagination] p a,nav[aria-label=Pagination] p span,figcaption[aria-label="Replay steps"] p a,figcaption[aria-label="Replay steps"] p span{display:inline-block}@media (max-width: 30rem){nav[aria-label=Pagination],figcaption[aria-label="Replay steps"]{--pagination_middle-column-width: 0}nav[aria-label=Pagination] a[rel=prev],figcaption[aria-label="Replay steps"] a[rel=prev]{justify-self:end}nav[aria-label=Pagination] a[rel=next],figcaption[aria-label="Replay steps"] a[rel=next]{justify-self:start}nav[aria-label=Pagination] p,figcaption[aria-label="Replay steps"] p{grid-column:1/-1;grid-row:2}}main>article+nav{margin-block-end:var(--layout-area-spacing)}figcaption[aria-label="Replay steps"]{margin-block-start:var(--spacing-8)}figcaption[aria-label="Replay steps"] p{font-size:var(--font-sm)}@media (max-width: 30rem){figcaption[aria-label="Replay steps"]{--pagination_middle-column-width: 1fr}figcaption[aria-label="Replay steps"] p{grid-column:2;grid-row:1}}dialog{padding:0;background:var(--color-background-light);color:var(--color-text);width:100%;max-width:600px;--search_margin-block-start: 200px;--search_margin-block-end: 200px;margin:var(--search_margin-block-start) auto var(--search_margin-block-end);border-radius:var(--border-radius-medium);--search_input-height: 60px}@media (max-width: 37.5rem){dialog{--search_margin-block-start: 100px;--search_margin-block-end: 0px;border-radius:0}}@media (prefers-reduced-motion: no-preference){dialog{animation:dialog-in var(--animation-duration-medium) ease-out}}dialog search{display:grid;grid-template-columns:var(--spacing-8) 1.25rem auto 2rem var(--spacing-8);grid-template-rows:var(--search_input-height) auto;align-items:center}dialog search>svg.icon:first-of-type{grid-column:2}dialog [type=search]{background:transparent;outline:0;font-size:var(--font-md);line-height:var(--line-height-normal);margin:var(--spacing-6) var(--spacing-4)}dialog [type=search]:focus~output{border-color:var(--color-primary-light)}dialog button{transition:color var(--animation-duration-short) ease}@media (hover: hover){dialog button:is(:hover,:focus){color:var(--color-primary)}}dialog output{grid-column:1/-1;padding:var(--spacing-8) calc(var(--spacing-8) - var(--scrollbar_size, 0)) var(--spacing-8) var(--spacing-8);max-height:calc(100vh - var(--search_margin-block-start) - var(--search_margin-block-end) - var(--search_input-height));border-block-start:var(--border-width-thin) solid var(--color-border);transition:border-color var(--animation-duration-medium) ease;overflow-x:auto;display:flex;flex-direction:column;row-gap:var(--spacing-4);--scrollbar_size: 8px;--scrollbar_border: 2px solid var(--color-background-light);--scrollbar_color-knob: var(--color-scrollbar-knob-active)}dialog output::-webkit-scrollbar-track{margin:var(--spacing-4) 0}dialog output h2{font-weight:var(--font-weight-medium);font-size:var(--font-sm);color:var(--color-text-light)}dialog output ul{display:flex;flex-direction:column;row-gap:var(--spacing-4)}dialog output ul:not(:last-child){margin-block-end:var(--spacing-8)}dialog output a{display:flex;align-items:baseline;justify-content:space-between;line-height:var(--line-height-normal);column-gap:var(--spacing-8);padding:var(--spacing-2) 0;--link_color-underline: transparent;transition:color var(--animation-duration-medium) ease}dialog output a small{font-size:var(--font-sm);color:var(--color-text-light)}@media (hover: hover){dialog output a:is(:hover,:focus){color:var(--color-primary-light)}dialog output a:is(:hover,:focus)>small{color:var(--color-primary-light)}}dialog output p{padding-block:var(--spacing-4);text-wrap:pretty}@keyframes dialog-in{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}nav[aria-label="Table of contents"]{grid-area:left;display:block;padding:112px var(--spacing-24) var(--spacing-20) var(--spacing-12)}@media (max-width: 83rem){nav[aria-label="Table of contents"]{display:none}}nav[aria-label="Table of contents"] div{position:sticky;top:var(--spacing-24);display:grid;grid-template-columns:minmax(0,1fr) minmax(auto,17.5rem);grid-template-rows:auto;row-gap:var(--spacing-10)}nav[aria-label="Table of contents"] div>*{grid-column:2}nav[aria-label="Table of contents"] h2{font-size:var(--font-xl);font-weight:var(--font-weight-medium);color:var(--color-text)}nav[aria-label="Table of contents"] ol{display:flex;flex-direction:column;gap:var(--spacing-4)}nav[aria-label="Table of contents"] ol ol{padding-inline-start:var(--spacing-6);padding-block:var(--spacing-2)}nav[aria-label="Table of contents"] li{font-size:var(--font-sm);line-height:var(--line-height-normal);color:var(--color-text-light);text-wrap:wrap}nav[aria-label="Table of contents"] a{--link_color-underline: transparent;transition:color var(--animation-duration-medium) ease}nav[aria-label="Table of contents"] a[aria-current=true]{color:var(--color-primary-light);-webkit-font-smoothing:subpixel-antialiased}@media (hover: hover){nav[aria-label="Table of contents"] a:is(:hover,:focus){color:var(--color-primary-light)}}nav[aria-label="Table of contents"] div>a{display:flex;align-items:center;column-gap:var(--spacing-2);width:fit-content;color:var(--color-text-light);border-radius:var(--border-radius-small);margin-block-start:calc(0px - var(--spacing-2))}@media (hover: hover) and (prefers-reduced-motion: no-preference){nav[aria-label="Table of contents"] div>a:is(:hover,:focus) svg{animation:arrow-rise .75s ease 2}}@keyframes arrow-rise{0%{transform:translateY(0)}50%{transform:translateY(-2px)}to{transform:translateY(0)}}details summary{display:flex;align-items:center;gap:var(--spacing-6);font-size:var(--font-md);font-weight:var(--font-weight-medium);cursor:pointer;transition:color var(--animation-duration-medium) ease}@media (hover: hover){details summary:is(:hover,:focus){color:var(--color-primary-light)}}details summary:before{display:inline-block;content:"▶";font-size:.75em;transition:transform var(--animation-duration-medium) ease}@media (max-width: 52rem){details summary{gap:var(--spacing-4)}details summary:before{margin-left:var(--spacing-6)}}details summary::marker{display:none;content:""}details summary::-webkit-details-marker{display:none}details>:not(summary){margin-inline:var(--layout-bleed-width)}@media (max-width: 52rem){details>:not(summary){margin-inline:0}}details>pre,details>blockquote,details>img,details>figure{border-radius:var(--layout-border-radius)}details[open] summary{margin-block-end:var(--spacing-4)}details[open] summary:before{transform:rotate(90deg)}.announcement{display:flex;column-gap:var(--spacing-20);row-gap:var(--spacing-12);align-items:center;justify-content:center;padding-inline:var(--layout-bleed-width);margin-block-end:var(--layout-row-spacing);margin-block-start:calc(-1 * (var(--layout-row-spacing) + var(--spacing-2)))}.announcement p{font-size:var(--font-sm);text-align:center}.announcement a{display:inline-block}} </style></head> <body> <a href="#start-of-content" data-skip-link>Skip to content</a> <header aria-label="Main"> <h1> <a title="30 seconds of code" aria-label="30 seconds of code" href="/"> <img fetchpriority="high" src="/assets/30s-logo.png" alt="Home" width="140" height="48"> </a> </h1> <nav data-nav-action-container> <a href="/" data-nav-action="home"> <svg width="1.25rem" height="1.25rem" viewBox="0 0 128 128" fill="none" title="Home" aria-label="Home" class="icon"><g clip-path="url(#a)" fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"><path d="M16 42.667A5.333 5.333 0 0 1 21.333 48v58.667A5.333 5.333 0 0 0 26.667 112h74.666a5.336 5.336 0 0 0 5.334-5.333V48a5.333 5.333 0 0 1 10.666 0v58.667a15.998 15.998 0 0 1-16 16H26.667a15.999 15.999 0 0 1-16-16V48A5.333 5.333 0 0 1 16 42.667Z"></path><path d="M42.667 64A5.333 5.333 0 0 1 48 58.667h32A5.333 5.333 0 0 1 85.333 64v53.333a5.334 5.334 0 1 1-10.666 0v-48H53.333v48a5.334 5.334 0 1 1-10.666 0V64Z"></path><path d="M60.623 1.206a5.333 5.333 0 0 1 6.754 0l58.667 48a5.333 5.333 0 1 1-6.754 8.255L64 12.224 8.71 57.461a5.333 5.333 0 0 1-6.754-8.255l58.667-48Z"></path></g><defs><clipPath id="a"><path fill="currentColor" d="M0 0h128v128H0z"></path></clipPath></defs></svg> </a> <a href="/collections/p/1" data-nav-action="collections"> <svg width="1.25rem" height="1.25rem" viewBox="0 0 128 128" fill="none" title="Collections" aria-label="Collections" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M21 94.25c0-2.9 2.35-5.25 5.25-5.25h4.5a5.25 5.25 0 1 1 0 10.5h-4.5A5.25 5.25 0 0 1 21 94.25ZM46 94.25c0-2.9 2.35-5.25 5.25-5.25h49.5a5.25 5.25 0 0 1 0 10.5h-49.5A5.25 5.25 0 0 1 46 94.25ZM21 64.25c0-2.9 2.35-5.25 5.25-5.25h4.5a5.25 5.25 0 1 1 0 10.5h-4.5A5.25 5.25 0 0 1 21 64.25ZM46 64.25c0-2.9 2.35-5.25 5.25-5.25h49.5a5.25 5.25 0 0 1 0 10.5h-49.5A5.25 5.25 0 0 1 46 64.25ZM21 34.25c0-2.9 2.35-5.25 5.25-5.25h4.5a5.25 5.25 0 1 1 0 10.5h-4.5A5.25 5.25 0 0 1 21 34.25ZM46 34.25c0-2.9 2.35-5.25 5.25-5.25h49.5a5.25 5.25 0 0 1 0 10.5h-49.5A5.25 5.25 0 0 1 46 34.25Z" fill="currentColor"></path></svg> </a> <button data-open-modal="omnisearch" data-nav-action="search"> <svg width="1.25rem" height="1.25rem" viewBox="0 0 128 128" fill="none" title="Search" aria-label="Search" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M53.333 18.667c-19.145 0-34.666 15.52-34.666 34.666C18.667 72.48 34.187 88 53.333 88 72.48 88 88 72.48 88 53.333c0-19.145-15.52-34.666-34.667-34.666ZM8 53.333C8 28.296 28.296 8 53.333 8s45.334 20.296 45.334 45.333S78.37 98.667 53.333 98.667C28.296 98.667 8 78.37 8 53.333Z" fill="currentColor"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M84.229 82.362a5.333 5.333 0 0 1 7.542 0l24 24a5.334 5.334 0 0 1-7.542 7.543l-24-24a5.333 5.333 0 0 1 0-7.543Z" fill="currentColor"></path></svg> </button> <a href="https://github.com/Chalarangelo/30-seconds-of-code" target="_blank" rel="noopener noreferrer"> <svg width="1.25rem" height="1.25rem" viewBox="0 0 128 128" fill="none" title="GitHub" aria-label="GitHub" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M5.493 89.373a5.333 5.333 0 0 1 6.467-3.88c4.351 1.087 7.266 4.019 9.35 6.465a94.373 94.373 0 0 1 2.21 2.735c.273.346.536.68.784.99.952 1.193 1.847 2.258 2.8 3.212 1.848 1.848 3.84 3.2 6.528 3.829 2.757.645 6.775.652 12.836-1.166a5.334 5.334 0 0 1 3.064 10.217c-7.273 2.182-13.254 2.523-18.33 1.335-5.146-1.205-8.821-3.853-11.64-6.672-1.38-1.38-2.568-2.814-3.595-4.1a207.96 207.96 0 0 1-.969-1.225c-.642-.815-1.204-1.53-1.808-2.238-1.75-2.054-2.835-2.789-3.817-3.034a5.333 5.333 0 0 1-3.88-6.468Z" fill="currentColor"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M114.667 45.44A5.333 5.333 0 0 1 120 50.773c0 15.714-4.807 26.253-12.794 32.969-5.367 4.513-11.779 6.953-18.1 8.346a23.306 23.306 0 0 1 1.561 10.134v20.445a5.333 5.333 0 0 1-10.667 0v-20.64c0-.14.005-.279.016-.419a12.639 12.639 0 0 0-3.525-9.789 5.333 5.333 0 0 1 3.238-9.013c8.074-.9 15.353-2.806 20.612-7.228 4.999-4.204 8.992-11.398 8.992-24.805a5.334 5.334 0 0 1 5.334-5.333Zm-96 .16A5.333 5.333 0 0 1 24 50.933c0 13.283 3.983 20.432 8.992 24.645 5.278 4.438 12.578 6.39 20.679 7.396a5.333 5.333 0 0 1 3.175 9.002 12.64 12.64 0 0 0-3.526 9.676c.009.125.013.25.013.375v20.64a5.333 5.333 0 0 1-10.666 0v-20.465a23.306 23.306 0 0 1 1.597-10.028c-6.331-1.444-12.76-3.911-18.136-8.432-7.977-6.707-12.795-17.185-12.795-32.809a5.333 5.333 0 0 1 5.334-5.333ZM33.27 5.747c4.185.929 10.007 3.226 17.7 8.383a5.333 5.333 0 1 1-5.94 8.86c-6.867-4.603-11.471-6.253-14.073-6.83l-.04-.009a21.707 21.707 0 0 0 .746 12.758 5.333 5.333 0 1 1-9.993 3.73 32.373 32.373 0 0 1 .575-24.073 5.334 5.334 0 0 1 3.385-3.012l1.517 5.113C25.63 5.554 25.635 5.552 25.64 5.55l.01-.003.022-.006.045-.013.097-.026a8.832 8.832 0 0 1 .791-.163c.432-.07.966-.125 1.606-.143 1.281-.035 2.96.085 5.058.55ZM107.693 5.55c.005.002.01.004-1.506 5.117l1.516-5.113a5.335 5.335 0 0 1 3.386 3.012 32.375 32.375 0 0 1 .574 24.072 5.333 5.333 0 1 1-9.993-3.73 21.7 21.7 0 0 0 .746-12.757l-.04.009c-2.601.577-7.206 2.227-14.073 6.83a5.333 5.333 0 0 1-5.94-8.86c7.694-5.157 13.516-7.454 17.701-8.383 2.098-.466 3.777-.586 5.059-.55.639.017 1.173.073 1.605.142a8.933 8.933 0 0 1 .791.163l.097.026.045.013.021.006.011.003Z" fill="currentColor"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M46.605 13.412a76.693 76.693 0 0 1 40.123 0 5.333 5.333 0 1 1-2.79 10.296 66.028 66.028 0 0 0-34.543 0 5.333 5.333 0 1 1-2.79-10.296ZM30.344 26.91a5.333 5.333 0 0 1 .185 7.54A23.68 23.68 0 0 0 24 50.906a5.333 5.333 0 0 1-10.666.057 34.347 34.347 0 0 1 9.471-23.866 5.333 5.333 0 0 1 7.54-.185Zm72.645 0a5.333 5.333 0 0 1 7.54.186A34.345 34.345 0 0 1 120 50.772a5.333 5.333 0 1 1-10.667.002 23.681 23.681 0 0 0-6.529-16.323 5.334 5.334 0 0 1 .185-7.54Z" fill="currentColor"></path></svg> </a> </nav> </header> <script type="module">const t={isMac:navigator.userAgent.toLowerCase().includes("mac"),navActionContainer:document.querySelector("[data-nav-action-container]"),navActionTriggers:{home:document.querySelector('[data-nav-action="home"]'),collections:document.querySelector('[data-nav-action="collections"]'),omnisearch:document.querySelector('[data-nav-action="search"]')},omnisearchDialog:document.querySelector('[data-modal="omnisearch"]'),navActionMap:{k:"omnisearch",j:"collections",h:"home"},handleKeyDown(e){const n=this.isMac?e.metaKey:e.ctrlKey,o=this.omnisearchDialog.open;!o&&n&&Object.keys(this.navActionMap).includes(e.key)?(this.hideHotkeys(),e.preventDefault(),this.triggerAction(this.navActionMap[e.key])):n&&!o?this.showHotkeys():this.hideHotkeys()},handleKeyUp(e){(this.isMac?e.key==="Meta":e.key==="Control")&&this.hideHotkeys()},triggerAction(e){this.navActionTriggers[e].click()},showHotkeys(){this.navActionContainer.dataset.navActionsShown=""},hideHotkeys(){delete this.navActionContainer.dataset.navActionsShown}};document.addEventListener("keydown",t.handleKeyDown.bind(t));document.addEventListener("keyup",t.handleKeyUp.bind(t));</script> <div id="start-of-content"></div> <main> <nav aria-label="Breadcrumb"> <ol> <li> <a href="/"> Home </a> <svg width="1em" height="1em" viewBox="0 0 128 128" fill="none" aria-hidden="true" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M44.229 28.229a5.333 5.333 0 0 1 7.542 0l32 32a5.333 5.333 0 0 1 0 7.542l-32 32a5.334 5.334 0 0 1-7.542-7.542L72.458 64l-28.23-28.229a5.333 5.333 0 0 1 0-7.542Z" fill="currentColor"></path></svg> </li><li> <a href="/js/p/1"> JavaScript </a> <svg width="1em" height="1em" viewBox="0 0 128 128" fill="none" aria-hidden="true" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M44.229 28.229a5.333 5.333 0 0 1 7.542 0l32 32a5.333 5.333 0 0 1 0 7.542l-32 32a5.334 5.334 0 0 1-7.542-7.542L72.458 64l-28.23-28.229a5.333 5.333 0 0 1 0-7.542Z" fill="currentColor"></path></svg> </li><li> <a href="/js/algorithm/p/1"> Algorithms </a> </li><li> <a href="/js/s/brainfuck-interpreter-part-1" aria-current="page"> Brainfuck interpreter - Part 1 </a> </li> </ol> </nav> <article> <h1>Making a Brainfuck interpreter in JavaScript - Part 1</h1> <img src="/assets/cover/lake-bench-800.webp" srcset="/assets/cover/lake-bench-800.webp 800w,/assets/cover/lake-bench-400.webp 400w,/assets/cover/lake-bench-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high"> <p>Remember how I tried my hand at making a <a href="/js/s/smallfuck-interpreter">Smallfuck interpreter</a> the other day? Well, this time around, I'm going to do the same for <strong>Brainfuck</strong>, but with a couple of twists:</p> <ol> <li>I'll be going down the <strong>AST</strong> route, instead of parsing and interpreting on the fly. This has the added benefit of creating an <strong>intermediate representation</strong> that can be used for portability and readability.</li> <li>I'll build something that sort of resembles a <strong>virtual machine</strong>, so that the code can be run in a more <strong>controlled environment</strong> and we can add some timeouts and other <strong>safety measures</strong>.</li> </ol> <p>For this article, I'll mainly explore the AST part of the interpreter. The VM part will be covered in the next article, which will be published in about a week or so.</p> <h2><a href="#language-introduction" id="language-introduction">Language introduction</a></h2> <p>Brainfuck is a <dfn title="A Turing-complete system can solve any computational problem given enough time and memory"><strong>Turing-complete</strong></dfn> <dfn title="Short for esoteric programming language; a computer programming language designed to experiment with unconventional ideas, be difficult to program in, or serve as a joke, rather than for practical use.">Esolang</dfn>, not unlike Smallfuck. It has only 8 commands, which makes it a very simple language to implement. The commands are:</p> <figure class="table-wrapper"><table> <thead> <tr> <th>Command</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code class="notranslate" translate="no">></code></td> <td>Move pointer to the right (by 1 cell)</td> </tr> <tr> <td><code class="notranslate" translate="no"><</code></td> <td>Move pointer to the left (by 1 cell)</td> </tr> <tr> <td><code class="notranslate" translate="no">+</code></td> <td>Increment the byte at the current cell</td> </tr> <tr> <td><code class="notranslate" translate="no">-</code></td> <td>Decrement the byte at the current cell</td> </tr> <tr> <td><code class="notranslate" translate="no">.</code></td> <td>Output the byte at the current cell</td> </tr> <tr> <td><code class="notranslate" translate="no">,</code></td> <td>Accept one byte of input, storing it at the current cell</td> </tr> <tr> <td><code class="notranslate" translate="no">[</code></td> <td>Jump past matching <code class="notranslate" translate="no">]</code> if value at current cell is <code class="notranslate" translate="no">0</code></td> </tr> <tr> <td><code class="notranslate" translate="no">]</code></td> <td>Jump back to matching <code class="notranslate" translate="no">[</code> if value at current cell is non-<code class="notranslate" translate="no">0</code></td> </tr> </tbody> </table></figure> <p>As you can see, the main differences from Smallfuck are the <strong>input/output commands</strong> and the fact that the <strong>memory is byte-based</strong> instead of bit-based. Other than that, the two languages are quite similar.</p> <h3><a href="#memory-representation" id="memory-representation">Memory representation</a></h3> <p>As is often the case, there are a few conflicting definitions of how the internal memory of the language works. In our case, we'll go for a more extensible memory system, using <strong>two arrays</strong>, <code class="notranslate" translate="no">left</code> and <code class="notranslate" translate="no">right</code> and a <code class="notranslate" translate="no">pointer</code>, starting at <code class="notranslate" translate="no">0</code>. Negative indexes will be used to access the <code class="notranslate" translate="no">left</code> array, while positive indexes will be used to access the <code class="notranslate" translate="no">right</code> array. This gives us an <strong>infinite memory system</strong> that grows in both directions.</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">class</span> <span class="token class-name">Memory</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">initialMemory</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">left</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">right</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">pointer</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">get</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>index <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword control-flow">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">#getRight</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">#getLeft</span><span class="token punctuation">(</span><span class="token operator">-</span>index <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">set</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>index <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">#setRight</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">else</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">#setLeft</span><span class="token punctuation">(</span><span class="token operator">-</span>index <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">movePointer</span><span class="token punctuation">(</span><span class="token parameter">offset</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">pointer</span> <span class="token operator">+=</span> offset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">#getLeft</span><span class="token punctuation">(</span><span class="token parameter">index</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">left</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token keyword nil">undefined</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">left</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword control-flow">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">left</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">#setLeft</span><span class="token punctuation">(</span><span class="token parameter">index<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">left</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">min</span><span class="token punctuation">(</span><span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">max</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">#getRight</span><span class="token punctuation">(</span><span class="token parameter">index</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">right</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token keyword nil">undefined</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">right</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword control-flow">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">right</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">#setRight</span><span class="token punctuation">(</span><span class="token parameter">index<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">right</span><span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">min</span><span class="token punctuation">(</span><span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">max</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token maybe-class-name">Memory</span><span class="token punctuation">;</span></pre> <figure class="admonition" data-admonition-type="note"> <figcaption>💬 Note</figcaption> <p>Yes, technically this isn't an infinite memory system, as it's <strong>constrained by the actual memory</strong> of the machine running the JavaScript code. But, for all intents and purposes, it can be scaled to any size, given enough real memory.</p> </figure> <p>Notice that we <strong>overflow and underflow</strong> values to the range <code class="notranslate" translate="no">[0, 255]</code>, as Brainfuck uses 1-byte cells. This allows us to handle wrapping around the edges of the memory.</p> <h3><a href="#input-and-output" id="input-and-output">Input and output</a></h3> <p>For <strong>input and output</strong>, we'll use the standard <code class="notranslate" translate="no">process.stdin</code> and <code class="notranslate" translate="no">process.stdout</code> streams. We'll be reading and writing <strong>single bytes</strong> at a time and converting from the byte value to a character and vice versa. This is a trivial task using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">String.fromCharCode()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">String.prototype.charCodeAt()</code></a>.</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token comment">// Read one byte</span> process<span class="token punctuation">.</span><span class="token property-access">stdin</span><span class="token punctuation">.</span><span class="token method function property-access">read</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">charCodeAt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Write one byte (65 is the ASCII code for 'A')</span> process<span class="token punctuation">.</span><span class="token property-access">stdout</span><span class="token punctuation">.</span><span class="token method function property-access">write</span><span class="token punctuation">(</span><span class="token known-class-name class-name">String</span><span class="token punctuation">.</span><span class="token method function property-access">fromCharCode</span><span class="token punctuation">(</span><span class="token number">65</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre> <h3><a href="#tokenization" id="tokenization">Tokenization</a></h3> <p><strong>Tokenization</strong> will be very similar to Smallfuck, except for the set of characters that are allowed. However, we'll make three <strong>optimizations</strong> that will drastically reduce the size and complexity of our AST:</p> <ol> <li>We'll <strong>merge consecutive</strong> <code class="notranslate" translate="no">+</code> and <code class="notranslate" translate="no">-</code> commands into a single command that increments or decrements the current cell by the sum of the commands. This will require the addition of a <code class="notranslate" translate="no">diff</code> parameter to the AST nodes.</li> <li>We'll <strong>merge consecutive</strong> <code class="notranslate" translate="no">></code> and <code class="notranslate" translate="no"><</code> commands into a single command that moves the pointer by the sum of the commands. This will require the addition of an <code class="notranslate" translate="no">offset</code> parameter to the AST nodes.</li> <li>We'll <strong>replace the pattern</strong> <code class="notranslate" translate="no">[-]</code> with a single command that sets the current cell to <code class="notranslate" translate="no">0</code>. This will require the addition of a new node type for this specific command, but will drastically reduce execution time.</li> </ol> <pre class="language-plaintext notranslate" translate="no">Brainfuck code: ++-+><<<[-]>+ Tokens: [['+', 2], ['>', -2], '0', '>', '+']</pre> <h2><a href="#abstract-syntax-tree" id="abstract-syntax-tree">Abstract Syntax Tree</a></h2> <p>An <strong>Abstract Syntax Tree</strong> (AST) is a tree representation of the abstract syntactic structure of source code written in a programming language. It's a way to represent the code in a way that's easier to manipulate and interpret. For Brainfuck, the AST is quite simple, as the language itself is simple.</p> <p>Each <strong>node</strong> in the AST represents a command in the Brainfuck language. The nodes have a <code class="notranslate" translate="no">type</code> property that can be one of the Brainfuck commands. Loop nodes are the only special case, requiring an additional <code class="notranslate" translate="no">nodes</code> property to store the inner nodes.</p> <pre class="language-plaintext notranslate" translate="no">Brainfuck code: +[>++<-] AST: [ { type: 'increment' }, { type: 'loop', nodes: [ { type: 'moveRight' }, { type: 'increment' }, { type: 'increment' }, { type: 'moveLeft' }, { type: 'decrement' } ] } ]</pre> <h3><a href="#the-ast-class" id="the-ast-class">The AST class</a></h3> <p>The <code class="notranslate" translate="no">AST</code> class represents the AST of a Brainfuck program. It has a single property, <code class="notranslate" translate="no">nodes</code>, which is an array of nodes. The class has a single method, <code class="notranslate" translate="no">addNode</code>, which adds a new node to the AST.</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="ast.js"><span class="token keyword">class</span> <span class="token class-name">AST</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">nodes</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">addNode</span><span class="token punctuation">(</span><span class="token parameter">node</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">nodes</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token constant">AST</span><span class="token punctuation">;</span></pre> <h3><a href="#the-ast-node-class" id="the-ast-node-class">The ASTNode class</a></h3> <p>The <code class="notranslate" translate="no">ASTNode</code> class represents a single node in the AST. For flexibility, I'll first define a set of <code class="notranslate" translate="no">nodeTypes</code> as an object representing the various node types (its keys) and their parameters, such as <code class="notranslate" translate="no">offset</code>, <code class="notranslate" translate="no">diff</code>, and <code class="notranslate" translate="no">nodes</code>.</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="astNode.js"><span class="token keyword">const</span> nodeTypes <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">movePointer</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">offset</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">updateCell</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">diff</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">clearCell</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">printValue</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">readValue</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">loop</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">nodes</span><span class="token operator">:</span> <span class="token keyword null nil">null</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> typeNames <span class="token operator">=</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">keys</span><span class="token punctuation">(</span>nodeTypes<span class="token punctuation">)</span><span class="token punctuation">;</span></pre> <p>As you can see, we've used <strong>descriptive names</strong> for the node types, such as <code class="notranslate" translate="no">movePointer</code>and <code class="notranslate" translate="no">updateCell</code>. This will make the tree easier to read and understand. Moreover, we'll group <code class="notranslate" translate="no">+</code> and <code class="notranslate" translate="no">-</code> under <code class="notranslate" translate="no">updateCell</code>, and <code class="notranslate" translate="no">></code> and <code class="notranslate" translate="no"><</code> under <code class="notranslate" translate="no">movePointer</code>, as the additional parameters will allow us to differentiate between them.</p> <p>We can now define a simple <code class="notranslate" translate="no">ASTNode</code> class that will take a <code class="notranslate" translate="no">type</code> and <code class="notranslate" translate="no">params</code> object as arguments. The <code class="notranslate" translate="no">params</code> object will be validated against the <code class="notranslate" translate="no">nodeTypes</code> object to ensure that the <strong>node type</strong> is valid and that the <strong>parameters</strong> are correct.</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="astNode.js"><span class="token keyword">const</span> nodeTypes <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> typeNames <span class="token operator">=</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">keys</span><span class="token punctuation">(</span>nodeTypes<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ASTNode</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">type<span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>typeNames<span class="token punctuation">.</span><span class="token method function property-access">includes</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Invalid node type: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>type<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">type</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> paramsWithDefaults <span class="token punctuation">}</span> <span class="token operator">=</span> nodeTypes<span class="token punctuation">[</span>type<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">entries</span><span class="token punctuation">(</span>paramsWithDefaults<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>key<span class="token punctuation">,</span> defaultValue<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> params<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">||</span> defaultValue<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token maybe-class-name">ASTNode</span><span class="token punctuation">;</span></pre> <h2><a href="#code-parser" id="code-parser">Code parser</a></h2> <p>We'll use a <strong>regular expression</strong> to first clean up the code by removing any characters that are not part of the Brainfuck language. We'll then split the code into character tokens and then <strong>merge consecutive tokens</strong> of the same type.</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="parser.js"><span class="token keyword">class</span> <span class="token class-name">Parser</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">splitTokens</span><span class="token punctuation">(</span><span class="token parameter">code</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> code<span class="token punctuation">.</span><span class="token method function property-access">replace</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[^><+\-,.[\]]</span><span class="token regex-delimiter">/</span><span class="token regex-flags">gm</span></span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">split</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token function">mergeConsecutiveTokens</span><span class="token punctuation">(</span><span class="token parameter">tokens</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> mergedTokens <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">let</span> lastGroup <span class="token operator">=</span> <span class="token keyword null nil">null</span><span class="token punctuation">;</span> <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> token <span class="token keyword">of</span> tokens<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>lastGroup<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>tokens<span class="token punctuation">,</span> count<span class="token punctuation">]</span> <span class="token operator">=</span> lastGroup<span class="token punctuation">;</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>tokens<span class="token punctuation">.</span><span class="token method function property-access">includes</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> lastGroup<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+=</span> token <span class="token operator">===</span> tokens<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword control-flow">continue</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span> token <span class="token operator">===</span> <span class="token string">']'</span> <span class="token operator">&&</span> tokens <span class="token operator">===</span> <span class="token string">'+-'</span> <span class="token operator">&&</span> count <span class="token operator">===</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">&&</span> mergedTokens<span class="token punctuation">[</span>mergedTokens<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'['</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> mergedTokens<span class="token punctuation">.</span><span class="token method function property-access">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> lastGroup <span class="token operator">=</span> <span class="token keyword null nil">null</span><span class="token punctuation">;</span> mergedTokens<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">continue</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>count<span class="token punctuation">)</span> mergedTokens<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">[</span>tokens<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> count<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> lastGroup <span class="token operator">=</span> <span class="token keyword null nil">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>token <span class="token operator">===</span> <span class="token string">'>'</span><span class="token punctuation">)</span> lastGroup <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'><'</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>token <span class="token operator">===</span> <span class="token string">'<'</span><span class="token punctuation">)</span> lastGroup <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'><'</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>token <span class="token operator">===</span> <span class="token string">'+'</span><span class="token punctuation">)</span> lastGroup <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'+-'</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>token <span class="token operator">===</span> <span class="token string">'-'</span><span class="token punctuation">)</span> lastGroup <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'+-'</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword control-flow">else</span> mergedTokens<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>lastGroup<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>tokens<span class="token punctuation">,</span> count<span class="token punctuation">]</span> <span class="token operator">=</span> lastGroup<span class="token punctuation">;</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>count<span class="token punctuation">)</span> mergedTokens<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">[</span>tokens<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> count<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">return</span> mergedTokens<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token maybe-class-name">Parser</span><span class="token punctuation">;</span></pre> <p>Up until this point, everything should be similar to the Smallfuck interpreter. Notice that we've taken some liberties in the <code class="notranslate" translate="no">mergeConsecutiveTokens</code> method to represent consecutive tokens as <strong>2-element arrays</strong> (type and <code class="notranslate" translate="no">offset</code>/<code class="notranslate" translate="no">diff</code>), as well as handling the <code class="notranslate" translate="no">[-]</code> pattern, replacing it with a <code class="notranslate" translate="no">0</code> token. This intermediate step isn't absolutely necessary, but it helped me debug the code and seemed a lot easier to read than going from raw tokens to AST nodes representing multiple nodes directly.</p> <p>Finally, we can add our <code class="notranslate" translate="no">parse</code> function to tie it all up:</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="parser.js"><span class="token keyword module">import</span> <span class="token constant">AST</span> <span class="token keyword module">from</span> <span class="token string">'./ast.js'</span><span class="token punctuation">;</span> <span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">ASTNode</span></span> <span class="token keyword module">from</span> <span class="token string">'./astNode.js'</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Parser</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">parse</span><span class="token punctuation">(</span><span class="token parameter">code</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> tokens <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">splitTokens</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> mergedTokens <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">mergeConsecutiveTokens</span><span class="token punctuation">(</span>tokens<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> ast <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AST</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> stack <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> token <span class="token keyword">of</span> mergedTokens<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token known-class-name class-name">Array</span><span class="token punctuation">.</span><span class="token method function property-access">isArray</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>type<span class="token punctuation">,</span> count<span class="token punctuation">]</span> <span class="token operator">=</span> token<span class="token punctuation">;</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'>'</span><span class="token punctuation">)</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'movePointer'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">offset</span><span class="token operator">:</span> count <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>type <span class="token operator">===</span> <span class="token string">'+'</span><span class="token punctuation">)</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'updateCell'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">diff</span><span class="token operator">:</span> count <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">switch</span> <span class="token punctuation">(</span>token<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'>'</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'movePointer'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">offset</span><span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'<'</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'movePointer'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">offset</span><span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'+'</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'updateCell'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">diff</span><span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'-'</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'updateCell'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">diff</span><span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'.'</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'printValue'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">','</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'readValue'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'0'</span><span class="token operator">:</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'clearCell'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'['</span><span class="token operator">:</span> stack<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span>ast<span class="token punctuation">)</span><span class="token punctuation">;</span> ast <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AST</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">']'</span><span class="token operator">:</span> <span class="token keyword">const</span> loop <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ASTNode</span><span class="token punctuation">(</span><span class="token string">'loop'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">nodes</span><span class="token operator">:</span> ast <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ast <span class="token operator">=</span> stack<span class="token punctuation">.</span><span class="token method function property-access">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ast<span class="token punctuation">.</span><span class="token method function property-access">addNode</span><span class="token punctuation">(</span>loop<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">break</span><span class="token punctuation">;</span> <span class="token keyword module">default</span><span class="token operator">:</span> <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Invalid token: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>stack<span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">)</span> <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Unmatched loop start'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword control-flow">return</span> ast<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// ...</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token maybe-class-name">Parser</span><span class="token punctuation">;</span></pre> <p>I think the only point worth mentioning here is, as is often the case, the use of a <strong>stack</strong> for loops. It helps us create the nested structure of the AST, but also acts as a way to <strong>validate that all loop brackets are matched</strong>.</p> <h2><a href="#code-execution" id="code-execution">Code execution</a></h2> <p>The AST itself is essentially a simple array of nodes. To execute it, we can use a simple <strong>loop over the nodes</strong>. Each node will then define what to do based on its type. Loop nodes will follow a similar pattern, implementing their own loop logic over their internal nodes. Let's see what that looks like for the <code class="notranslate" translate="no">AST</code> class:</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="ast.js"><span class="token keyword">class</span> <span class="token class-name">AST</span> <span class="token punctuation">{</span> <span class="token comment">// ...</span> <span class="token function">execute</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory<span class="token punctuation">,</span> stdin<span class="token punctuation">,</span> stdout<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> node <span class="token keyword">of</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">nodes</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> node<span class="token punctuation">.</span><span class="token method function property-access">execute</span><span class="token punctuation">(</span><span class="token punctuation">{</span> memory<span class="token punctuation">,</span> stdin<span class="token punctuation">,</span> stdout <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token constant">AST</span><span class="token punctuation">;</span></pre> <p>Now, for the <code class="notranslate" translate="no">ASTNode</code> class, we can simply extend the <code class="notranslate" translate="no">nodeTypes</code> object to include a <code class="notranslate" translate="no">createExecute</code> method for each node. This way we can prepare the <code class="notranslate" translate="no">execute</code> function in the <code class="notranslate" translate="no">constructor</code>, instead of having to write a <code class="notranslate" translate="no">switch</code> statement:</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="astNode.js"><span class="token keyword">const</span> nodeTypes <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">movePointer</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">offset</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function">createExecute</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> offset <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> memory<span class="token punctuation">.</span><span class="token method function property-access">movePointer</span><span class="token punctuation">(</span>offset<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">updateCell</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">diff</span><span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function">createExecute</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> diff <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> memory<span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token property-access">pointer</span><span class="token punctuation">,</span> memory<span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token property-access">pointer</span><span class="token punctuation">)</span> <span class="token operator">+</span> diff<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">clearCell</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function">createExecute</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> memory<span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token property-access">pointer</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">printValue</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function">createExecute</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory<span class="token punctuation">,</span> stdout <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> stdout<span class="token punctuation">.</span><span class="token method function property-access">write</span><span class="token punctuation">(</span><span class="token known-class-name class-name">String</span><span class="token punctuation">.</span><span class="token method function property-access">fromCharCode</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token property-access">pointer</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">readValue</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function">createExecute</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory<span class="token punctuation">,</span> stdin <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> memory<span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token property-access">pointer</span><span class="token punctuation">,</span> stdin<span class="token punctuation">.</span><span class="token method function property-access">read</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">?.</span><span class="token method function property-access">charCodeAt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">loop</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">nodes</span><span class="token operator">:</span> <span class="token keyword null nil">null</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function">createExecute</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> nodes <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> memory<span class="token punctuation">,</span> stdin<span class="token punctuation">,</span> stdout <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token property-access">pointer</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> nodes<span class="token punctuation">.</span><span class="token method function property-access">execute</span><span class="token punctuation">(</span><span class="token punctuation">{</span> memory<span class="token punctuation">,</span> stdin<span class="token punctuation">,</span> stdout <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> typeNames <span class="token operator">=</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">keys</span><span class="token punctuation">(</span>nodeTypes<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">ASTNode</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">type<span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>typeNames<span class="token punctuation">.</span><span class="token method function property-access">includes</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword control-flow">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Invalid node type: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>type<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">type</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token literal-property property">params</span><span class="token operator">:</span> paramsWithDefaults <span class="token punctuation">}</span> <span class="token operator">=</span> nodeTypes<span class="token punctuation">[</span>type<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">entries</span><span class="token punctuation">(</span>paramsWithDefaults<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>key<span class="token punctuation">,</span> defaultValue<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> params<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">||</span> defaultValue<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">execute</span> <span class="token operator">=</span> nodeTypes<span class="token punctuation">[</span>type<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">createExecute</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token maybe-class-name">ASTNode</span><span class="token punctuation">;</span></pre> <p>And that's it! We now have a fully functional Brainfuck interpreter that uses an AST to represent the code and execute it. Let's put it to the test:</p> <pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Memory</span></span> <span class="token keyword module">from</span> <span class="token string">'./memory.js'</span><span class="token punctuation">;</span> <span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Parser</span></span> <span class="token keyword module">from</span> <span class="token string">'./parser.js'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> program <span class="token operator">=</span> <span class="token string">'++++++++[>++++[>++<-]>+[<]<-]>>.---.+++++++..+++.'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> ast <span class="token operator">=</span> <span class="token maybe-class-name">Parser</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>program<span class="token punctuation">)</span><span class="token punctuation">;</span> ast<span class="token punctuation">.</span><span class="token method function property-access">execute</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">memory</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Memory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">stdin</span><span class="token operator">:</span> process<span class="token punctuation">.</span><span class="token property-access">stdin</span><span class="token punctuation">,</span> <span class="token literal-property property">stdout</span><span class="token operator">:</span> process<span class="token punctuation">.</span><span class="token property-access">stdout</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> process<span class="token punctuation">.</span><span class="token property-access">stdout</span><span class="token punctuation">.</span><span class="token method function property-access">write</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Output: 'HELLO\n';</span></pre> <h2><a href="#conclusion" id="conclusion">Conclusion</a></h2> <p>We've come halfway through the whole project in this first part of the implementation. Next time around, we'll be building a <strong>virtual machine</strong> to run the code in a more controlled environment. This will allow us to add <strong>safety measures</strong> and <strong>timeouts</strong> to the code execution, as well as <strong>debugging tools</strong>, shall we need them. Stay tuned!</p> <figure class="admonition" data-admonition-type="note"> <figcaption>💬 Note</figcaption> <p>You can find the codebase for this entire project in <a href="https://github.com/Chalarangelo/bf-interpreter" rel="noopener noreferrer" target="_blank">this GitHub repository</a>. Feel free to clone it and play around with it! Give it a star if you like it, too!</p> </figure> <footer> <p> Last updated: <time datetime="2025-03-01">March 1, 2025</time> <a href="https://github.com/Chalarangelo/30-seconds-of-code/blob/master/content/snippets/js/s/brainfuck-interpreter-part-1.md" target="_blank" rel="noopener noreferrer">View On GitHub</a> </p> </footer> </article> <nav aria-label="Pagination"> <a href="/js/s/smallfuck-interpreter" rel="prev" aria-disabled="false"> <svg width="1.25em" height="1.25em" viewBox="0 0 128 128" fill="none" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M83.771 28.229a5.333 5.333 0 0 1 0 7.542L55.542 64l28.23 28.229a5.333 5.333 0 0 1-7.543 7.542l-32-32a5.333 5.333 0 0 1 0-7.542l32-32a5.333 5.333 0 0 1 7.542 0Z" fill="currentColor"></path></svg> Previous </a> <a href="/js/s/brainfuck-interpreter-part-2" rel="next" aria-disabled="false"> Next <svg width="1.25em" height="1.25em" viewBox="0 0 128 128" fill="none" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M44.229 28.229a5.333 5.333 0 0 1 7.542 0l32 32a5.333 5.333 0 0 1 0 7.542l-32 32a5.334 5.334 0 0 1-7.542-7.542L72.458 64l-28.23-28.229a5.333 5.333 0 0 1 0-7.542Z" fill="currentColor"></path></svg> </a> <p><a href="/js/tokenizers-interpreters/p/1">Tokenizers & Interpreters</a> collection <span>(6 of 8 articles)</span></p> </nav> <section class="preview-list"> <h2>More like this</h2> <ul> <li> <img src="/assets/cover/mug-flower-book-400.webp" srcset="/assets/cover/mug-flower-book-400.webp 400w,/assets/cover/mug-flower-book-800.webp 800w" alt="" height="180" width="360" loading="lazy"> <article> <small> JavaScript · <time datetime="2025-03-08"> March 8, 2025 </time> </small> <h3> <a href="/js/s/brainfuck-interpreter-part-2">Making a Brainfuck interpreter in JavaScript - Part 2</a> </h3> <p>Picking up where I left off last time, I'm wrapping up the Brainfuck interpreter, by making a simple VM for code execution and debugging.</p> </article> </li><li> <img src="/assets/cover/rustic-cup-400.webp" srcset="/assets/cover/rustic-cup-400.webp 400w,/assets/cover/rustic-cup-800.webp 800w" alt="" height="180" width="360" loading="lazy"> <article> <small> JavaScript · <time datetime="2025-02-24"> February 24, 2025 </time> </small> <h3> <a href="/js/s/smallfuck-interpreter">Implementing a Smallfuck Interpreter in JavaScript</a> </h3> <p>Yet another interpreter article, this time around we'll be building a full-fledged interpreter for the esolang Smallfuck.</p> </article> </li><li> <img src="/assets/cover/waves-from-above-400.webp" srcset="/assets/cover/waves-from-above-400.webp 400w,/assets/cover/waves-from-above-800.webp 800w" alt="" height="180" width="360" loading="lazy"> <article> <small> JavaScript · <time datetime="2023-12-17"> December 17, 2023 </time> </small> <h3> <a href="/js/s/caesar-cipher">Implement the Caesar cipher in JavaScript</a> </h3> <p>The Caesar cipher is a simple substitution cipher, which can be easily implemented with a few lines of JavaScript code.</p> </article> </li><li> <img src="/assets/cover/antelope-400.webp" srcset="/assets/cover/antelope-400.webp 400w,/assets/cover/antelope-800.webp 800w" alt="" height="180" width="360" loading="lazy"> <article> <small> JavaScript · <time datetime="2024-05-11"> May 11, 2024 </time> </small> <h3> <a href="/js/s/k-means">Group data using the K-means clustering algorithm in JavaScript</a> </h3> <p>Implement the K-means clustering algorithm in JavaScript to group data into clusters.</p> </article> </li> </ul> </section> </main> <nav aria-label="Table of contents"> <div> <h2>Contents</h2> <table-of-contents><ol><li><a href="#language-introduction">Language introduction</a><ol><li><a href="#memory-representation">Memory representation</a></li> <li><a href="#input-and-output">Input and output</a></li> <li><a href="#tokenization">Tokenization</a></li></ol></li><li><a href="#abstract-syntax-tree">Abstract Syntax Tree</a><ol><li><a href="#the-ast-class">The AST class</a></li> <li><a href="#the-ast-node-class">The ASTNode class</a></li></ol></li><li><a href="#code-parser">Code parser</a></li><li><a href="#code-execution">Code execution</a></li><li><a href="#conclusion">Conclusion</a></li></ol></table-of-contents> <a href="#start-of-content"> <svg width="1.25em" height="1.25em" viewBox="0 0 128 128" fill="none" aria-hidden="true" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M31.5377 61.5377C29.4874 63.5879 29.4874 66.912 31.5377 68.9623C33.5879 71.0125 36.9121 71.0125 38.9623 68.9623L59 48.9246L59 91C59 93.8995 61.3505 96.25 64.25 96.25C67.1495 96.25 69.5 93.8995 69.5 91L69.5 48.9246L89.5377 68.9623C91.5879 71.0125 94.9121 71.0125 96.9623 68.9623C99.0126 66.912 99.0126 63.5879 96.9623 61.5377L67.9623 32.5377C65.9121 30.4874 62.5879 30.4874 60.5377 32.5377L31.5377 61.5377Z" fill="currentColor"></path></svg> Top </a> </div> </nav> <script type="module">class n extends HTMLElement{constructor(){super(),this.observer=null,this.current=null,this.timeout=null,this.links=[...this.querySelectorAll("a")],this.observableElements=document.querySelectorAll("main > article > :is(h2, h3, h4) > a[id]"),this.observe(),window.addEventListener("resize",()=>{this.observer&&(this.observer.disconnect(),this.observer=null),clearTimeout(this.timeout),this.timeout=setTimeout(()=>this.observe(),200)})}updateCurrent(e){for(const{isIntersecting:r,target:s}of e){if(!r)continue;const t=this.links.find(i=>i.hash==="#"+s.id);if(!(!t||this.current===t)){this.current?.removeAttribute("aria-current"),t.setAttribute("aria-current","true"),this.current=t;break}}}observe(){this.observer||(this.observer=new IntersectionObserver(e=>this.updateCurrent(e),{rootMargin:this.getRootMargin()}),this.observableElements.forEach(e=>this.observer.observe(e)))}getRootMargin(){return`-32px 0% ${128-document.documentElement.clientHeight}px`}}customElements.define("table-of-contents",n);</script> <footer> <div> <p> Hello! <span class="wave">👋</span> I’m Angelos, a professional software engineer, based in Greece. I work on <strong>30 seconds of code</strong> in my free time to create the best resource I never had when I started out as a developer. </p> <nav aria-label="Sitemap"> <h5>Sitemap</h5> <ul> <li><a href="/">Home</a></li> <li><a href="/snippets/p/1">Articles</a></li> <li><a href="/collections/p/1">Collections</a></li> <li><a href="/about">About</a></li> <li><a href="/update-logs/p/1">Updates</a></li> <li><a href="/feed.xml">RSS</a></li> </ul> <small>Powered by <a href="https://www.netlify.com/" rel="noopener noreferrer nofollow" target="_blank">Netlify</a>, <a href="https://astro.build/" rel="noopener noreferrer nofollow" target="_blank">Astro</a> & <a href="https://github.com/" rel="noopener noreferrer nofollow" target="_blank">GitHub</a> <br> © 2017-2025 <a href="https://chalarangelo.me" rel="noopener noreferrer nofollow" target="_blank">Angelos Chalaris</a>. All rights reserved. </small> </nav> </div> </footer> <dialog data-modal="omnisearch"> <search> <svg width="1.25rem" height="1.25rem" viewBox="0 0 128 128" fill="none" aria-hidden="true" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M53.333 18.667c-19.145 0-34.666 15.52-34.666 34.666C18.667 72.48 34.187 88 53.333 88 72.48 88 88 72.48 88 53.333c0-19.145-15.52-34.666-34.667-34.666ZM8 53.333C8 28.296 28.296 8 53.333 8s45.334 20.296 45.334 45.333S78.37 98.667 53.333 98.667C28.296 98.667 8 78.37 8 53.333Z" fill="currentColor"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M84.229 82.362a5.333 5.333 0 0 1 7.542 0l24 24a5.334 5.334 0 0 1-7.542 7.543l-24-24a5.333 5.333 0 0 1 0-7.543Z" fill="currentColor"></path></svg> <input type="search" placeholder="Search..." id="omnisearch" aria-label="Search articles and collections"> <button data-close-modal="omnisearch"> <svg width="2rem" height="2rem" viewBox="0 0 128 128" fill="none" aria-label="Close" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M90.7523 97.8234C92.7049 99.776 95.8708 99.776 97.8234 97.8234C99.776 95.8708 99.776 92.705 97.8234 90.7523L70.9533 63.8823L97.8234 37.0123C99.776 35.0596 99.776 31.8938 97.8234 29.9412C95.8708 27.9886 92.7049 27.9886 90.7523 29.9412L63.8823 56.8112L37.0122 29.9412C35.0596 27.9885 31.8938 27.9885 29.9411 29.9412C27.9885 31.8938 27.9885 35.0596 29.9411 37.0122L56.8112 63.8823L29.9411 90.7524C27.9885 92.705 27.9885 95.8708 29.9411 97.8234C31.8937 99.7761 35.0596 99.7761 37.0122 97.8234L63.8823 70.9534L90.7523 97.8234Z" fill="currentColor"></path></svg> </button> <output aria-label="Search results" for="omnisearch"> <p>Start typing a keyphrase to see matching articles.</p> </output> </search> </dialog> <script type="module" src="/_astro/Omnisearch.astro_astro_type_script_index_0_lang.B9Xbxmo2.js"></script> </body></html>