commit 85d05c959516458887303f0e79469e82a1be057e Author: LordBaryhobal Date: Fri Dec 13 23:04:02 2024 +0100 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..f98bd4a --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# Typstuff, stuff I made with Typst + +This repository lists some of the stuff I have made using Typst + +Smaller scripts will be included directly in this repository + +--- + +### Table of contents + +- [RIVET](#rivet) +- [Chronos](#chronos) +- [Circuiteria](#circuiteria) +- [Functastic](#functastic) +- [Math tools](#math-tools) +- [Sankeynoa](#sankeynoa) +- [RISCV Utilities](#riscv-utilities) +- [Simple UML class diagrams](#simple-uml-class-diagrams) +- [Tasty TacOS](#tasty-tacos) +- [Leetcode trees](#leetcode-trees) +- [Cafeteria menus](#cafeteria-menus) +- [Avent of Code](#avent-of-code) + + +## RIVET +*Links*: +[Repo](https://git.kb28.ch/HEL/rivet-typst) / +[Universe](https://typst.app/universe/package/rivet) + +*Description*: +Register / Instruction Visualizer & Explainer Tool with Typst, using CeTZ + +

+ +

+ +## Chronos +*Links*: +[Repo](https://git.kb28.ch/HEL/chronos) / +[Universe](https://typst.app/universe/package/chronos) + +*Description*: +A Typst package to draw sequence diagrams with CeTZ + +

+ +

+ +## Circuiteria +*Links*: +[Repo](https://git.kb28.ch/HEL/circuiteria) / +[Universe](https://typst.app/universe/package/circuiteria) + +*Description*: +Drawing block circuits with Typst made easy, using CeTZ + +

+ +

+ +## Functastic +*Links*: +[Repo](https://git.kb28.ch/HEL/functastic) + +*Description*: +A Typst package to create sign tables of mathematical functions + +

+ +

+ +## Math tools +*Links*: +[Repo](https://git.kb28.ch/HEL/math-tools) + +*Description*: +Useful mathematical functions + +

+ +

+ +## Sankeynoa +*Links*: +[Repo](https://git.kb28.ch/HEL/sankeynoa) + +*Description*: +Beautiful Sankey diagrams using CeTZ + +

+ +

+ +## RISCV Utilities +*Links*: +[Directory](./misc/riscv/) + +*Description*: +Some RISCV utilities to show instructions + +

+ +

+ +## Simple UML class diagrams +*Links*: +[Directory](./misc/uml/) + +*Description*: +Functions do make simple class diagrams and directly parse UML + +

+ +

+ +## Tasty TacOS +*Links*: +[Repo](https://git.kb28.ch/HEL/tasty-tacos) + +*Description*: +Various algorithms from the OS course + +

+ +

+ +## Leetcode trees +*Links*: +[Directory](./misc/leetcode_trees/) + +*Description*: +Function to easily display leetcode tree exercises in a CeTZ canvas + +

+ +

+ +## Cafeteria menus +*Links*: +[Directory](./misc/menus/) + +*Description*: +Cafeteria menus for the week, used by a Discord and a Telegram bot + +

+ +

+ +## Avent of Code +*Links*: +[Repo](https://git.kb28.ch/HEL/AdventOfCode2024) + +*Description*: +My attempt at Advent of Code (2024) + +

+ +

