Projects [projs]

Contribution racket-langserver [racket-langserver]

A Language Server Protocol implementation for Racket.

  1. implement cross-file jump to definition. #59
  2. auto formatting: remove trailing whitespace. #61
  3. implement inlay hints. #84
  4. add diagnostic and code action for unused variables. #87
  5. support notification workspace/didChangeWorkspaceFolders. #140
  6. support notification workspace/didRenameFiles. #144
  7. support installed Racket languages (e.g. Rhombus). #150
  8. support notification workspace/didChangeWatchedFiles. #151
  9. support configuration request workspace/configuration. #156
  10. fix server configuration updation #164
  11. Extract server concept to remove complicated IO interaction #173
  12. Respect client capabilities #182
  13. Record rootUri and rootPath in initialize params as workspace folders #184
  14. Find workspace references (limits to opened files) #160
  15. reduce costs of service workspace-references #195
  16. sticky scroll for #lang racket #215
  17. enable inlay hints for typed/racket #227
  18. Hint struct fields at constructor calls #228

tr [tr-0000]

A site generator based on a collection of racket/scribble programs.

下面著手描述這個專案的核心想法

地址唯一性 [tr-0003]

每張卡片應有唯一的地址

生成 embed 與 index HTML [tr-0001]

embed 之間需要按引用關係構造,所以用 topological sort 排序(從 metadata.json 中復原依賴關係)後按順序編譯

通過分成兩階段生成,可以讓 index 引入 embed 的內容

raco tr next 與使用方式 [tr-0002]

讓 raco tr next xxx 生成新的地址,就可以用 code $(raco tr next xxx).scrbl 這樣的方式開啟新檔案

數學支援 [tr-0004]

用 Katex 支援公式,用 LaTeX 支援複雜的圖

LaTeX 工作的獨立標籤 [tr-0006]

每個 address yyy 對 LaTeX 會創造新的相關的 _tmp/yyy/tex3212.tex 之類的檔案,tex3212 是用 gensym 產生。

如此一來 *.tex 檔案可知道自己是哪個 address 產出,如果 source 沒有更新便不編譯。

RSS 與 searching [tr-0005]

利用預先生成的 addr.metadata.json 進一步生成 RSS 與索引資料

tr/card [tr-0008]

Code changes: https://github.com/dannypsnl/tr/commit/c1000033203c4216cdccff53bac7f2288a76b867

在 metadata 裡面加入 locals 這個概念,在這個陣列裡面儲存 title 跟 taxon 資訊。遞迴時先標好地址,把資訊存到相應的地址中,就得到平坦的結構。於是就不一定需要開新的檔案來寫新的 card 了。

Commit ddffdf3 之後新增了很重要的選項:#:open,寫成 @tr/card[#:open #f]{...},確保使用者可以選擇預設不打開這張卡片。

tr 的快取機制 [tr-G7G0]

如果原始檔案沒有變化,我們當然希望盡可能不要重新建構它,原因大致上都是因為建構是需要時間的,比如解譯一次 scribble 檔案然後生成 html 需要不少時間、typst 產生圖片需要不少時間等等。從很早期 tr 就有用很單純的方法判斷原始碼有沒有變化過:metadata 的變更時間

從 PR #53 開始 tr 就改從 content-addressed build store 機制判斷了。這套機制的想法是這樣:卡片 render 的結果會依賴一些輸入,用這些輸入計算出一個 sha1 signature,這就是 store 的 key

每次建構之前會用這個 key 判斷 store 中有沒有快取存在,如果有命中就把快取好的產物複製到輸出目錄,不然就表示需要真的建構一次(不管是因為快取被清理掉了還是因為原始碼更新了),並且把內容儲存進 store

