commit 4d6c7201ee231f6a8082af0714c4159a1ad5ab42 Author: Alexander Nutz Date: Mon May 19 23:08:28 2025 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08920c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +pub.sh +*.html +*.pdf diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c4d258d --- /dev/null +++ b/build.sh @@ -0,0 +1,20 @@ +set -e + +rm -rf build +mkdir build + +compile () { + typst compile --root . --features html -j 4 $@ +} + +page () { + compile --format pdf --input web=false "pages/$1" "build/$1.min.pdf" + compile --format html --input web=true "pages/$1" "build/$1.desktop.html" + compile --format html --input web=false "pages/$1" "build/$1.min.html" +} + +page "article-make-regex-engine-1.typ" +page "project-etc-nand.typ" +page "index.typ" + +cp build/index.typ.desktop.html build/index.html diff --git a/common.typ b/common.typ new file mode 100644 index 0000000..66c4b0f --- /dev/null +++ b/common.typ @@ -0,0 +1,243 @@ +#let to-bool(str) = { + if str == "true" { + return true + } + if str == "false" { + return false + } + assert(false) +} + +#let is-web = to-bool(sys.inputs.at("web", default: "true")) +#let is-html() = { return target() == "html" } + +#let res-path() = { + if is-html() { + "res/" + } else { + "/res/" + } +} + +#let title(content) = { + context if is-html() { + html.elem("h1", content) + } else { + text(23pt, content) + } +} + +#let br() = { + context if is-html() { + html.elem("br") + } + "\n" +} + +#let space(num: 1) = { + context if is-html() { + [~] * (num - 1) + [ ] + } else { + " " * num + } +} + +#let html-p(txt) = { + context if is-html() { + html.elem("p", txt) + } else { + text(txt) + } +} + +#let sized-p(size, txt) = { + context if is-html() { + html.elem("p", attrs: (style: "font-size: " + str(size.abs.pt()) + "pt"), txt) + } else { + text(size, txt) + } +} + +#let html-frame(content) = { + context if is-html() { + html.frame(content) + } else { + content + } +} + +#let html-opt-elem(kind, attrs, content) = { + context if is-html() { + html.elem(kind, attrs: attrs, content) + } else { + content + } +} + +#let html-span(attrs, content) = { + html-opt-elem("span", attrs, content) +} + +#let html-bold(content) = { + context if is-html() { + html.elem("b", content) + } else { + content + } +} + +#let html-style(class:"", style, content) = { + html-span((class:class, style: style), content) +} + +#let slink(target, link-text) = text(fill: blue)[ + #underline[#link(target, link-text)] +] + +#let flink(target, link-text) = text(fill: blue)[ + #underline[#link(target, link-text)] + #footnote[#text(fill: blue)[#link(target)]] +] + +#let section(b) = block(breakable: false, [ + \ + #b +]) + +#let take-while(array, fn) = { + let out = (); + for x in array { + if fn(x) { + out += (x,); + } else { + break; + } + } + return out; +} + +#let gen-tree-from-headings( + hide-first: false, + marker: context "├─" + space(), + marker-end: context "└─" + space(), + side: context "|" + space(num:2), + side-empty: context space(num:3), + elemfn: x => x, + headings, +) = { + for (idx, item) in headings.enumerate() { + let content = []; + for ind in range(item.level - 1) { + let will-future = take-while(headings.slice(idx), + x => x.level > ind) + .any(x => x.level == ind + 1); + content += if will-future { + side + } else { + side-empty + } + } + content += if take-while(headings.slice(idx + 1), + x => x.level >= item.level) + .any(x => x.level == item.level) { + marker + } else { + marker-end + } + elemfn(content, item) + } +} + +#let len2css(len, default: "auto", map-pt: x => str(x)+"pt") = { + if len == auto or type(len) == dictionary { + return default + } + if type(len) == relative and float(len.ratio) * 100 != 0 { + assert(len.length.pt() == 0) + return str(float(len.ratio) * 100) + "%"; + } + if type(len) == relative { + len = len.length + } + return map-pt(len.abs.pt()) +} + +#let option-map(option, default, fn) = { + if option == none { default } + else { fn(option) } +} + +#let stroke2css(stroke) = { + if type(stroke) == dictionary { + return "none" + } + let th = len2css(stroke.thickness, default: "1pt") + return th + " solid black" // TODO: paint +} + +#let css-style(it) = { + return " + display: inline-block; + border: "+stroke2css(it.stroke)+"; + border-radius: "+len2css(it.radius, default: "0px")+"; + " + option-map(len2css(it.width, default:none), "", x => "width:"+x+";") +" + " + option-map(len2css(it.height, default:none), "", x => "height:"+x+";") +" + padding: " + len2css(it.inset) +} + +#let html-script(code) = { + [#context if is-html(){ + html.elem("script", code) + }] +} + +#let html-script-dom-onload(code) = { + html-script("document.addEventListener('DOMContentLoaded', function() { "+code+" })") +} + +#let column-fixed(..contents) = { + html-style(class:"column-fixed", "display: inline-flex; position: fixed; justify-content: center; width: 25%")[ + #table(..contents) + + #context if is-html() { + html.elem("style", " + .column-fixed > table > tbody > tr > td > * { width: 100%; } + ") + } + ] +} + +#let table-of-contents() = { + html-style(class:"table-of-contents", "", [ + #box( + stroke: black, + radius: 2pt, + inset: 3%, + )[ + Table of contents\ + #context { + let idx = state("stupid-gen-page-state", 0); + gen-tree-from-headings(query(heading), + elemfn: (content, x) => { + [#content #context html-span((class:"headingr",id:"headingr-"+str(idx.get())), link(x.location(), x.body)) #context idx.update(x=> x + 1) #br()] + }) + } + ] + #html-script-dom-onload(" + let tags = ['h2','h3','h4'].flatMap(x => Array.from(document.getElementsByTagName(x))); + document.getElementById('headingr-0').classList.add('current') + document.addEventListener('scroll', (event) => { + let curr = tags.map(x => [x, (x.getBoundingClientRect().top + x.getBoundingClientRect().bottom) / 2]).filter(x => x[1] >= 0).sort((a,b) => a[1] > b[1])[0][0]; + let idx = tags.sort((a,b) => a.getBoundingClientRect().top > b.getBoundingClientRect().top).map((x, idx) => [idx, x]).filter(x => x[1] == curr)[0][0]; + Array.from(document.getElementsByClassName('headingr')).map(x => x.classList.remove('current')) + document.getElementById('headingr-'+idx).classList.add('current') + }); + ") + ] + [ + #context if is-html() { + html.elem("style", " + .table-of-contents > p > span { width: 100%; } + ") + } + ]) +} diff --git a/components/pcb-view.typ b/components/pcb-view.typ new file mode 100644 index 0000000..1628c62 --- /dev/null +++ b/components/pcb-view.typ @@ -0,0 +1,24 @@ +#import "../common.typ": * + +#let pcb(front,back, size-percent:100) = { + context if is-html() { + if is-web { + html.elem("img", attrs:(draggable:"false", tite:"Click Me!", src:front, data-other:back, onclick:"swapFrontBack(this);", style:"width:"+str(size-percent)+"%; cursor:pointer;")) + } else { + html.elem("img", attrs:(src:front, style:"width:100%;")) + } + } else { + [#image(front) #image(back)] + } + context if is-html() and is-web { + html-script(" + function swapFrontBack(img) { + let oldsrc = img.src; + img.src = img.dataset.other; + img.dataset.other = oldsrc; + } + ") + } +} + + diff --git a/core-page-style.typ b/core-page-style.typ new file mode 100644 index 0000000..ea8b418 --- /dev/null +++ b/core-page-style.typ @@ -0,0 +1,115 @@ +#import "common.typ": * + + +#let small-font-size = 14pt +#let default-font-size = 17pt + +#let core-page-style(content) = {[ + + +#show: x => context { + set page(width: auto, height: auto) if is-web and not is-html() + set page(paper: "a4") if not is-web and not is-html() + x +} + +#set text(font: "DejaVu Sans Mono", size: default-font-size) + +#show raw: it => box( + stroke: black, + radius: 2pt, + inset: if is-html() { 1.4pt } else { 4pt }, + outset: 0pt, + baseline: 3.1pt, + text(it) +) + +#show box: it => { + context if is-html() { + html.elem("span", attrs: (style: css-style(it)))[#it.body] + } else { + it + } +} + +#show underline: it => { + context if is-html() { + html.elem("u", it.body) + } else { + it + } +} + +#show heading: it => underline[#it #v(3pt)] + +#set underline(stroke: 1pt, offset: 2pt) + +#show footnote: it => if is-web { [] } else { it } +#show footnote.entry: it => if is-web { [] } else { it } + +#context if is-html() { +html.elem("style", " + @font-face { + font-family: 'DejaVu Sans Mono'; + src: url('res/DejaVuSansMono-Bold.woff2') format('woff2'), + url('res/DejaVuSansMono-Bold.woff') format('woff'), + local('DejaVu Sans Mono'), + local('Courier New'), + local(Courier), + local(monospace); + font-weight: bold; + font-style: normal; + font-display: swap; + } + + @font-face { + font-family: 'DejaVu Sans Mono'; + src: url('res/DejaVuSansMono.woff2') format('woff2'), + url('res/DejaVuSansMono.woff') format('woff'), + local('DejaVu Sans Mono'), + local('Courier New'), + local(Courier), + local(monospace); + font-weight: normal; + font-style: normal; + font-display: swap; + } + + body { + font-family: DejaVu Sans Mono; + font-size: "+len2css(default-font-size)+"; + } + + td { + width: 100%; + display: inline; + vertical-align: top; + } + + h1,h2,h3,h4 { + margin-top: 1%; + margin-bottom: 0.75%; + " + if is-web { "margin-left: -0.75%;" } else { "" } + + + " + } + + p { + margin-top: 0.75%; + margin-bottom: 0.75%; + } + + ul { + margin-top: 0%; + } + + .current { + font-weight: bold; + } +") +} + + +#content +] +} diff --git a/pages/article-make-regex-engine-1.typ b/pages/article-make-regex-engine-1.typ new file mode 100644 index 0000000..baf1f54 --- /dev/null +++ b/pages/article-make-regex-engine-1.typ @@ -0,0 +1,224 @@ +#import "../common.typ": * +#import "../simple-page-layout.typ": * +#import "../core-page-style.typ": * + +#simple-page( + gen-table-of-contents: true +)[ + +#section[ + #title[Making a simple RegEx engine] + + #title[Part 1: Introduction to RegEx] + + #sized-p(small-font-size)[ + Written by alex_s168 + ] +] + +#if is-web {section[ + Note that the #min-pdf-link[PDF Version] of this page might look a bit better styling wise. +]} + +#section[ + = Introduction + If you are any kind of programmer, + you've probably heard of #flink("https://en.wikipedia.org/wiki/Regular_expression", "RegEx") + + RegEx (Regular expression) is kind of like a small programming language + used to define string search and replace patterns. + + \ + RegEx might seem overwhelming at first, but you can learn the most important features of RegEx very quickly. + + \ + It is important to mention that there is not a single standard for RegEx syntax, + but instead each "implementation" has it's own quirks, and additional features. + Most common features however behave identically on most RegEx "engines"/implementations. +] + +#section[ + = Syntax + The behavior of RegEx expressions / patterns depends on the match options passed to the RegEx engine. + + Common match options: + - Anchored at start and end of line + - Case insensitive + - multi-line or instead whole string +] + +#section[ + == "Atoms" + In this article, we will refer to single expression parts as "atoms". +] + +#section[ + === Characters + Just use the character that you want to match. For example ```re a``` to match an `a`. + This however does not work for all characters, because many are part of special RegEx syntax. +] + +#section[ + === Escaped Characters + Thee previously mentioned special characters like `[` can be matched by putting a backslash in front of them: ```re \[``` + + #context html-frame[ + #table( + columns: (auto,auto), + table.header( + [Pattern], + [Description] + ), + + [```re \\```], [match a literal backslash], + [```re \n```], [match a new-line], + ) + ] +] + +#section[ + === Character Groups + RegEx engines already define some groups of characters that can make writing RegEx expressions quicker. + + #context html-frame[ + #table( + columns: (auto,auto), + table.header( + [Pattern], + [Description], + ), + + [```re .```], [any character except for line breaks], + [```re \s```], [any whitespace or line break], + [```re \S```], [any character except whitespaces or line breaks], + [```re \d```], [any digit from 0 to 9], + [```re \D```], [any character except digits from 0 to 9], + [```re \w```], [a letter, digit, or underscore], + [```re \W```], [any character except for letters, digits, and underscores], + ) + ] +] + +#section[ + === Anchors + ```re ^``` is used to assert the beginning of a line in multi-line mode, + or the beginning of the string in whole-string mode. + + ```re $``` is used to assert the end of a line in multi-line mode, + or the end of the string in whole-string mode. + + The behaviours of these depend on the #slink()[match options] +] + +#section[ + == Greedy VS Lazy + Some combinators will either match "lazy", or "greedy". + + Lazy is when the engine only matches as many characters required to get to the next step. + This should almost always be used. + + Greedy matching is when the engine tries to match as many characters as possible. + The problem with this is that it might cause "backtracking", + which happens when the engine goes back in the pattern multiple times to ensure that as many characters + as possible where matched. This can cause big performance issues. +] + +#section[ + == Combinators + Multiple atoms can be combined together to form more complex patterns. +] + +#section[ + === Chain + When two expressions are next to each other, they will be chained together, + which means that both will be evaluated in-order. + + Example: ```re x\d``` matches a `x` and then a digit, like for example `x9` +] + +#section[ + === Or + Two expressions separated by a `|` cause the RegEx engine to first try to match the left side, + and only if it fails, it tries the right side instead. + + Note that "or" has a long left and right scope, + which means that ```re ab|cd``` will match either ```re ab``` or ```re cd``` +] + +#section[ + === Or-Not + Tries to match the expression on the left to it, but won't error if it doesn't succeed. + + Note that "or-not" has a short left scope, + which means that ```re ab?``` will always match ```re a```, and then try to match ```re b``` +] + +#section[ + === Repeated + A expression followed by either a ```re *``` for #slink()[greedy] repeat, + or a ```re *?``` for #slink()[lazy] repeat. + + This matches as many times as possible, but can also match the pattern zero times. + + Note that this has a short left scope. +] + +#section[ + === Repeated At Least Once + A expression followed by either a ```re +``` for #slink()[greedy] repeat, + or a ```re +?``` for #slink()[lazy] repeat. + + This matches as many times as possible, and at least one time. + + Note that this has a short left scope. +] + +#section[ + === (Non-Capture) Group + Groups multiple expressions together for scoping. + + Example: ```re (?:abc)``` will just match `abc` +] + +#section[ + === Capture Group + Similar to #slink()[Non-Capture Groups] except that they capture the matched text. + This allows the matched text of the inner expression to be extracted later. + + Capture group IDs are enumerated from left to right, starting with 1. + + Example: ```re (abc)de``` will match `abcde`, + and store `abc` in group 1. +] + +#section[ + === Character Set + By surrounding multiple characters in square brackets, + the engine will match any of them. + Special characters or expressions won't be parsed inside them, + which means that this can also be used to escape characters. + + For example: ```re [abc]``` will match either `a`, `b` or `c`. + + and ```re [ab(?:c)]``` will match either `a`, `b`, `(`, `?`, `:`, `c`, or `)`. + + #slink()[Character groups] and #slink()[escaped characters] + still work inside character sets. + + Character sets can also contain ranges. + For example: ```re [0-9a-z]``` will match either any digit, or any lowercase letter. +] + +#section[ + = Conclusion + RegEx is perfect for when you just want to match some patterns, + but the syntax can make patterns very hard to read or modify. + + In the next article, we will start to dive into implementing RegEx. + + Stay tuned! +] + + + +] diff --git a/pages/index.typ b/pages/index.typ new file mode 100644 index 0000000..631caf3 --- /dev/null +++ b/pages/index.typ @@ -0,0 +1,81 @@ +#import "../common.typ": * +#import "../simple-page-layout.typ": * + +#let gen-page(content) = { + core-page-style[ + #if is-web { + table( + stroke: none, + columns: (25%, 50%, 25%), + [], + [ + #html-style("position: absolute; left: 28%; width: 100%")[ + #box(width: 50%, content) + ] + ], + ) + } else { + content + } + ] +} + +#let tree-list(..elements) = { + gen-tree-from-headings(elemfn: (content, x) => [ + #html-opt-elem("p", (style:"line-height:1.1"))[ + #html-style("display:flex; text-indent:0pt;")[ + #html-style("margin-right: 11pt;", content) + #html-style("flex:1;", x.body) + ] + ] + ], elements.pos()) +} + +#gen-page[ + + #br() + #title[alex_s168's] + #br() + + Articles + #br() + #tree-list( + (level:1, body: [ Making a simple RegEx engine ]), + (level:2, body: html-href("article-make-regex-engine-1.typ.desktop.html")[ Part 1: Introduction to RegEx ]), + ) + #br() + + Socials + #br() + #tree-list( + (level:1, body: html-href("https://github.com/alex-s168")[ GitHub ]), + (level:1, body: [Discord: alex_s168]), + (level:1, body: html-href("mailto:alexandernutz68@gmail.com")[ E-Mail ]), + (level:1, body: html-href("https://codeberg.org/alex-s168")[ Codeberg ]), + ) + #br() + + Working on + #br() + #tree-list( + (level:1, body: [ Programming languages and compilers ]), + (level:2, body: [ #link("https://github.com/vxcc-backend/vxcc")[ vxcc-old ]: (discontinued) Simple optimizing compiler backend ]), + (level:2, body: [ #link("https://github.com/alex-s168/uiuac")[ uiuac ]: (discontinued) Optimizing compiler for the #link("https://uiua.org")[Uiua programming language] ]), + (level:2, body: [ #link("https://github.com/Lambda-Mountain-Compiler-Backend/lambda-mountain")[ LSTS's standard library ] ]), + (level:2, body: [ #link("https://github.com/h6-lang/h6")[ h6 ]: Minimal stack-based programming language ]), + (level:2, body: [ #link("https://github.com/alex-s168/lil-rs")[ lil-rs ]: Rust implementation of #link("http://beyondloom.com/decker/lil.html")[lil] ]), + + (level:1, body: [ Misc. ]), + (level:2, body: [ #link("https://github.com/alex-s168/tpre")[ tpre ]: Fast and minimal RegEx engine ]), + + (level:1, body: [ PCBs ]), + (level:2, body: [ #link("project-etc-nand.typ.desktop.html")[ etc-nand ]: #link("https://github.com/ETC-A/etca-spec/")[ ETC.A ] CPU from NAND gates ]), + + (level:1, body: [ FPGA designs ]), + (level:2, body: [ RMII MAC in #link("https://www.chisel-lang.org/")[ Chisel ] ]), + ) + + #br()#br() + This website is written almost entierly in typst. + +] diff --git a/pages/project-etc-nand.typ b/pages/project-etc-nand.typ new file mode 100644 index 0000000..19494ca --- /dev/null +++ b/pages/project-etc-nand.typ @@ -0,0 +1,84 @@ +#import "../common.typ": * +#import "../simple-page-layout.typ": * +#import "../components/pcb-view.typ": * + +#let pcb-size-percent = 80 +#let qpcb(file) = { + let p = res-path()+"etc-nand/"+file + pcb(p+"_front.png", p+"_back.png", size-percent: pcb-size-percent) +} + +#simple-page( + gen-table-of-contents: true +)[ + + +#section[ + #title[ etc-nand ] +] + +#if is-web {section[ + Note that the #min-pdf-link[PDF Version] of this page might look a bit better styling wise. + + You can click the PCB images to switch to the other side. +]} + +#section[ + = Overview + + etc-nand is a real-world #link("https://github.com/ETC-A/etca-spec/")[ ETC.A ] CPU built from almost only quad NAND gate ICs (74hc00) + + It will probably be finished in a few months. +] + +#section[ + == Estimates + + Estimated gate count: + - 2800 NAND gates + - 320 tristate buffers + + #br() + Estimated component counts: + - 700x 74hc00 quad NAND gates + - 40x 74HC54 octal tristate buffers + - a few simple resistors +] + +#section[ + == Planned Specifications + ETC.A base instruction set + byte operations + S&F + Von Neumann + + The CPU will communicate with peripherals over a 16 bit data + 15 bit address memory bus +] + +#section[ + = Purchase + You will be able to purchase one in the future. + + Stay tuned! +] + +#section[ + = Images + Images of PCBs that are either already manifactured or currently beeing manifactured by JLCPCB. +] + +#section[ + == 16 bit register + #context qpcb("reg16") +] + +#section[ + == 8 bit ALU slice + A #link()[8 bit adder module] will be placed in the middle + #context qpcb("alu8") +] + +#section[ + == 8 bit adder + #context qpcb("add8") +] + + +] diff --git a/res/etc-nand/add8_back.png b/res/etc-nand/add8_back.png new file mode 100644 index 0000000..107fcc4 Binary files /dev/null and b/res/etc-nand/add8_back.png differ diff --git a/res/etc-nand/add8_front.png b/res/etc-nand/add8_front.png new file mode 100644 index 0000000..d50ab6a Binary files /dev/null and b/res/etc-nand/add8_front.png differ diff --git a/res/etc-nand/alu8_back.png b/res/etc-nand/alu8_back.png new file mode 100644 index 0000000..9c3a732 Binary files /dev/null and b/res/etc-nand/alu8_back.png differ diff --git a/res/etc-nand/alu8_front.png b/res/etc-nand/alu8_front.png new file mode 100644 index 0000000..a68f2d8 Binary files /dev/null and b/res/etc-nand/alu8_front.png differ diff --git a/res/etc-nand/reg16_back.png b/res/etc-nand/reg16_back.png new file mode 100644 index 0000000..bcdc811 Binary files /dev/null and b/res/etc-nand/reg16_back.png differ diff --git a/res/etc-nand/reg16_front.png b/res/etc-nand/reg16_front.png new file mode 100644 index 0000000..6d58a97 Binary files /dev/null and b/res/etc-nand/reg16_front.png differ diff --git a/simple-page-layout.typ b/simple-page-layout.typ new file mode 100644 index 0000000..1584f36 --- /dev/null +++ b/simple-page-layout.typ @@ -0,0 +1,47 @@ +#import "common.typ": * +#import "core-page-style.typ": * + +#let html-href(target, content) = { + [#context if is-html() { + html.elem("a", attrs:(href:target), content) + } else { content }] +} + +#let min-pdf-link(content) = { + [#context if is-html() { + html.elem("a", attrs:(href:"#",onclick:"gotoMinPdf();"), content) + } else { content }] +} + +#let simple-page(gen-table-of-contents: true, gen-index-ref: true, content) = { + core-page-style[ + #if is-web { + table( + stroke: none, + columns: (25%, 50%, 25%), + column-fixed( + [#if gen-table-of-contents { [#table-of-contents()] }], + min-pdf-link("Minimal PDF Version"), + [#if gen-index-ref {[ + #context br() + #context html-href("index.html")[#html-bold[Website Home]] + ]}] + ), + [ + #let off = 3; + #html-style("position: absolute; left: "+str(25+off)+"%; width: "+str(75-off)+"%")[ + #box(width: 50%, content) + ] + ], + ) + } else { + content + } + + #html-script(" + function gotoMinPdf() { + window.location.href = window.location.href.replace(/\.\w+.html/g, '.min.pdf'); + } + ") + ] +}