+ + + diff --git a/images/aoc.png b/images/aoc.png new file mode 100644 index 0000000..b9b74b9 Binary files /dev/null and b/images/aoc.png differ diff --git a/images/chronos.png b/images/chronos.png new file mode 100644 index 0000000..f771ad5 Binary files /dev/null and b/images/chronos.png differ diff --git a/images/circuiteria.png b/images/circuiteria.png new file mode 100644 index 0000000..cab4f00 Binary files /dev/null and b/images/circuiteria.png differ diff --git a/images/functastic.png b/images/functastic.png new file mode 100644 index 0000000..941e832 Binary files /dev/null and b/images/functastic.png differ diff --git a/images/leetcode_trees.png b/images/leetcode_trees.png new file mode 100644 index 0000000..022bf6b Binary files /dev/null and b/images/leetcode_trees.png differ diff --git a/images/math_tools.png b/images/math_tools.png new file mode 100644 index 0000000..60c5dda Binary files /dev/null and b/images/math_tools.png differ diff --git a/images/menus.png b/images/menus.png new file mode 100644 index 0000000..0bcba75 Binary files /dev/null and b/images/menus.png differ diff --git a/images/riscv.png b/images/riscv.png new file mode 100644 index 0000000..06f5619 Binary files /dev/null and b/images/riscv.png differ diff --git a/images/rivet.png b/images/rivet.png new file mode 100644 index 0000000..6ad8447 Binary files /dev/null and b/images/rivet.png differ diff --git a/images/sankeynoa.png b/images/sankeynoa.png new file mode 100644 index 0000000..1566416 Binary files /dev/null and b/images/sankeynoa.png differ diff --git a/images/tasty_tacos.png b/images/tasty_tacos.png new file mode 100644 index 0000000..d0283ae Binary files /dev/null and b/images/tasty_tacos.png differ diff --git a/images/uml.png b/images/uml.png new file mode 100644 index 0000000..b0910ff Binary files /dev/null and b/images/uml.png differ diff --git a/misc/blobs/blobs.typ b/misc/blobs/blobs.typ new file mode 100644 index 0000000..8d9e0cc --- /dev/null +++ b/misc/blobs/blobs.typ @@ -0,0 +1,67 @@ +#import "@preview/cetz:0.3.1": draw + +#let rand-consts = ( + a: 1103515245, + c: 12345, + m: (1).bit-lshift(31) +) +#let random(seed) = { + let seed = calc.rem(rand-consts.a * seed + rand-consts.c, rand-consts.m) + let value = seed / rand-consts.m + return (seed, value) +} + +#let draw-blob( + seed, + center, + radius, + n-pts: 8, + curviness: 0.4, + debug: false, + ..args +) = { + let pts = () + let angle-step = 360deg / n-pts + let seed = seed + let v + + if debug { + draw.circle(center, radius: radius, stroke: (dash: "dashed")) + } + for i in range(n-pts) { + (seed, v) = random(seed) + let angle = angle-step * (i + v - 0.5) + + (seed, v) = random(seed) + let f = calc.rem(i, 2) * 2 - 1 + let r = radius * (1 + f * v * curviness) + + let dx = r * calc.cos(angle) + let dy = r * calc.sin(angle) + let pt = (center.first() + dx, center.last() + dy) + pts.push(pt) + + if debug { + draw.circle( + pt, + radius: .1, + stroke: none, + fill: black + ) + draw.line( + center, + (rel: center, to: (angle-step * i, radius)), + stroke: (dash: "dashed") + ) + } + } + + let name = args.named().at("name", default: none) + draw.group(name: name, { + draw.hobby(..pts, close: true, ..args) + if name != none { + draw.copy-anchors(name) + draw.anchor("center", center) + } + }) +} diff --git a/misc/blobs/main.png b/misc/blobs/main.png new file mode 100644 index 0000000..c571e30 Binary files /dev/null and b/misc/blobs/main.png differ diff --git a/misc/blobs/main.typ b/misc/blobs/main.typ new file mode 100644 index 0000000..71efdeb --- /dev/null +++ b/misc/blobs/main.typ @@ -0,0 +1,79 @@ +#import "@preview/cetz:0.3.1": canvas, draw +#import "blobs.typ": draw-blob + +#set page(width: auto, height: auto, margin: 1cm) + +#canvas({ + draw-blob( + 2, (0, 0), 1.5, + stroke: green, + name: "blob1" + ) + + draw-blob( + 8, (5, 0), 1.5, + n-pts: 10, + stroke: orange, + name: "blob2" + ) + + draw.circle( + "blob1.center", + radius: .05, + fill: black, + stroke: none, + name: "x" + ) + + draw.circle( + "blob2.center", + radius: .05, + fill: black, + stroke: none, + name: "y" + ) + + draw.content( + "x", + [$arrow(x)$], + anchor: "east", + padding: 3pt + ) + + draw.content( + "y", + [$arrow(y)$], + anchor: "west", + padding: 3pt + ) + + draw.content( + (-1.2, .8), + text(fill: green)[$RR^n$] + ) + + draw.content( + (4.6, 1.4), + text(fill: orange)[$RR^m$] + ) + + let mid = (2.5, .5) + + draw.bezier-through( + "x.north-east", mid, "y.north-west", + stroke: blue + .5pt, + mark: (end: ">", fill: blue), + name: "arrow" + ) + draw.content( + mid, + [$f$], + anchor: "south", + padding: 3pt + ) + + draw.content( + (2.5, -1), + text(fill: blue)[$f: RR^n -> RR^m$] + ) +}) \ No newline at end of file diff --git a/misc/leetcode_trees/leetcode_trees.pdf b/misc/leetcode_trees/leetcode_trees.pdf new file mode 100644 index 0000000..614c947 Binary files /dev/null and b/misc/leetcode_trees/leetcode_trees.pdf differ diff --git a/misc/leetcode_trees/leetcode_trees.typ b/misc/leetcode_trees/leetcode_trees.typ new file mode 100644 index 0000000..cb39449 --- /dev/null +++ b/misc/leetcode_trees/leetcode_trees.typ @@ -0,0 +1,97 @@ +#import "@preview/cetz:0.3.1": canvas, draw + +#let leetcode-tree(nodes, highlighted: ()) = { + let height = calc.ceil( + calc.log( + nodes.len() + 1, + base: 2 + ) + ) + + let dy = 1 + let dx = 0.8 + let r = 0.3 + let width = dx * (calc.pow(2, height - 1) + 1) + + for lvl in range(height) { + let n = calc.pow(2, lvl) + let y = -lvl * dy + + for i in range(n) { + let j = i + n - 1 + if j >= nodes.len() { + break + } + + let node = nodes.at(j) + if node != none { + let x = width * (i + .5) / n + let name = "l" + str(lvl) + "-n" + str(i) + let is-highlighted = (j in highlighted) or (lvl, i) in highlighted + draw.circle( + (x, y), + radius: r, + name: name, + fill: if is-highlighted { + blue.lighten(75%) + } else { + none + } + ) + draw.content( + name + ".center", + str(node) + ) + + if lvl != 0 { + let pi = i.bit-rshift(1) + draw.line( + "l" + str(lvl - 1) + "-n" + str(pi), + name + ) + } + } + } + } +} + +#set page(width: auto, height: auto, margin: 1cm) + +#canvas({ + leetcode-tree( + (2,1,3,none,none,none,4), + highlighted: (2,) + ) +}) + +#pagebreak() + +#canvas({ + leetcode-tree( + (1,2,3,4,5,6,none,none,none,7,8) + ) +}) + +#pagebreak() + +#canvas({ + leetcode-tree( + (99,3,2,none,6,4,5,none,none,none,none,8,7) + ) +}) + +#pagebreak() + +#canvas({ + leetcode-tree( + (0,3,1,none,none,none,2) + ) +}) + +#pagebreak() + +#canvas({ + leetcode-tree( + (0,3,1,2) + ) +}) \ No newline at end of file diff --git a/misc/menus/menu.json b/misc/menus/menu.json new file mode 100644 index 0000000..e86bc2c --- /dev/null +++ b/misc/menus/menu.json @@ -0,0 +1,65 @@ +[ + { + "date": "2024-09-09", + "menus": [ + { + "name": "Risotto à la courge", + "extra": [ + "Chips de courge", + "Fromage râpé" + ], + "prices": { + "E": 8.1, + "C": 11.0, + "D": 9.9, + "V": 12.6 + } + }, + { + "name": "Ragoût de cerf", + "extra": [ + "Spätzli au beurre", + "Choux de Bruxelles et chou rouge" + ], + "prices": { + "E": 11.5, + "C": 14.4, + "D": 13.3, + "V": 16.0 + } + } + ] + }, + { + "date": "2024-09-10", + "menus": [ + { + "name": "Rösti aux champignons", + "extra": [ + "Sauce crême", + "Salade du chef" + ], + "prices": { + "E": 7.0, + "C": 9.0, + "D": 8.0, + "V": 10.0 + } + }, + { + "name": "Boulette de poulet", + "extra": [ + "Sauce alfredo", + "Pâtes spaghetti", + "Jardinière de légumes" + ], + "prices": { + "E": 10.5, + "C": 13.4, + "D": 12.3, + "V": 15.0 + } + } + ] + } +] \ No newline at end of file diff --git a/misc/menus/menu.png b/misc/menus/menu.png new file mode 100644 index 0000000..0bcba75 Binary files /dev/null and b/misc/menus/menu.png differ diff --git a/misc/menus/menu.typ b/misc/menus/menu.typ new file mode 100644 index 0000000..302d0b5 --- /dev/null +++ b/misc/menus/menu.typ @@ -0,0 +1,202 @@ +#let path = sys.inputs.at("path", default: "menu.json") +#let all-combos = sys.inputs.at("all-combos", default: "false") == "true" +#let categories = sys.inputs.at("categories", default: "E,D,C,V").split(",") +#let dark = sys.inputs.at("dark", default: "false") == "true" +#let days = json(path) +#let nbsp = sym.space.nobreak + +#let colors = if dark {( + bg: rgb("#1f1f24"), + text: white, + categories: rgb("#70c947"), + menu-sep: rgb("#ababab"), + title-underline: rgb("#eb2324"), + day-sep: rgb("#a3cf40") +)} else {( + bg: white, + text: black, + categories: rgb("#577F25"), + menu-sep: rgb("#ababab"), + title-underline: rgb("#e34243"), + day-sep: rgb("#82BC00") +)} + +#let width = if days.len() > 1 {20cm} else {10cm} + +#set page( + margin: 0cm, + height: auto, + width: width, + fill: colors.bg +) + +#set text( + fill: colors.text +) + +#let parse-date(date-txt) = { + let (year, month, day) = date-txt.split("-") + return datetime(year: int(year), month: int(month), day: int(day)) +} + +#let fmt-number( + number, + decimals: 2, + decimal-sep: ".", + thousands-sep: "'" +) = { + let integer = calc.trunc(number) + let decimal = calc.fract(number) + let res = str(integer).clusters() + .rev() + .chunks(3) + .map(c => c.join("")) + .join(thousands-sep) + .rev() + + if decimals != 0 { + res += decimal-sep + decimal = decimal * calc.pow(10, decimals) + decimal = calc.round(decimal) + decimal = str(decimal) + ("0" * decimals) + res += decimal.slice(0, decimals) + } + + return res +} + +#let day-names = ( + "Lundi", + "Mardi", + "Mercredi", + "Jeudi", + "Vendredi", + "Samedi", + "Dimanche" +) + +#let display-menus( + days, + categories, + day-sep-stroke: colors.day-sep + 1pt, + title-underline-stroke: colors.title-underline + 2pt, + menu-sep-stroke: colors.menu-sep + 1pt, + price-categ-color: colors.categories +) = { + set text(font: "Liberation Sans", hyphenate: true) + let cols = calc.min(2, days.len()) + + let cells = () + + for day in days { + let date = parse-date(day.date) + let weekday = date.weekday() - 1 + let day-name = day-names.at(weekday) + let menus = () + for menu in day.menus { + let price-cell = grid.cell(rowspan: 2)[] + let prices = menu.at( + "prices", + default: (:) + ) + if menu.keys().contains("price") { + prices = ("": menu.price) + } + + let pairs = prices.pairs() + .filter(p => p.first() in categories) + .sorted(key: p => p.last()) + + let prices = pairs.map(p => { + let categ = emph( + text( + fill: price-categ-color, + p.first() + ) + ) + let price = fmt-number(p.last()) + categ + ":" + nbsp + "CHF" + nbsp + price + }) + price-cell = grid.cell( + rowspan: menu.extra.len() + 1, + stack(dir: ttb, spacing: .4em, ..prices) + ) + //let price = "CHF" + sym.space.nobreak + str(menu.price) + menus.push( + grid( + columns: (1fr, auto), + column-gutter: .4em, + row-gutter: .4em, + [*#menu.name*], + price-cell, + ..menu.extra.map(l => text(10pt, l)) + ) + ) + } + + let title = align( + center, + stack( + dir: ttb, + spacing: .5em, + text(size: 14pt, weight: "bold", day-name), + text(size: 10pt, date.display("[day].[month]")) + ) + ) + let title-box = box( + width: 50%, + inset: .6em, + stroke: (bottom: title-underline-stroke), + title + ) + let title-cell = grid.cell(align: center, title-box) + + cells.push(box(width: 100%, grid( + columns: (100%), + inset: (x, y) => ( + top: if y == 0 {0pt} else {.8em}, + bottom: if y == 0 {0pt} else {2em} + ), + stroke: (_, y) => (top: if y < 2 {none} else {menu-sep-stroke}), + title-cell, + ..menus + ))) + } + + if calc.rem(cells.len(), 2) == 1 { + cells.last() = grid.cell(colspan: calc.min(2, cols), cells.last()) + } + + grid( + columns: (100% / cols,) * cols, + inset: (x: 1.5em, y: .8em), + stroke: (x, y) => { + let borders = (:) + if x != 0 { + borders.insert("left", day-sep-stroke) + } + if y != 0 { + borders.insert("top", day-sep-stroke) + } + return borders + }, + ..cells + ) +} + +#{ + if all-combos { + let n-combos = calc.pow(2, categories.len()) + for i in range(n-combos) { + let filtered-categories = categories.enumerate().filter(p => { + let (j, categ) = p + return i.bit-rshift(j).bit-and(1) != 0 + }).map(p => p.last()) + display-menus(days, filtered-categories) + pagebreak(weak: true) + } + + } else { + display-menus(days, categories) + } +} diff --git a/misc/menus/menu_dark.png b/misc/menus/menu_dark.png new file mode 100644 index 0000000..ceb2433 Binary files /dev/null and b/misc/menus/menu_dark.png differ diff --git a/misc/menus/menu_student.png b/misc/menus/menu_student.png new file mode 100644 index 0000000..4d71d88 Binary files /dev/null and b/misc/menus/menu_student.png differ diff --git a/misc/riscv/main.pdf b/misc/riscv/main.pdf new file mode 100644 index 0000000..32e6112 Binary files /dev/null and b/misc/riscv/main.pdf differ diff --git a/misc/riscv/main.typ b/misc/riscv/main.typ new file mode 100644 index 0000000..2482622 --- /dev/null +++ b/misc/riscv/main.typ @@ -0,0 +1,39 @@ +#import "riscv.typ" + +#set page(width: auto, height: auto, margin: 1cm) + +#riscv.explain-instruction-encoding("lui x0, 0x12") + +#riscv.explain-instruction-encoding("add sp, a0, a1") + +#pagebreak() + +#riscv.riscv-to-bin(``` +0x00000300 main: jal ra, simple # call +0x00000304 add s0, s1, s1 +0x0000051c simple: jalr x0, ra, 0 # return +```) + +#pagebreak() + +#riscv.riscv-to-bin(``` +f1: + addi sp, sp, -20 # make space on stack for 5 words + sw a0, 16(sp) + sw a1, 12(sp) + sw ra, 8(sp) # save ra on stack + sw s4, 4(sp) + sw s5, 0(sp) + jal ra, f2 + lw ra, 8(sp) # restore ra (and other regs) from stack + addi sp, sp, 20 # deallocate stack space + jalr zero, ra, 0 + +# f2 (leaf-function) only uses s4 and calls no functions +f2: + addi sp, sp, -4 # make space on stack for 1 word + sw s4, 0(sp) + lw s4, 0(sp) + addi sp, sp, 4 # deallocate stack space + jalr zero, ra, 0 +```) \ No newline at end of file diff --git a/misc/riscv/riscv.typ b/misc/riscv/riscv.typ new file mode 100644 index 0000000..a08682f --- /dev/null +++ b/misc/riscv/riscv.typ @@ -0,0 +1,455 @@ +#let colors = ( + address: rgb(228, 103, 103), + label: rgb(119, 97, 204), + op: rgb(235, 105, 40) +) + +#let opcodes = ( + "lui": 0b0110111, + "auipc": 0b0010111, + "jal": 0b1101111, + "jalr": 0b1100111, + "beq": 0b1100011, + "bne": 0b1100011, + "blt": 0b1100011, + "bge": 0b1100011, + "bltu": 0b1100011, + "bgeu": 0b1100011, + "lb": 0b0000011, + "lh": 0b0000011, + "lw": 0b0000011, + "lbu": 0b0000011, + "lhu": 0b0000011, + "sb": 0b0100011, + "sh": 0b0100011, + "sw": 0b0100011, + "addi": 0b0010011, + "slti": 0b0010011, + "sltiu": 0b0010011, + "xori": 0b0010011, + "ori": 0b0010011, + "andi": 0b0010011, + "slli": 0b0010011, + "srli": 0b0010011, + "srai": 0b0010011, + "add": 0b0110011, + "sub": 0b0110011, + "sll": 0b0110011, + "slt": 0b0110011, + "sltu": 0b0110011, + "xor": 0b0110011, + "srl": 0b0110011, + "sra": 0b0110011, + "or": 0b0110011, + "and": 0b0110011, +) + +#let types = ( + "lui": "U", + "auipc": "U", + "jal": "J", + "jalr": "I", + "beq": "B", + "bne": "B", + "blt": "B", + "bge": "B", + "bltu": "B", + "bgeu": "B", + "lb": "L", + "lh": "L", + "lw": "L", + "lbu": "L", + "lhu": "L", + "sb": "S", + "sh": "S", + "sw": "S", + "addi": "I", + "slti": "I", + "sltiu": "I", + "xori": "I", + "ori": "I", + "andi": "I", + "slli": "I", + "srli": "I", + "srai": "I", + "add": "R", + "sub": "R", + "sll": "R", + "slt": "R", + "sltu": "R", + "xor": "R", + "srl": "R", + "sra": "R", + "or": "R", + "and": "R", +) + +#let funct3 = ( + "jalr": 0b000, + "beq": 0b000, + "bne": 0b001, + "blt": 0b100, + "bge": 0b101, + "bltu": 0b110, + "bgeu": 0b111, + "lb": 0b000, + "lh": 0b001, + "lw": 0b010, + "lbu": 0b100, + "lhu": 0b101, + "sb": 0b000, + "sh": 0b001, + "sw": 0b010, + "addi": 0b000, + "slti": 0b010, + "sltiu": 0b011, + "xori": 0b100, + "ori": 0b110, + "andi": 0b111, + "slli": 0b001, + "srli": 0b101, + "srai": 0b101, + "add": 0b000, + "sub": 0b000, + "sll": 0b001, + "slt": 0b010, + "sltu": 0b011, + "xor": 0b100, + "srl": 0b101, + "sra": 0b101, + "or": 0b110, + "and": 0b111, +) + +#let funct7 = ( + "slli": 0b0000000, + "srli": 0b0000000, + "srai": 0b0100000, + "add": 0b0000000, + "sub": 0b0100000, + "sll": 0b0000000, + "slt": 0b0000000, + "sltu": 0b0000000, + "xor": 0b0000000, + "srl": 0b0000000, + "sra": 0b0100000, + "or": 0b0000000, + "and": 0b0000000, +) + +#let regs = ( + "zero": 0, "ra": 1, "sp": 2, "gp": 3, + "tp": 4, "t0": 5, "t1": 6, "t2": 7, + "s0": 8, "fp": 8, "s1": 9, "a0": 10, "a1": 11, + "a2": 12, "a3": 13, "a4": 14, "a5": 15, + "a6": 16, "a7": 17, "s2": 18, "s3": 19, + "s4": 20, "s5": 21, "s6": 22, "s7": 23, + "s8": 24, "s9": 25, "s10": 26, "s11": 27, + "t3": 28, "t4": 29, "t5": 30, "t6": 31 +) + +#let zfill(string, length, c: "0") = { + let res = c * length + string + return res.slice(-length) +} + +#let int-to-bin(value, bits) = { + if value < 0 { + let m = (1).bit-lshift(bits) - 1 + let v = str((-value).bit-xor(m) + 1, base: 2) + return zfill(v, bits, c: "1") + } + let v = str(value, base: 2) + return zfill(v, bits) +} + +#let parse-reg(value) = { + let number = value.last() + if not value.starts-with("x") { + number = regs.at(value) + } + return int(number) +} + +#let parse-imm(value) = { + let imm = eval(value) + assert(type(imm) == int) + return imm +} + +#let parse-addr(value) = { + let matches = value.match(regex("(.*)\((.*)\)")) + let imm = matches.captures.first() + let reg = matches.captures.last() + imm = parse-imm(imm) + reg = parse-reg(reg) + return (reg, imm) +} + +#let parse-args(instr, instr-type, args) = { + let parsed = (:) + let r1 = parse-reg(args.first()) + + if instr-type in ("R", "I", "L", "U", "J") { + parsed.insert("rd", r1) + } else if instr-type == "B" { + parsed.insert("rs1", r1) + } else if instr-type == "S" { + parsed.insert("rs2", r1) + } + + let r2 = none + let r3 = none + let imm = none + if instr-type == "R" { + r2 = parse-reg(args.at(1)) + r3 = parse-reg(args.at(2)) + } else if instr-type in ("I", "B") { + r2 = parse-reg(args.at(1)) + imm = parse-imm(args.at(2)) + } else if instr-type in ("U", "J") { + imm = parse-imm(args.at(1)) + } else if instr-type in ("L", "S") { + (r2, imm) = parse-addr(args.at(1)) + } + + if instr-type == "R" { + parsed.insert("rs1", r2) + parsed.insert("rs2", r3) + } else if instr-type in ("I", "L", "S") { + parsed.insert("rs1", r2) + parsed.insert("imm", imm) + } else if instr-type == "B" { + parsed.insert("rs2", r2) + parsed.insert("imm", imm) + } else if instr-type in ("U", "J") { + parsed.insert("imm", imm) + } + + return parsed +} + +#let format-imm(imm, sections) = { + let value = 0 + for section in sections { + if type(section) == int { + let bit = imm.bit-rshift(section).bit-and(1) + value = value.bit-lshift(1).bit-or(bit) + } else { + let (start, end) = section + if start > end { + (start, end) = (end, start) + } + let span = end - start + 1 + let mask = (1).bit-lshift(span) - 1 + let bits = imm.bit-rshift(start).bit-and(mask) + value = value.bit-lshift(span).bit-or(bits) + } + } + return value +} + +#let instr-to-bin(instruction) = { + let (instr, ..args) = instruction.split(" ") + args = args.join("").split(",").map(a => a.trim()) + instr = lower(instr) + let parts = () + let parts-names = () + let opcode = opcodes.at(instr) + let instr-type = types.at(instr) + let code = 0 + + let parsed-args = parse-args(instr, instr-type, args) + let imm = parsed-args.at("imm", default: none) + + let add-part(code, parts, part, bits) = { + code = code.bit-lshift(bits).bit-or(part) + parts.push(int-to-bin(part, bits)) + return (code, parts) + } + + if instr in ("slli", "srli", "srai") { + let f7 = funct7.at(instr) + (code, parts) = add-part(code, parts, f7, 7) + parts-names.push("funct7") + let v = format-imm(imm, ((4,0),)) + (code, parts) = add-part(code, parts, v, 5) + parts-names.push("shamt") + + } else if instr-type == "R" { + let v = funct7.at(instr) + parts-names.push("funct7") + (code, parts) = add-part(code, parts, v, 7) + } else if instr-type in ("I", "L") { + let v = format-imm(imm, ((11,0),)) + (code, parts) = add-part(code, parts, v, 12) + parts-names.push("imm[11:0]") + } else if instr-type == "S" { + let v = format-imm(imm, ((11, 5),)) + (code, parts) = add-part(code, parts, v, 7) + parts-names.push("imm[11:5]") + } else if instr-type == "B" { + let v = format-imm(imm, (12, (10, 5))) + (code, parts) = add-part(code, parts, v, 7) + parts-names.push("imm[12|10:5]") + } else if instr-type == "U" { + let v = format-imm(imm, ((19, 0),)) + (code, parts) = add-part(code, parts, v, 20) + parts-names.push("imm[31:12]") + } else if instr-type == "J" { + let v = format-imm(imm, (20, (10, 1), 11, (19, 12))) + (code, parts) = add-part(code, parts, v, 20) + parts-names.push("imm[20|10:1|11|19:12]") + } + + if instr-type in ("R", "S", "B") { + (code, parts) = add-part(code, parts, parsed-args.rs2, 5) + parts-names.push("rs2") + } + if instr-type in ("R", "I", "L", "S", "B") { + (code, parts) = add-part(code, parts, parsed-args.rs1, 5) + parts-names.push("rs1") + (code, parts) = add-part(code, parts, funct3.at(instr), 3) + parts-names.push("funct3") + } + + if instr-type in ("R", "I", "L", "U", "J") { + (code, parts) = add-part(code, parts, parsed-args.rd, 5) + parts-names.push("rd") + } else if instr-type == "S" { + let v = format-imm(imm, ((4, 0),)) + (code, parts) = add-part(code, parts, v, 5) + parts-names.push("imm[4:0]") + } else if instr-type == "B" { + let v = format-imm(imm, ((4, 1), 11)) + (code, parts) = add-part(code, parts, v, 5) + parts-names.push("imm[4:1|11]") + } + + (code, parts) = add-part(code, parts, opcode, 7) + parts-names.push("opcode") + + return ( + instruction: instruction, + parts: parts, + names: parts-names, + code: code + ) +} + +#let color-instruction(line) = { + let res = "" + if line.starts-with("0x") { + let (addr, ..rest) = line.split(" ") + line = rest.join(" ").trim() + res += text(raw(addr), fill: colors.address) + " " + } + + if ":" in line { + let label + (label, line) = line.split(":") + line = line.trim() + res += text(raw(label), fill: colors.label) + ": " + if line.len() == 0 { + return res + } + } + let (op, ..args) = line.split(" ") + args = args.join("").split(",").map(a => a.trim()) + args = args.map(a => raw(a)) + + res += text(raw(op), fill: colors.op) + " " + res += args.join(", ") + + return res +} + +#let explain-instruction-encoding(instruction) = { + let encoded = instr-to-bin(instruction) + let parts = encoded.parts + let names = encoded.names + let code = encoded.code + parts.zip(names).map(((v, n)) => $underbrace(#raw(v), #n)$).join(" ") + [ \= #raw(instruction) \= 0x#raw(zfill(str(code, base: 16), 8))] +} + +#let replace-label-refs(instr, labels, addr) = { + let (op, ..args) = instr.split(" ") + args = args.join("").split(",").map(a => a.trim()) + if op in ("beq", "bne", "blt", "bge", "bltu", "bgeu", "jal") { + let label = args.last() + let target = labels.at(label) + let offset = target - addr + args.last() = str(offset) + } + return op + " " + args.join(", ") +} + +#let riscv-to-bin(riscv, start-address: 0) = { + let src = riscv + if type(riscv) == raw or type(riscv) == content { + src = riscv.text + } + + let lines = src.split("\n") + .map(l => l.split("#").first().trim()) + .filter(l => l.len() != 0) + let labels = (:) + let addr = start-address + + let lines2 = () + let addresses = () + for line in lines { + let set-addr = none + if line.starts-with("0x") { + let rest + (set-addr, ..rest) = line.split(" ") + line = rest.join(" ") + addr = eval(set-addr) + } + + if ":" in line { + let label + (label, line) = line.split(":") + labels.insert(label, addr) + line = line.trim() + if line.len() == 0 { + continue + } + } + lines2.push(line.trim()) + addresses.push(addr) + if set-addr == none { + addr += 4 + } + } + + let cells = () + for (line, addr) in lines2.zip(addresses) { + let instr = replace-label-refs(line, labels, addr) + let encoded = instr-to-bin(instr) + let addr-txt = "0x" + zfill(str(addr, base: 16), 8) + let code-txt = "0x" + zfill(str(encoded.code, base: 16), 8) + addr-txt = raw(addr-txt) + addr-txt = text(addr-txt, fill: colors.address) + + + let label = "" + if addr in labels.values() { + label = labels.pairs().filter(p => p.last() == addr).first().first() + label += ":" + } + label = text(raw(label), fill: colors.label) + cells.push(addr-txt) + cells.push(label) + cells.push(raw(code-txt)) + cells.push(color-instruction(line)) + } + table( + align: left + horizon, + stroke: none, + columns: 4, + inset: 5pt, + ..cells + ) +} \ No newline at end of file diff --git a/misc/riscv/riscv_1.png b/misc/riscv/riscv_1.png new file mode 100644 index 0000000..06f5619 Binary files /dev/null and b/misc/riscv/riscv_1.png differ diff --git a/misc/riscv/riscv_2.png b/misc/riscv/riscv_2.png new file mode 100644 index 0000000..32ec6ed Binary files /dev/null and b/misc/riscv/riscv_2.png differ diff --git a/misc/riscv/riscv_3.png b/misc/riscv/riscv_3.png new file mode 100644 index 0000000..3412c92 Binary files /dev/null and b/misc/riscv/riscv_3.png differ diff --git a/misc/uml/main.pdf b/misc/uml/main.pdf new file mode 100644 index 0000000..5e6c1f5 Binary files /dev/null and b/misc/uml/main.pdf differ diff --git a/misc/uml/main.typ b/misc/uml/main.typ new file mode 100644 index 0000000..90fa6e0 --- /dev/null +++ b/misc/uml/main.typ @@ -0,0 +1,104 @@ +#import "uml.typ" + +#set page(width: auto, height: auto, margin: 1cm) + +#uml.diagram({ + import uml: * + + class("Person", abstract, { + attr("name", "str") + attr("phoneNumber", "str") + attr("emailAddress", "str") + method("purchaseParkingPass") + }) + class("Address", { + attr("street", "str") + attr("city", "str") + attr("state", "str") + attr("postalCode", "int") + attr("country", "str") + method("validate", "bool", private) + method("outputAsLabel", "str") + }) + class("Student", { + attr("studentNumber", "int") + attr("averageMark", "int") + method("isEligibleToEnroll", "bool", ("str",)) + method("getSeminarsTaken", "int") + }) + class("Professor", { + attr("salary", "int", package) + attr("staffNumber", "int", protected) + attr("yearsOfService", "int", private) + attr("numberOfClasses", "int") + }) +}) + +#pagebreak() + +#uml.diagram({ + import uml: * + + class("Pageable", interface, { + attr("UNKNOWN_N_OF_PAGES", "int", default: -1) + }) + + class("SQLStatement", { + method("executeQuery", "ResultSet", (sql: "String")) + method("isPoolable", "Boolean") + method("getQueryTimeout", "int") + method("clearWarnings") + }) + + class("SearchService", { + attr("config", "Configuration", private) + attr("engine", "SearchEngine", private) + method("search", "SearchResult", (query: "SearchRequest")) + method("createEngine", "SearchEngine", private, static) + }) + + class("SearchService", grouped: true, { + attr("config", "Configuration", private) + attr("engine", "SearchEngine", private) + method("search", "SearchResult", (query: "SearchRequest")) + method("createEngine", "SearchEngine", private, static) + }) +}) + +#pagebreak() + +#import "parser.typ": parse +#parse(``` +@class-grouped=true +class Button { + -theController : Controller* + +press() : void + +setTheController(p : Controller*) : void +} + +abstract class Controller { + -theLight : Light* + -lampState : boolean + +evButtonPressed() : void + +setTheLight(p : Light*) : void + -toggle() : void +} + +class Light { + +on() : void + +off() : void +} +```) + +#pagebreak() + +#parse(``` +class City { + +resources: HashMap[ResourceType, Int] + +globalHappiness: Double +} + +abstract class Building { + +getResourcesOnBuilt(): HashMap[ResourceType, Int] +} +```) \ No newline at end of file diff --git a/misc/uml/parser.typ b/misc/uml/parser.typ new file mode 100644 index 0000000..117e973 --- /dev/null +++ b/misc/uml/parser.typ @@ -0,0 +1,94 @@ +#import "uml.typ" + +#let parse-args(data) = { + let args = data.split(",").map(a => a.trim()).filter(a => a.len() != 0) + let res + if ":" in data { + res = (:) + } else { + res = () + } + + for arg in args { + let parts = arg.split(":") + let arg-name = none + let arg-type + if parts.len() == 1 { + if type(res) == dictionary { + panic("Cannot mix named and unnamed arguments") + } + arg-type = parts.first().trim() + res.push(arg-type) + } else { + arg-name = parts.first().trim() + arg-type = parts.slice(1).join(":").trim() + res.insert(arg-name, arg-type) + } + } + + return res +} + +#let parse(data) = { + let elements = () + + if type(data) == raw or type(data) == content { + data = data.text + } + + let classes = data.matches(regex("(?ms)(abstract class|class|interface) (.*?)\s*\{(.*?)\}")) + + for class in classes { + let family-name = class.captures.first() + let family = ( + class: none, + "abstract class": uml.abstract, + "interface": uml.interface, + "enum": uml.enum, + ).at(family-name) + + let name = class.captures.at(1) + let class-data = class.captures.last().trim() + + let members = class-data.matches(regex("(?m)^\s*(.*?)\s*(:\s*([^:]*?))?$")).filter(m => m.text.len() != 0) + let attributes = () + let methods = () + for member in members { + let member-name = member.captures.first() + let val-type = member.captures.last() + let visibility = none + + if member-name.starts-with(regex("\+|-|~|#")) { + let vis = member-name.first() + member-name = member-name.slice(1) + visibility = ( + "+": uml.public, + "-": uml.private, + "~": uml.package, + "#": uml.protected + ).at(vis) + } + + if member-name.ends-with(")") { + let func = member-name.match(regex("^(.*?)\((.*)\)$")) + member-name = func.captures.first() + let args = parse-args(func.captures.last()) + methods += uml.method(member-name, visibility, val-type, args) + } else { + attributes += uml.attr(member-name, visibility, val-type) + } + } + + elements += uml.class(name, family, attributes + methods) + } + + let defaults = (:) + let params = data.matches(regex("(?m)^\s*@\s*(.*?)\s*=\s*(.*)\s*$")) + for param in params { + let key = param.captures.first() + let value = eval(param.captures.last(), scope: (uml: uml)) + defaults.insert(key, value) + } + + return uml.diagram(elements, defaults) +} \ No newline at end of file diff --git a/misc/uml/uml.typ b/misc/uml/uml.typ new file mode 100644 index 0000000..6136b00 --- /dev/null +++ b/misc/uml/uml.typ @@ -0,0 +1,569 @@ +#import "@preview/cetz:0.2.2": canvas, draw + +#let class-counter = counter("uml-class") + +#let class-colors = ( + rgb("#C02B22"), + rgb("#D4721C"), + rgb("D3BC2F"), + rgb("60CB12"), + rgb("#2ECC40"), + rgb("#39CCA5"), + rgb("#38B4DA"), + rgb("#0074d9"), + rgb("#450DC9"), + rgb("#890DC9"), +) + +#let visibility(vis, symbol) = (type: "visibility", visibility: vis, symbol: symbol) + +#let public = visibility("public", "+") +#let private = visibility("private", "-") +#let protected = visibility("protected", "#") +#let package = visibility("package", "~") + +#let class-family(family, prefix: none) = ( + type: "class-family", + family: family, + prefix: prefix +) +#let abstract = class-family("abstract") +#let interface = class-family("interface", prefix: "interface") +#let trait = class-family("trait") +#let enum = class-family("enum") +#let utility = class-family("utility", prefix: "utility") + +#let staticity(static) = (type: "staticity", static: static) +#let static = staticity(true) +#let non-static = staticity(false) + +#let class(name, ..args, grouped: none) = { + let attributes = () + let methods = () + let visibility = none + let family = none + + let args = args.pos() + if args.len() != 0 { + for arg in args { + if type(arg) == dictionary { + if arg.type == "visibility" { + visibility = arg + } else if arg.type == "class-family" { + family = arg + } + } else if type(arg) == array { + for elmt in arg { + if type(elmt) == dictionary { + if elmt.type == "visibility" { + visibility = elmt + } else if elmt.type == "attribute" { + attributes.push(elmt) + } else if elmt.type == "method" { + methods.push(elmt) + } + } + } + } + } + } + return (( + type: "class", + name: name, + visibility: visibility, + family: family, + attributes: attributes, + methods: methods, + color: rgb(184, 84, 80), + grouped: grouped + ),) +} + +#let attr(name, default: none, ..args) = { + let visibility = none + let staticity = none + let val-type = none + for arg in args.pos() { + if type(arg) == dictionary { + if arg.type == "visibility" { + visibility = arg + } else if arg.type == "type" { + val-type = arg + } else if arg.type == "staticity" { + staticity = staticity + } + } else if type(arg) == str { + val-type = arg + } + } + return (( + type: "attribute", + name: name, + visibility: visibility, + staticity: staticity, + val-type: val-type, + default: default + ),) +} + +#let method(name, ..args) = { + let visibility = none + let staticity = none + let parameters = none + let return-type = none + + for arg in args.pos() { + if type(arg) == dictionary { + if "type" in arg { + if arg.type == "visibility" { + visibility = arg + } else if arg.type == "staticity" { + staticity = arg + } + } else { + parameters = arg + } + } else if type(arg) == str { + return-type = arg + } else if type(arg) == array { + parameters = arg + } + } + return (( + type: "method", + name: name, + visibility: visibility, + staticity: staticity, + parameters: parameters, + return-type: return-type + ),) +} + +#let render-attr(attr, defaults, show-visibility: true) = { + let visibility = if attr.visibility == none { + defaults.attr-visibility + } else { + attr.visibility + } + let staticity = if attr.staticity == none { + defaults.attr-staticity + } else { + attr.staticity + } + + let name = attr.name + if staticity.static { + name = underline(name) + } + let txt = name + if show-visibility { + txt = visibility.symbol + txt + } + + if attr.val-type != none { + txt += ": " + attr.val-type + } + if attr.default != none { + txt += " = " + str(attr.default) + } + return txt +} + +#let render-method(method, defaults, show-visibility: true) = { + let visibility = if method.visibility == none { + defaults.method-visibility + } else { + method.visibility + } + let staticity = if method.staticity == none { + defaults.method-staticity + } else { + method.staticity + } + let params = () + + if type(method.parameters) == array { + params = method.parameters + } else if type(method.parameters) == dictionary { + params = method.parameters + .pairs() + .map(((n, t)) => n + ": " + t) + } + + let name = method.name + if staticity.static { + name = underline(name) + } + let txt = name + if show-visibility { + txt = visibility.symbol + txt + } + txt += "(" + params.join(", ") + ")" + if method.return-type != none { + txt += ": " + method.return-type + } + + return txt +} + +#let get-attributes-by-visibility(class, defaults) = { + let by-vis = (:) + for attr in class.attributes { + let visibility = if attr.visibility == none { + defaults.attr-visibility + } else { + attr.visibility + } + if not visibility.visibility in by-vis { + by-vis.insert(visibility.visibility, ()) + } + by-vis.at(visibility.visibility).push(attr) + } + return by-vis +} + +#let get-methods-by-visibility(class, defaults) = { + let by-vis = (:) + for method in class.methods { + let visibility = if method.visibility == none { + defaults.method-visibility + } else { + method.visibility + } + if not visibility.visibility in by-vis { + by-vis.insert(visibility.visibility, ()) + } + by-vis.at(visibility.visibility).push(method) + } + return by-vis +} + +#let render-class-attributes(class, defaults) = { + let grouped = if class.grouped == none { + defaults.class-grouped + } else { + class.grouped + } + let cells = () + + if not grouped { + for attr in class.attributes { + cells.push(render-attr(attr, defaults, show-visibility: true)) + } + } else { + let by-vis = get-attributes-by-visibility(class, defaults) + for (vis, attrs) in by-vis { + cells.push(table.cell(vis + ":")) + cells += attrs.map(attr => { + let txt = render-attr(attr, defaults, show-visibility: false) + pad(left: 1em, txt) + }) + } + } + return cells +} + +#let render-class-methods(class, defaults) = { + let grouped = if class.grouped == none { + defaults.class-grouped + } else { + class.grouped + } + let cells = () + + if not grouped { + for method in class.methods { + cells.push(render-method(method, defaults, show-visibility: true)) + } + } else { + let by-vis = get-methods-by-visibility(class, defaults) + for (vis, attrs) in by-vis { + cells.push(table.cell(vis + ":")) + cells += attrs.map(method => { + let txt = render-method(method, defaults, show-visibility: false) + pad(left: 1em, txt) + }) + } + } + return cells +} + +#let render-class(class, defaults) = context { + let family = if class.family == none { + defaults.class-family + } else { + class.family + } + + let cells = () + cells += render-class-attributes(class, defaults) + + cells.push(table.hline()) + + cells += render-class-methods(class, defaults) + + let name = [*#class.name*] + let family = class.family + if family == abstract { + name = emph(name) + } + if family != none and family.prefix != none { + name = [*«#family.prefix»*\ #name] + } + let class-i = class-counter.get().first() + let col = class-colors.at(calc.rem(class-i, class-colors.len())) + + set table.hline(stroke: col) + table( + inset: (top: 0.5em, bottom: 0.5em, left: 0.5em, right: 0.5em), + stroke: (x, y) => { + let s = (left: col, right: col) + if y == 0 { + s.insert("top", col) + s.insert("bottom", col) + } + return s + }, + align: (x, y) => { + if y == 0 { + return center + horizon + } + return left + horizon + }, + fill: (x, y) => if y == 0 { + col.lighten(75%) + } else { + none + }, + table.header(name), + ..cells, + table.hline() + ) + class-counter.step() +} + +#let diagram(..args) = { + class-counter.update(0) + + let args = args.pos() + + let defaults = ( + class-family: none, + class-grouped: false, + attr-visibility: public, + attr-staticity: non-static, + method-visibility: public, + method-staticity: non-static, + ) + let elements = () + if args.len() != 0 { + for arg in args { + if type(arg) == dictionary { + defaults += arg + } else if type(arg) == array { + elements = arg + } + } + } + + let cells = () + for element in elements { + if element.type == "class" { + cells.push(render-class(element, defaults)) + } + } + box( + stroke: black, + inset: 1em, + grid( + columns: 2, + column-gutter: 1em, + align: center + horizon, + row-gutter: 1em, + ..cells + ) + ) +} + +#let render-attr2(pos, id, attr, defaults, show-visibility: true) = { + let visibility = if attr.visibility == none { + defaults.attr-visibility + } else { + attr.visibility + } + let staticity = if attr.staticity == none { + defaults.attr-staticity + } else { + attr.staticity + } + + let name = attr.name + if staticity.static { + name = underline(name) + } + let txt = name + if show-visibility { + txt = visibility.symbol + txt + } + + if attr.val-type != none { + txt += ": " + attr.val-type + } + if attr.default != none { + txt += " = " + str(attr.default) + } + + draw.content(pos, txt, name: id) +} + +#let render-method2(method, defaults, show-visibility: true) = { + let visibility = if method.visibility == none { + defaults.method-visibility + } else { + method.visibility + } + let staticity = if method.staticity == none { + defaults.method-staticity + } else { + method.staticity + } + let params = () + + if type(method.parameters) == array { + params = method.parameters + } else if type(method.parameters) == dictionary { + params = method.parameters + .pairs() + .map(((n, t)) => n + ": " + t) + } + + let name = method.name + if staticity.static { + name = underline(name) + } + let txt = name + if show-visibility { + txt = visibility.symbol + txt + } + txt += "(" + params.join(", ") + ")" + if method.return-type != none { + txt += ": " + method.return-type + } + + draw.content((), txt) +} + +#let render-class-attributes2(class, defaults) = { + let grouped = if class.grouped == none { + defaults.class-grouped + } else { + class.grouped + } + let cells = () + let prev = () + + if not grouped { + for (i, attr) in class.attributes.enumerate() { + let id = "class-"+class.name+"-attr-"+str(i) + let txt = render-attr(attr, defaults, show-visibility: true) + draw.content((rel: (0, -0.4em), to: prev), name: id, anchor: "north-west", txt) + prev = id + ".south-west" + } + } else { + let by-vis = get-attributes-by-visibility(class, defaults) + for (vis, attrs) in by-vis { + cells.push(table.cell(vis + ":")) + let vis-id = "class-"+class.name+"-attrs-"+vis + draw.content((rel: (0, -0.4em), to: prev), name: vis-id, anchor: "north-west", vis + ":") + prev = vis-id + ".south-west" + + for (i, attr) in attrs.enumerate() { + let txt = render-attr(attr, defaults, show-visibility: false) + txt = pad(left: 1em, txt) + let id = vis-id + "-" + str(i) + draw.content((rel: (0, -0.4em), to: prev), name: id, anchor: "north-west", txt) + prev = id + ".south-west" + } + } + } +} + +#let render-class-methods2(class, defaults) = { + +} + +#let render-class2(class, defaults) = { + let attr-group = render-class-attributes2(class, defaults) + let method-group = render-class-methods2(class, defaults) + + let family = if class.family == none { + defaults.class-family + } else { + class.family + } + let name = [*#class.name*] + let family = class.family + if family == abstract { + name = emph(name) + } + if family != none and family.prefix != none { + name = [*«#family.prefix»*\ #name] + } + let class-i = class-counter.get().first() + let col = class-colors.at(calc.rem(class-i, class-colors.len())) + + draw.group(name: "class-"+class.name, { + draw.group(name: "class-"+class.name+"-name", padding: 5pt, { + draw.content((), name) + }) + draw.on-layer(-1, { + draw.rect("class-"+class.name+"-name.north-west", "class-"+class.name+"-name.south-east", fill: col.lighten(75%), stroke: col, layer: -1) + }) + draw.group(name: "class-"+class.name+"-attrs", padding: 0.2em, { + attr-group + }) + method-group + }) + draw.rect("class-"+class.name+".north-west", "class-"+class.name+".south-east", stroke: col) +} + +#let render-class3(class, defaults) = { + let id = "class-" + class.name + return draw.content((rel: (3, 0), to: ()), render-class(class, defaults), name: id) +} + + +#let diagram2(..args) = context { + class-counter.update(0) + + let args = args.pos() + + let defaults = ( + class-family: none, + class-grouped: false, + attr-visibility: public, + attr-staticity: non-static, + method-visibility: public, + method-staticity: non-static, + ) + let elements = () + if args.len() != 0 { + for arg in args { + if type(arg) == dictionary { + defaults += arg + } else if type(arg) == array { + elements = arg + } + } + } + + let cells = () + canvas({ + for element in elements { + if element.type == "class" { + render-class3(element, defaults) + } + } + draw.line("class-Test", "class-Essai", mark: (end: "diamond", scale: 2)) + }) +} \ No newline at end of file