signature 在 transclude 的拓樸序下排列(被 transcluded 的卡片會先被處理):

  1. 卡片自己的 source code:原始 .scrbl 檔案的 bytes,加上它每個 @include 進來的檔案的 bytes。所以即使 .scrbl 沒動,Agda html 之類的外部檔案一有變化時這張卡也會失效。不需要另外用 Makefile 之類的工具清理快取

  2. 卡片自己算出來的 metadata(已經帶有傳播過來的 backlinks / context / related)。

  3. 每個transclude卡片的完整 signature。所以改 transclude 卡片會讓每個嵌入它的 parent 卡片重新簽名;但反過來,改 parent 卡片不會影響 transclude 卡片

  4. 每個被引用的 neighborhood 的 title 跟 taxon(不管是來自 context/references/backlinks/related/authors),這是一張卡片的 neighborhoods 唯一會 render 的欄位。所以改一張卡片的標題,會重建那些在 Related 區塊 mention 它的卡片;但改卡片的內文則不會影響 neighborhoods

  5. 一個render-config-tag:設定檔裡的 head 欄位會影響render的內容。兩個render出來bytes會不同的output那他們的target就會有不同的signature,不會複製到對方的輸出

store 下來的內容放在 _tmp/cache/store/<sig>/,儲存:render 好的 embed.html 跟 out/(編譯好的 latex / typst svg)。index.html 不快取,因為這個計算很快

  1. deploy可以從同一份 _tmp 建立兩棵樹:先執行dev建構再執行release建構的話,只要內容跟config有影響內容的欄位相同,signature就相同,於是第二個target只需要從store複製,不用重複建構

  2. 把卡片revert回去還是會有快取命中,因為較早的signature對應的entry還在硬碟上(這在外部工具產出的一些東西不在signature計算中時確實偶爾會是問題,但不嚴重)

store 雖然保證了不同建構 target 時的正確性,但不知道某個特定 target 是由哪個 signature 產生,所以每個 output 目錄會留一個戳記 <output>/<addr>/.sig,記下它的 index.html 是哪個 signature 產生的。當戳記吻合、index.html 存在、且每個被快取的 output 檔都還在時,per-target 的工作(svg 加 index.html)就可以被跳過,只把 embed 刷新回 _tmp 供 transclude 它的父卡讀取。被刪掉或被竄改的 output 檔,即使戳記吻合,下一次 build 也會從 store 重新產生

private cards [tr-490U]

我突然意識到我雖然引入了這個功能(commit 4c9a9ed)但是還沒有記錄。這個功能的用途是讓 content/private 中的cards在release build中會被排除,如果這時候其他cards引用到任何一個private cards建構就不會成功

Config as code [tr-XH91]

在site.rkt中編寫設定檔,這讓一些複雜欄位可以用racket完成,以本站為例設定檔是

#lang racket/base
(require scribble/html)
(provide site)

(define site
  (hash 'assets '("assets" "slide-assets" "images")
        'description "……"
        'domain "dannypsnl.me/"
        'mode "dev"
        'title "Lîm Tsú-thuàn"
        'head (list (link 'rel: "me" 'href: "https://g0v.social/@dannypsnl")
                    (meta 'name: "fediverse:creator" 'content: "@dannypsnl@g0v.social")
                    (link 'rel: "webmention" 'href: "https://webmention.io/dannypsnl.me/webmention")
                    (link 'rel: "alternate" 'type: "application/rss+xml"
                          'title: "Lîm Tsú-thuàn" 'href: "/rss.xml"))))

是否發佈由設定檔控制 [tr-YGRJ]

private cards現在也納入Config as code的設計,用

(define (remove-content-p source-path)
  (define segs (map path->string (explode-path source-path)))
  (and (equal? "content" (first segs))
       (equal? "private" (second segs))))

(define site
  (hash ; ...
        'remove-content-if remove-content-p))

提供這個過濾判斷

Tufte風格的margin note [tr-P10S]

@note{...} 可以用來建立出現在右側的邊註邊註

寫在 @note 裡的 @mention["..."] 有特殊處理:會展開成可點擊的卡片,就像這樣tr2025 - 2026 · Lîm Tsú-thuàn。也因為這個,tr 的快取機制裡neighborhood從只看title跟taxon擴充到也看日期、作者與DOI

編號用CSS counter;寬螢幕時註解浮到正文右邊的邊界;中等寬度退化成正文裡的一個區塊;窄螢幕則是點了ref之後才會展開

自訂Header [tr-VGBX]

原本預設在每頁最上面放一個 « Home 連結,index例外。現在 Config as code 多了 header 欄位可以整個換掉它

'header (nav 'class: "site-nav"
             (a 'href: "/" "my site")
             (a 'href: "/about/" "about"))

設定之後index也會render這段html。tr 的快取機制的config tag也因此要拆成兩半,確保改header會重新產生每一頁的 index.html,但不會讓embed失效而重跑LaTeX、Typst

MaJu [maju]

A jj GUI client.

Contribution magic-racket [magic-racket]

The best coding experience for Racket in VS Code.

  1. Open selection in macro stepper. #148

violet [violet]

violet is a small dependent type programming language.

The little prover shrubbery 版本 [the-little-prover]

The little prover use shrubbery notation.

verse calculus (redex model) [verse-calculus-redex]

sauron [sauron]

A DrRacket plugin to make it experience like an IDE

racket-llvm [racket-llvm]

LLVM bindings for racket

Contribution agda [agda]

Contribution agda-mode-vscode [agda-mode-vscode]

agda-mode on VSCode

  1. Provide hover text to hint about how to type a symbol. #258

bib2tr [bib2tr]

A tool to convert .bib file to a list of reference cards.

html2scrbl [html2scrbl]

A web tool to convert input HTML to scribble/html

effect system in typed/racket [typed-racket-eff]

An effect system integrated with typed/racket.

agda-tree [agda-tree]

Converts agda produced *.tree to valid *.tree.

html2tree [html2tree]

Converts html syntax to forester namespace html syntax.

Algebraic Graph [algebraicgraph]

Contribution llir/llvm [llir]

m := ir.NewModule()

globalG := m.NewGlobalDef("g", constant.NewInt(types.I32, 2))

funcAdd := m.NewFunc("add", types.I32,
        ir.NewParam("x", types.I32),
        ir.NewParam("y", types.I32),
)
ab := funcAdd.NewBlock("")
ab.NewRet(ab.NewAdd(funcAdd.Params[0], funcAdd.Params[1]))

funcMain := m.NewFunc(
        "main",
        types.I32,
)  // omit parameters
mb := funcMain.NewBlock("") // llir/llvm would give correct default name for block without name
mb.NewRet(mb.NewCall(funcAdd, constant.NewInt(types.I32, 1), mb.NewLoad(types.I32, globalG)))

println(m.String())

Generated IR:

@g = global i32 2

define i32 @add(i32 %x, i32 %y) {
; 

redux [redux]

type CountingModel struct {
    rematch.Reducer
    State int

    Increase *rematch.Action `action:"IncreaseImpl"`
}

func (c *CountingModel) IncreaseImpl(s, payload int) int {
    return s + payload
}

func main() {
    c := &CountingModel {
        State: 0,
    }
    store := store.New(c)
    store.Dispatch(c.Increase.With(30))
    store.Dispatch(c.Increase.With(20))

    fmt.Printf("result: %d\n", store.StateOf(c)) // expect: 50
}

rocket [rocket]

import (
    "github.com/dannypsnl/rocket"
)

type User struct {
    Name string `route:"name"`
    Age  uint64 `route:"age"`
}

func hello(u *User) string {
    return "Hello " + u.Name + ", your age is " + strconv.FormatUint(u.Age, 10)
}

// main.go
func main() {
    rocket.Ignite(8080).
        Mount(
            // put `hello` under a path `/user/:name/:age`, where `:name` and `:age` are variant parameters
            rocket.Get("/user/:name/:age", hello),
        ).
        Launch()
}