@ -0,0 +1,98 @@ | |||||
$code-bg: hsl(230,1%,98%); | |||||
$code-fg: #ABB2BF; | |||||
$code-red: #D65122; | |||||
$code-red-br: #AE3B36; | |||||
$code-green: #88B966; | |||||
$code-yellow: #DEB468; | |||||
$code-orange: #C58853; | |||||
$code-blue: #519DEB; | |||||
$code-pink: #C678DD; | |||||
$code-cyan: #48A8B5; | |||||
$code-white: #ABB2BF; | |||||
$code-grey: #7F848E; | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-regular.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-regular.ttf') format('truetype'); | |||||
} | |||||
body { | |||||
margin: 0; | |||||
background-color:$code-bg; | |||||
color:#ABB2BF; | |||||
} | |||||
html { | |||||
background-color: $code-bg; | |||||
color:#ABB2BF; | |||||
} | |||||
pre.Agda { | |||||
margin: 0; | |||||
padding: 1em; | |||||
background-color: $code-bg; | |||||
color: $code-fg; | |||||
} | |||||
@keyframes highlight { | |||||
0% { | |||||
background-color: #F5DEB3; | |||||
} | |||||
100% { | |||||
background-color: $code-bg; | |||||
} | |||||
} | |||||
/* Aspects. */ | |||||
.Agda { | |||||
.Comment { color: $code-grey; } | |||||
.Background { background-color: $code-bg; } | |||||
.Markup { color: $code-fg; } | |||||
.Keyword { color: $code-orange; } | |||||
.String { color: $code-red; } | |||||
.Number { color: $code-pink; } | |||||
.Symbol { color: $code-fg; } | |||||
.PrimitiveType { color: $code-blue; } | |||||
.Pragma { color: $code-fg; } | |||||
/* NameKinds. */ | |||||
.Bound { color: $code-fg; } | |||||
.Generalizable { color: $code-fg; } | |||||
.InductiveConstructor { color: $code-green; } | |||||
.CoinductiveConstructor { color: $code-green; } | |||||
.Datatype { color: $code-blue; } | |||||
.Field { color: #F570B7; } | |||||
.Function { color: $code-blue; } | |||||
.Module { color: $code-pink; } | |||||
.Postulate { color: $code-blue; } | |||||
.Primitive { color: $code-blue; } | |||||
.Record { color: $code-blue; } | |||||
/* OtherAspects. */ | |||||
.UnsolvedMeta { color: $code-fg; background: yellow } | |||||
.UnsolvedConstraint { color: $code-fg; background: yellow } | |||||
.TerminationProblem { color: $code-fg; background: #FFA07A } | |||||
.IncompletePattern { color: $code-fg; background: #F5DEB3 } | |||||
.Error { color: red; text-decoration: underline } | |||||
.TypeChecks { color: $code-fg; background: #ADD8E6 } | |||||
.Deadcode { color: $code-fg; background: #808080 } | |||||
.ShadowingInTelescope { color: $code-fg; background: #808080 } | |||||
/* Standard attributes. */ | |||||
a { text-decoration: none } | |||||
a[href]:hover { | |||||
text-decoration: 2px #B4EEB4 underline dotted; | |||||
} | |||||
a[href]:target { | |||||
animation: highlight 2.5s; | |||||
} | |||||
background-color: #282C34; | |||||
font-family: 'Iosevka', 'Fantasque Sans Mono', 'Roboto Mono', monospace; | |||||
font-weight: 400; | |||||
font-size: 16pt; | |||||
} |
@ -1,45 +0,0 @@ | |||||
code, div.sourceCode { | |||||
font-family: Fantasque Sans Mono, Fira Mono, monospace; | |||||
font-size: 15pt; | |||||
} | |||||
code.inline { | |||||
background-color: rgb(250,250,250); | |||||
/* border: 1px solid rgb(200,200,200); */ | |||||
} | |||||
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode { | |||||
margin: 0; padding: 0; vertical-align: baseline; border: none; | |||||
} | |||||
table.sourceCode { | |||||
width: 100%; line-height: 100%; | |||||
} | |||||
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; } | |||||
td.sourceCode { padding-left: 5px; } | |||||
code.kw, span.kw { color: #af005f; } /* Keyword */ | |||||
code.dt, span.dt { color: #5f5faf; } /* DataType */ | |||||
code.dv, span.dv { color: #268bd2; } /* DecVal */ | |||||
code.bn, span.bn { color: #268bd2; } /* BaseN */ | |||||
code.fl, span.fl { color: #268bd2; } /* Float */ | |||||
code.ch, span.ch { color: #cb4b16; } /* Char */ | |||||
code.st, span.st { color: #cb4b16; } /* String */ | |||||
code.co, span.co { color: #8a8a8a; } /* Comment */ | |||||
code.ot, span.ot { color: #007020; } /* Other */ | |||||
code.al, span.al { color: #ff0000; } /* Alert */ | |||||
code.fu, span.fu { color: #666666; } /* Function */ | |||||
code.er, span.er { color: #ff0000; } /* Error */ | |||||
code.wa, span.wa { color: #60a0b0; } /* Warning */ | |||||
code.cn, span.cn { color: #880000; } /* Constant */ | |||||
code.sc, span.sc { color: #4070a0; } /* SpecialChar */ | |||||
code.vs, span.vs { color: #4070a0; } /* VerbatimString */ | |||||
code.ss, span.ss { color: #2aa198; } /* SpecialString */ | |||||
code.va, span.va { color: #073642; } /* Variable */ | |||||
code.cf, span.cf { color: #007020; } /* ControlFlow */ | |||||
code.op, span.op { color: #d33682; } /* Operator */ | |||||
code.pp, span.pp { color: #bc7a00; } /* Preprocessor */ | |||||
code.at, span.at { color: #7d9029; } /* Attribute */ | |||||
code.do, span.do { color: #ba2121; } /* Documentation */ | |||||
code.an, span.an { color: #60a0b0; } /* Annotation */ | |||||
code.cv, span.cv { color: #60a0b0; } /* CommentVar */ |
@ -1,13 +0,0 @@ | |||||
@font-face { | |||||
font-family: "Computer Modern"; | |||||
src: url('/static/cmunorm.woff'); | |||||
} | |||||
.math { | |||||
font-family: Computer Modern, serif; | |||||
} | |||||
.math .display { | |||||
font-size: 18px; | |||||
text-align: center; | |||||
} |
@ -0,0 +1,419 @@ | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-regular.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-regular.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: expanded; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extended.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extended.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: normal; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-oblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-oblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: normal; | |||||
src: url('/static/woff2/iosevk-abbie-oblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-oblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: expanded; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extendedoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: expanded; | |||||
src: url('/static/woff2/iosevk-abbie-extendedoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: normal; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-italic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-italic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 400; | |||||
font-stretch: expanded; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extendeditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendeditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-medium.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-medium.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: expanded; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extendedmedium.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedmedium.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: normal; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-mediumoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-mediumoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: normal; | |||||
src: url('/static/woff2/iosevk-abbie-mediumoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-mediumoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: expanded; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extendedmediumoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedmediumoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: expanded; | |||||
src: url('/static/woff2/iosevk-abbie-extendedmediumoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedmediumoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: normal; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-mediumitalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-mediumitalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 500; | |||||
font-stretch: expanded; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extendedmediumitalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedmediumitalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-semibold.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-semibold.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: expanded; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extendedsemibold.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedsemibold.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: normal; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-semiboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-semiboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: normal; | |||||
src: url('/static/woff2/iosevk-abbie-semiboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-semiboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: expanded; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extendedsemiboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedsemiboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: expanded; | |||||
src: url('/static/woff2/iosevk-abbie-extendedsemiboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedsemiboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: normal; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-semibolditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-semibolditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 600; | |||||
font-stretch: expanded; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extendedsemibolditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedsemibolditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-bold.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-bold.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: expanded; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extendedbold.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedbold.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: normal; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-boldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-boldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: normal; | |||||
src: url('/static/woff2/iosevk-abbie-boldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-boldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: expanded; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extendedboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: expanded; | |||||
src: url('/static/woff2/iosevk-abbie-extendedboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: normal; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-bolditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-bolditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 700; | |||||
font-stretch: expanded; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extendedbolditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedbolditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extrabold.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extrabold.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: expanded; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extendedextrabold.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedextrabold.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: normal; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extraboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extraboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extraboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extraboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: expanded; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extendedextraboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedextraboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: expanded; | |||||
src: url('/static/woff2/iosevk-abbie-extendedextraboldoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedextraboldoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: normal; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extrabolditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extrabolditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 800; | |||||
font-stretch: expanded; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extendedextrabolditalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedextrabolditalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: normal; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-heavy.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-heavy.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: expanded; | |||||
font-style: normal; | |||||
src: url('/static/woff2/iosevk-abbie-extendedheavy.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedheavy.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: normal; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-heavyoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-heavyoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: normal; | |||||
src: url('/static/woff2/iosevk-abbie-heavyoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-heavyoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: expanded; | |||||
font-style: oblique; | |||||
src: url('/static/woff2/iosevk-abbie-extendedheavyoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedheavyoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka Oblique'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: expanded; | |||||
src: url('/static/woff2/iosevk-abbie-extendedheavyoblique.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedheavyoblique.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: normal; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-heavyitalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-heavyitalic.ttf') format('truetype'); | |||||
} | |||||
@font-face { | |||||
font-family: 'Iosevka'; | |||||
font-display: swap; | |||||
font-weight: 900; | |||||
font-stretch: expanded; | |||||
font-style: italic; | |||||
src: url('/static/woff2/iosevk-abbie-extendedheavyitalic.woff2') format('woff2'), url('/static/ttf/iosevk-abbie-extendedheavyitalic.ttf') format('truetype'); | |||||
} |
@ -0,0 +1,62 @@ | |||||
$purple-50: #faf5ff; | |||||
$purple-100: #f3e8ff; | |||||
$purple-200: #e9d5ff; | |||||
$purple-300: #d8b4fe; | |||||
$purple-400: #c084fc; | |||||
$purple-500: #a855f7; | |||||
$purple-600: #9333ea; | |||||
$purple-700: #7e22ce; | |||||
$purple-800: #6b21a8; | |||||
$purple-900: #581c87; | |||||
$yellow-50: #fefce8; | |||||
$yellow-100: #fef9c3; | |||||
$yellow-200: #fef08a; | |||||
$yellow-300: #fde047; | |||||
$yellow-400: #facc15; | |||||
$yellow-500: #eab308; | |||||
$yellow-600: #ca8a04; | |||||
$yellow-700: #a16207; | |||||
$yellow-800: #854d0e; | |||||
$yellow-900: #713f12; | |||||
$bluegray-50: #f8fafc; | |||||
$bluegray-100: #f1f5f9; | |||||
$bluegray-200: #e2e8f0; | |||||
$bluegray-300: #cbd5e1; | |||||
$bluegray-400: #94a3b8; | |||||
$bluegray-500: #64748b; | |||||
$bluegray-600: #475569; | |||||
$bluegray-700: #334155; | |||||
$bluegray-800: #1e293b; | |||||
$bluegray-900: #0f172a; | |||||
$red-50: #fef2f2; | |||||
$red-100: #fee2e2; | |||||
$red-200: #fecaca; | |||||
$red-300: #fca5a5; | |||||
$red-400: #f87171; | |||||
$red-500: #ef4444; | |||||
$red-600: #dc2626; | |||||
$red-700: #b91c1c; | |||||
$red-800: #991b1b; | |||||
$red-900: #7f1d1d; | |||||
$nav-height: 48px; | |||||
$font-size: 14pt; | |||||
$code-bg: #282C34; | |||||
$code-fg: #ABB2BF; | |||||
$code-red: #D65122; | |||||
$code-red-br: #AE3B36; | |||||
$code-green: #88B966; | |||||
$code-yellow: #DEB468; | |||||
$code-orange: #C58853; | |||||
$code-blue: #519DEB; | |||||
$code-pink: #C678DD; | |||||
$code-cyan: #48A8B5; | |||||
$code-white: #ABB2BF; | |||||
$code-grey: #7F848E; | |||||
// foo |
@ -8,19 +8,10 @@ span#reading-length { display: none; } | |||||
</style> | </style> | ||||
<div class="contact-list"> | <div class="contact-list"> | ||||
<div class="contact-card"> | |||||
<div class="contact-header"> | |||||
<img alt="profile picture" title="Abbie's profile picture" src=/static/icon/[email protected] /> | |||||
<span class="username">Abbie#4600</span> | |||||
</div> | |||||
My Discord friend requests are always open. Feel free to add me for questions or comments! | |||||
</div> | |||||
<div class="contact-card"> | <div class="contact-card"> | ||||
<div class="contact-header"> | <div class="contact-header"> | ||||
<img alt="profile picture" title="Abbie's profile picture" src=/static/icon/[email protected] /> | |||||
<span class="username">@plt_abbie</span> | |||||
<span class="username">@plt_amy</span> | |||||
</div> | </div> | ||||
<span> | <span> | ||||
@ -30,12 +21,11 @@ span#reading-length { display: none; } | |||||
<div class="contact-card"> | <div class="contact-card"> | ||||
<div class="contact-header"> | <div class="contact-header"> | ||||
<img alt="profile picture" title="Abbie's profile picture" src=/static/icon/[email protected] /> | |||||
<span class="username">abbie</span> | |||||
<span class="username">{ames}</span> | |||||
</div> | </div> | ||||
<span> | <span> | ||||
Message me directly on <a href="https://libera.chat">Libera</a> IRC, or join `##dependent` to talk about types! | |||||
I'm active in `##dependent` on [libera.chat](https://libera.chat) to talk about types! | |||||
</span> | </span> | ||||
</div> | </div> | ||||
@ -45,8 +35,7 @@ If you like what I do, here are some ways you can support this blog: | |||||
<div class="contact-list"> | <div class="contact-list"> | ||||
<div class="contact-card"> | <div class="contact-card"> | ||||
<span class="username"><a href="https://ko-fi.com/pltabby">Ko-fi</a></span> | |||||
<span class="username"><a href="https://ko-fi.com/plt_amy">Ko-fi</a></span> | |||||
You can send me a one-time donation on Ko-Fi. Just remember not to read the name on the receipt! | You can send me a one-time donation on Ko-Fi. Just remember not to read the name on the receipt! | ||||
(Paypal sucks) | (Paypal sucks) | ||||
</div> | </div> | ||||
@ -2,16 +2,78 @@ | |||||
title: Home | title: Home | ||||
--- | --- | ||||
<div id="post-toc-container"> | |||||
<article> | |||||
<p> | |||||
You've reached Abigail's blog, a place where I exposit and reflect on programming languages and type theory. | |||||
Here's the posts I've written recently: | |||||
</p> | |||||
<h2>Posts</h2> | |||||
$partial("templates/post-list.html")$ | |||||
<p>…or you can find more in the <a href="/archive.html">archives</a>.</p> | |||||
</article> | |||||
</div> | |||||
<div id=index> | |||||
<div style="grid-column: 2;"> | |||||
<h2 id=hi>Hi!</h2> | |||||
<p style="height: 98px"> | |||||
<a class=ico-left href="/"> | |||||
<img alt="profile picture" decoding=async src="/static/icon/[email protected]" /> | |||||
</a> | |||||
I'm Amélia, a non-binary (they/them) mathematician & programmer. This | |||||
blog is where I write about programming languages: their implementation, | |||||
their semantics, etc. | |||||
</p> | |||||
<hr /> | |||||
<div id=social> | |||||
<a href="https://twitter.com/plt_amy"> | |||||
<img class=social decoding=async title="My Twitter profile" src="/static/svg/twitter.svg" style="background-color: #1DA1F2;" /> | |||||
</a> | |||||
<a href="https://github.com/plt-amy"> | |||||
<img class=social decoding=async title="My GitHub profile" src="/static/svg/github.svg" style="background-color: black;" /> | |||||
</a> | |||||
<a href="https://git.amelia.how"> | |||||
<img class=social decoding=async title="My personal Gitea" src="/static/svg/gitea.svg" style="background-color: #609926;" /> | |||||
</a> | |||||
<a href="https://ko-fi.com/plt_amy"> | |||||
<img class=social decoding=async title="Buy me a coffee on Ko-Fi" src="/static/svg/kofi.svg" style="background-color: #434B57;" /> | |||||
</a> | |||||
</div> | |||||
<hr /> | |||||
<p style="height: 98px"> | |||||
<a class=ico-right href="https://cubical.1lab.dev"> | |||||
<img class=ico-right alt="cube" decoding=async src="/static/icon/cube-128x.png" /> | |||||
</a> | |||||
In addition to this blog, I maintain <a | |||||
href="https://cubical.1lab.dev">the 1Lab</a>, a formalised, cross-linked | |||||
reference resource for Homotopy Type Theory, done in Cubical Agda. | |||||
</p> | |||||
<hr /> | |||||
<p style="height: 98px"> | |||||
<a class=ico-left href="https://amulet.works"> | |||||
<img alt="amulet" decoding=async src="/static/svg/amulet.svg" /> | |||||
</a> | |||||
My most significant project other than this blog and the 1lab is <a | |||||
href="https://amulet.works">Amulet</a>, a functional programming | |||||
language in the ML tradition with support for advanced type-level | |||||
programming. | |||||
</p> | |||||
</div> | |||||
<div style="grid-column: 4;"> | |||||
<h2>Posts</h2> | |||||
<p> | |||||
Here are the lastest 5 posts from the blog: | |||||
</p> | |||||
$partial("templates/post-list.html")$ | |||||
<p>…or you can find more in the <a href="/archive.html">archives</a>.</p> | |||||
</div> | |||||
</div> | |||||
<style> | |||||
main > div#title { | |||||
display: none; | |||||
} | |||||
</style> |
@ -0,0 +1,420 @@ | |||||
--- | |||||
title: Typing (GHC) Haskell in Haskell | |||||
subtitle: The OutsideIn(X) Elaboration Algorithm | |||||
date: September 5th, 2021 | |||||
public: false | |||||
--- | |||||
Typing Haskell in Haskell, in addition to being a solved problem, is the name of [a paper] by Mark P. Jones that constructs, in detail, a solution to that problem. The goal of that paper is noble: a complete specification of Haskell's type system as an executable Haskell program. And, indeed, in 2000, when that paper was published, it _was_ a complete specification of Haskell's type system, depending on what you mean by Haskell. However, most people do not mean "Haskell 2010" when they say Haskell, let alone Haskell 98 - what the paper implements. Further, it's been 21 years! | |||||
[a paper]: https://web.cecs.pdx.edu/~mpj/thih/thih.pdf | |||||
When I say Haskell, personally, I mean "GHC's default language", and possibly throw in some 20 extensions on top anyway. Here's a small list of conveniences 2021 Haskell programmers are used to, but were implemented in the two decades since _Typing Haskell in Haskell_ was first published - or, in the case of FunDeps, were simply not standardised: | |||||
- Rank-N types, a limited implementation of first-class polymorphism, let a Haskell programmer write `forall`s to the left of as many arrows as she wants. For a motivating example, take the ST monad, from which a value can be extracted using `runST`: | |||||
```haskell | |||||
runST :: (forall s. ST s a) -> a | |||||
``` | |||||
Since the type of the state token - `s` - is universally quantified, it's not "chosen" by the ST computation, but rather by `runST` itself, making sure that the computation can't adversarially "choose" an instantiation of `s` that violates referential transparency. | |||||
Rank-N types were first implemented in GHC in November 2001, in [this commit](https://gitlab.haskell.org/ghc/ghc/-/commit/5e3f005d3012472e422d4ffd7dca5c21a80fca80). | |||||
[rankn]: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf | |||||
- Generalised algebraic data types (GADTs), which let us introduce local _equational constraints_ between types by means of pattern matching. I'm a big fan of GADTs, so much so that I paid 20 bucks to register the domain [gadt.fans](https://gadt.fans). The classic example of GADTs is a well-typed interpreter, where the type of each constructor constrains the return type of the interpreter: | |||||
```haskell | |||||
data Exp a where | |||||
Add :: Exp Int -> Exp Int -> Exp Int | |||||
IsZ :: Exp Int -> Exp Bool | |||||
If :: Exp Bool -> Exp a -> Exp a -> Exp a | |||||
Lit :: Int -> Exp Int | |||||
eval :: Exp a -> a | |||||
eval (Lit i) = i | |||||
{- most cases omitted for brevity -} | |||||
``` | |||||
GADTs were first implemented in GHC in September 2004, in [this commit](https://gitlab.haskell.org/ghc/ghc/-/commit/23f40f0e9be6d4aa5cf9ea31d73f4013f8e7b4bd). | |||||
- Functional dependencies, inspired by database theory, let a programmer specify that some of the arguments to one of their type classes is entirely determined by the value of some other argument. If that's a bit abstract, a more operational reading is that functional dependencies improve inferred types by adding new equalities. The classic example is this: | |||||
```haskell | |||||
class Collects c e | c -> e where | |||||
singleton :: e -> c | |||||
union :: c -> c -> c | |||||
``` | |||||
Without the functional dependency, the inferred type for the function `bagTwo` below would be `(Collects c e1, Collects c e2) => e1 -> e2 -> c`{.haskell}, implying that bagTwo is capable of placing two values of different types in the same collection `c`. | |||||
```haskell | |||||
bagTwo x y = singleton x `union` singleton y | |||||
``` | |||||
With the functional dependency `c -> e` in place, the two inferered constraints `(Collects c e1, Collects c e2)` _interact_ to introduce an equality `e1 ~ e2`, improving the inferred type of the function to | |||||
```haskell | |||||
bagTwo :: Collects c a => a -> a -> c | |||||
``` | |||||
Functional dependencies were first implemented in GHC in December 1999, in [this commit](https://gitlab.haskell.org/ghc/ghc/-/commit/297f714906efa8a76378c6fa6db3cd592f896749). The connection between database theory and type systems, integral in the design of functional dependencies for Haskell type classes, is made clear in [the original paper](http://web.cecs.pdx.edu/~mpj/pubs/fundeps-esop2000.pdf), section 5. | |||||
- Type families, originally introduced as _associated types_, are, as [Richard Eisenberg put it](https://gitlab.haskell.org/ghc/ghc/-/issues/11080), "non-generative, non-injective symbols whose equational theory is given by GHC". Put another way, they're almost-but-not-quite functions between types. Type families are _weird_, and complicate type checking massively. For instance, consider the following program, taken from Storalek et al.'s "Injective Type Families for Haskell": | |||||
```haskell | |||||
class Manifold a where | |||||
type Base a | |||||
project :: a -> Base a | |||||
unproject :: Base a -> a | |||||
id :: Manifold a => Base a -> Base a | |||||
id = project . unproject | |||||
``` | |||||
Does this program type check? Surprisingly, the answer is no! The reason is that the type variable `a` only appears under type families, and in the set of constraints, so GHC reports the function's type as ambiguous. | |||||
To understand why this is problematic, imagine that we have two types `X`{.haskell} and `Y`{.haskell} such that `Base X = Base Y = [Double]`{.haskell}. Given a `vec :: [Double]`, what instance of `Manifold` should the call `id vec` use? We can't choose - we can only guess, and runtime behaviour that depends on a compiler guess is very much frowned upon! | |||||
Type families were originally implemented ca. 2006, but I've been unable to track down the precise commit. I believe it was done as part of the patch which changed GHC's intermediate representation to System $F_C$ (we'll get to it) - this is backed up by this sentence from the conclusion of the $F_C$ paper: "At the same time, we re-implemented GHC’s support for newtypes and GADTs to work as outlined in §2 and added support for associated (data) types". | |||||
All of these features interact with eachother in entirely non-trivial ways, creating a powerful source of GHC infelicities with $n^2$ magnitude. The interaction between GADTs and type families, for instance, mandates an elaboration algorithm which can cope with _local assumptions_ in a principled way, since GADTs can introduce equalities between existentials which interact with type family axioms non-trivially. Wobbly types just won't cut it. | |||||
That's where $\operatorname{OutsideIn}$ comes in - or, more specifically, $\operatorname{OutsideIn}(X)$, since the elaboration algorithm is parametrised over the constraint domain $X$. This post is intended as a companion to [the JFP paper introducing OutsideIn](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/jfp-outsidein.pdf), not as a replacement. The core idea is that we can record where the local assumptions are introduced in a tree of _implication constraints_, built out of the constraints in our domain $X$, and these can be reduced - outside-in - to an $X$-specific solver. | |||||
Diverging from the paper slightly, I'll implement the elaborator as a _bidirectional_ algorithm, which lets us take advantage of programmer-written type signatures. The signatures are there for a reason! It's silly to use type signatures as a source of complication (infer a type for the binding, then match it against the signature) rather than as a source of _simplification_. Plus - bidirectional type checking makes higher-rank types almost trivial - I think we can all agree that's a good thing, yeah? | |||||
# The Problem Statement | |||||
We're given a Haskell program - well, a program written in a proper subset of a proper superset of Haskell - and we want to tell whether it's type correct. Our superset extends Haskell 2010 to feature type families, GADTs, rank-N types and functional dependencies, but our subset doesn't contain most of Haskell's niceties, like definitions by equations, guards, or even `if`{.haskell}: you get `case`{.haskell}, and _you're going to like it_. | |||||
Well, more than just telling whether or not the program is type correct, we want to produce an _elaborated program_ in a simpler language - GHC calls this "Core" - if and only if the program is correct, and report a (set of) good type errors otherwise. The elaborated program also has to be type correct, and, ideally, we have a _second_, much smaller type checker over the Core language that calls the big, complicated elaborator out on its bullshit. Because of this, the elaborator has to produce _evidence_ justifying its wilder claims. | |||||
There are two kinds of evidence we need to produce: _coercions_ are inserted where the expected type of an expression is equal to its actual type in a non-trivial way. Consider the program below, and its elaboration to the right: | |||||
<div class=mathpar> | |||||
```haskell | |||||
data T1 a where | |||||
TI :: T1 Int | |||||
TB :: T1 Bool | |||||
foo :: T1 a -> a | |||||
foo x = case x of | |||||
TI -> 123 | |||||
TB -> True | |||||
``` | |||||
```haskell | |||||
data T1 a where | |||||
TI :: (a ~# Int) => T1 a | |||||
TB :: (a ~# Bool) => T1 a | |||||
foo :: T1 a -> a | |||||
foo x = case x of | |||||
TI phi -> 123 |> Sym phi | |||||
TB phi -> True |> Sym phi | |||||
``` | |||||
</div> | |||||
This program, which uses GADTs (see `data ... where`{.haskell}), has two non-trivial equalities between types. In the `TI -> 123`{.haskell} case, we used an `Int`{.haskell}[^1] literal where a value of type `a` was expected. But in that branch, `a` is equal to `Int`{.haskell}! In the elaborated output, this non-trivial local equality is explicitly witnessed by a _coercion variable_ `phi :: a ~# Int`{.haskell}, and the use of `123 :: Int`{.haskell} at type `a` has to be mediated by a _cast_. | |||||
[^1]: Actually, if you know how numeric literals desugar, you might know the actual elaboration produced here is different: `123` becomes `fromInteger @a ($NumInt |> Num (Sym phi)) (123 :: Integer)`{.haskell}. This is because it's totally legit to cast the `Num Int`{.haskell} dictionary to a `Num a`{.haskell} dictionary using the local equality, and, since `123`{.haskell} is sugar for `fromInteger @α (123 :: Integer)`{.haskell}, `α` gets solved to `a`, not `Int`{.haskell}. | |||||
The other kind of evidence is not specific to GADTs, type families, or any other type fanciness: _dictionaries_ witness the existence of a type class `instance`{.haskell}, but, unlike coercions (which only exist to make the second type checker happy), exist at runtime. Consider the program below and its elaboration: | |||||
<div class=mathpar> | |||||
```haskell | |||||
class S a where | |||||
s :: a -> String | |||||
instance S Int where | |||||
s = show | |||||
foo :: Int -> String | |||||
foo x = s (x + 123) | |||||
``` | |||||
```haskell | |||||
data $S a = | |||||
$MkS { s :: a -> String } | |||||
$dSInt :: S Int | |||||
$dSInt = $MkS @Int (show @Int $dShowInt) | |||||
foo :: Int -> String | |||||
foo x = s @Int $dSInt ((+) @Int $dNumInt x 123) | |||||
``` | |||||
</div> | |||||
Type `class`{.haskell}es are elaborated to `data`{.haskell} types, and `instance`{.haskell}s are compiled to actual proper values of those data types. When you apply a function with overloaded type - like `s`, `show` and `(+)` - the compiler inserts the value corresponding to the `instance`{.haskell} that was selected to satisfy the class constraint. Further, `instance`{.haskell}s with superclass constraints become functions from dictionaries to dictionaries, and superclasses on `class`{.haskell}es become values embedded in the dictionary, just like class methods. | |||||
You'll also notice another artifact of elaboration here: the use of `s` at type `Int`{.haskell} became a _visible type application_ `s @Int`{.haskell}. This is, again, to satisfy the second type checker, but it can in principle be used as an actual implementation of polymorphism - one that doesn't box. See [Sixten](https://github.com/ollef/sixten) for a language that exploits this type passing to implement polymorphism without monomorphisation. Type applications are used in every polymorphic function application, not just those with class constraints. | |||||
## Why it's hard | |||||
GADTs complicate the problem of type inference in a way that's become rather famous: GADTs destroy the _principal types_ property. Recall: A **principal type** for a function $f$ is a type $\tau$ such that, $\Gamma \vdash f : \tau$ and, if $\Gamma \vdash f : \sigma$, then $\sigma$ is a substitution instance of $\tau$. Using less KaTeX, a principal type for a function is a _most general type_ for a type. For instance, the functions below are annotated with their principal types: | |||||
```haskell | |||||
id :: a -> a | |||||
id x = x | |||||
const :: a -> b -> a | |||||
const x _ = x | |||||
``` | |||||
But now consider this program using GADTs: | |||||
```haskell | |||||
data T a where | |||||
T1 :: Int -> T Bool | |||||
T2 :: T a | |||||
test x y = case x of | |||||
T1 n -> n > 0 | |||||
T2 -> y | |||||
``` | |||||
One can verify - and we will - that the function test types as either `test :: forall a. T a -> Bool -> Bool`{.haskell} or as `forall a. T a -> a -> a`{.haskell}, but neither of these types is an instance of the other! Let's look at why `test` checks with either of those types, with a _lot_ of detail - mimicking by hand the execution of the algorithm. Don't worry about all the terms I'll be throwing around: they'll all be explained later, I promise! | |||||
<details class=blockquote> | |||||
<summary> **`test :: forall a. T a -> Bool -> Bool`{.haskell}** </summary> | |||||
The algorithm is in checking mode, since we have a type signature. | |||||
1. Introduce a binding `x :: T a`{.haskell} into scope. We must check the body of the function against the type `Bool -> Bool`{.haskell} | |||||
2. Introduce a binding `y :: Bool` into scope. We must check the body of the function against the type `Bool`{.haskell}. | |||||
3. Check the `case`{.haskell} expression against the type `Bool`{.haskell}. There are two branches. | |||||
* `T1 n -> n > 0`{.haskell}: | |||||
* Instantiate the type of the constructor `T1 :: forall a. (a ~ Bool) => Int -> T a`{.haskell} with `a := X`{.haskell} to get the type `T1 :: a ~ Bool => Int -> T a`{.haskell}, where `a` is a _skolem_ type variable. The type variable `a` becomes a skolem and not a unification variable because it is an _existential_ of T1. | |||||
* Introduce the local equality assumption `phi :: a ~ Bool`{.haskell} and the variable `n :: Int`{.haskell}. | |||||
* Check that `n > 0 :: Bool`{.haskell}. For brevity, we'll take this to be one atomic step, which succeeds, but the real algorithm must treat all of those subexpressions independently. | |||||
* `T2 -> y`{.haskell}. We must check that `y :: Bool`{.haskell}, which succeeds. | |||||
Since all of these steps succeed (most of them are introducing variables and can't fail) - the program is type-correct. Note that in the branch with a local equality, our assumption that `a ~ Bool`{.haskell} wasn't used. | |||||
</details> | |||||
<details class=blockquote> | |||||
<summary> **`test :: forall a. T a -> a -> a`{.haskell}** </summary> | |||||
The algorithm is in checking mode, since we have a type signature. | |||||
1. Introduce a binding `x :: T a`{.haskell} into scope. We must check the body of the function against the type `a -> a`{.haskell} | |||||
2. Introduce a binding `y :: a` into scope. We must check the body of the function against the type `a`{.haskell}. | |||||
3. Check the `case`{.haskell} expression against the type `Bool`{.haskell}. There are two branches. | |||||
* `T1 n -> n > 0`{.haskell}: | |||||
* Instantiate the type of the constructor `T1 :: forall a. (a ~ Bool) => Int -> T a`{.haskell} with `a := X`{.haskell} to get the type `T1 :: a ~ Bool => Int -> T a`{.haskell}, where `a` is a _skolem_ type variable. The type variable `a` becomes a skolem and not a unification variable because it is an _existential_ of T1. | |||||
* Introduce the local equality assumption `phi :: a ~ Bool`{.haskell} and the variable `n :: Int`{.haskell}. | |||||
* Check that `n > 0 :: a`{.haskell}. We infer that `n > 0 :: Bool`{.haskell}, and we must unify `Bool ~ a`{.haskell}. This unification succeeds because of the given equality `phi :: a ~ Bool`{.haskell}, which we are free to invert. | |||||
* `T2 -> y`{.haskell}. We must check that `y :: a`{.haskell}, which succeeds. | |||||
Since all of these steps succeed (most of them are introducing variables and can't fail) - the program is type-correct. In this typing, compared with the previous, we made use of the assumption `phi :: a ~ Bool` brought into scope by the match against the constructor `T1 n`. | |||||
</details> | |||||
The execution trace for both cases is remarkably similar - the only difference is in that if the function is typed as `T a -> a -> a`{.haskell}, we must make use of the local equality brought into scope to justify that we're allowed to use a value nominally of type `Bool`{.haskell} as one of type `a`. We are free to do this, but it's not obvious if, without a type annotation to guide us, we should. Consider now the following very minor alteration to test: | |||||
```haskell | |||||
test x y = case x of | |||||
T1 n -> n > 0 | |||||
T2 -> not y | |||||
``` | |||||
The only possible type for this program is `T a -> Bool -> Bool`{.haskell}, and so, we can decide without any major complications that the GADT equality should _not_ be used. | |||||
# How To Check Types | |||||
In this section we'll solve the infinitely simpler problem of elaborating a language with rank-N types and type classes - including functional dependencies - but crucially, no GADTs. To do this we'll use a _bidirectional_, _constraint-based_ elaboration algorithm. | |||||
First, bidirectional means that, unlike in a type _inference_ system, type information flows both in and out of the algorithm. Practically speaking, we have two functions to implement the case where type information is an input to the algorithm (`check`{.haskell}) and one where type information is a return from the algorithm (`infer`{.haskell}). | |||||
```haskell | |||||
infer :: Raw.Expr -> Elab (Core.Term, Core.Type) | |||||
check :: Raw.Expr -> Core.Type -> Elab Core.Term | |||||
``` | |||||
If you know how to infer a type for an expression `e` but you need to check it against a known type `wanted_type`, you can do it by unification, whereas if you know how to check an expression `f` against a type but you need to infer a type for it, you can do it by inventing a new _metavariable_ and checking against that[^2]: | |||||
[^2]: In the OutsideIn(X) paper, metavariables are known as _unification variables_. The term _metavariable_ is common in the dependently-typed world, whereas _unification variable_ is more common among Haskell and ML researchers. | |||||
<div class=mathpar> | |||||
```haskell | |||||
check e wanted_type = do | |||||
(elab, actual_type) <- infer x | |||||
unify wanted_type actual_type | |||||
pure elab | |||||
``` | |||||
```haskell | |||||
infer f = do | |||||
ty <- newMeta | |||||
elab <- check f ty | |||||
pure (elab, ty) | |||||
``` | |||||
</div> | |||||
Constraint-based means that, at least conceptually, the algorithm works by first generating constraints by walking through the AST (we do this bidirectionally), and only later solving the generated constraints. But, as a very fruitful optimisation, there are cases where the constraints need not be stashed away for later: If we want to solve a unification problem, for instance, where a metavariable is being compared against a concrete type, and we're free to solve the variable with that type, we might as well do it inline. | |||||
_Elaboration_ is a natural extension of "type checking" in which the program is both checked and transformed into a simpler intermediate representation in the same step. The name "type checker" sort-of implies that the output is a boolean (or, more realistically, a list of errors): this is rarely true in practice, but I still prefer the use of the name "elaborator" to make clear that the output is a different _language_ from the input, and not merely a type-annotated version of the input. | |||||
I'm going to start by talking about the intermediate language we'll elaborate into, _System $F_C$, first. This is because of an assumption I'm making: I'm assuming most of my readers are familiar with Haskell - at least in passing - but not very familiar with GHC's intermediate language. That's why we start there! | |||||
## Our Target Language | |||||
System $F_C$, as the name kind-of sort-of implies, is a superset of System $F$, the _second-order_ lambda calculus. For those not in the loop, System F has all the same features of a normal typed lambda calculus (variables, lambda abstraction, application, algebraic data types, and pattern matching[^3]), but additionally features _first class polymorphism_. Roughly, this means that in System F, a `forall`{.haskell} type can appear everywhere a "normal" type can appear - you could form the type `[forall a. a -> a]`{.haskell} of "lists of identity functions", for instance. | |||||
[^3]: If you disagree with the inclusion of algebraic data types and pattern matching in the list of features of a "normal typed lambda calculus"---there's nothing you can do about it, this is my blog, lol. | |||||
Now, this doesn't mean that first class polymorphism is available to languages that elaborate into System $F_C$ - GHC, for instance, struggled with what they call "impredicative polymorphism" for years, up until very recently. Amulet did a slightly better job because, being a research toy and not a production compiler (that happens to be also be a research toy), there was less code to move around by implementing support for first-class polymorphism. | |||||
Since `forall`{.haskell} is a new type former, it also has a corresponding introduction form and elimination form. The introduction rule says that if you can build a term `e : t` in a context where `a` is a type variable of kind `k`, then the term `Λ (a :: k). e` has type `forall (a :: k). σ`{.haskell}. To stick with ASCII for "control" symbols, I'm going to write `Λ (a :: k)` as `\ @(a :: k)`{.haskell}, omitting `κ` if it is obvious - Also, I'm sticking with Haskell notation, even if `::` should really mean cons. | |||||
Similarly, the elimination rule says that to consume an expression `e :: forall (a :: k). t`{.haskell}, what we need to do is come up with a _type_ `s :: k`{.haskell}. Then we can _instantiate_ (using a different word so as to not overload "apply") `e`{.haskell} at `s`{.haskell} to get a term `e @s :: t[s/a]`{.haskell} - where `t[s/a]` denotes the substitution of `s` for `a` in `t`, avoiding capture. | |||||
Here's a simple Haskell program, and its translation into the notation I'll use for $F_C$. We'll go over it afterwards. | |||||
<div class=mathpar> | |||||
```haskell | |||||
data List a | |||||
= Nil | |||||
| Cons a (List a) | |||||
map :: (a -> b) -> List a -> List a | |||||
-- this line intentionally left blank | |||||
map f (Cons x xs) = Cons (f x) (map f xs) | |||||
map f Nil = Nil | |||||
``` | |||||
```haskell | |||||
data List :: * -> * where | |||||
Nil :: forall a. List a | |||||
Cons :: forall a. a -> List a -> List a | |||||
map :: forall a b. (a -> b) -> List a -> List a | |||||
map @a @b f x = case x of | |||||
Cons x xs -> Cons @b (f x) (map @a @b f xs) | |||||
Nil -> Nil @b | |||||
``` | |||||
</div> | |||||
Let's go over the differences: | |||||
* In Haskell, we allow datatype declarations using the Haskell 98 syntax, but in $F_C$ all data types are given in GADT syntax. Furthermore, `List`{.haskell} was given a kind annotation when it was elaborated - the kind of `List`{.haskell} says it maps ground types to ground types. By "ground type" I mean something that's potentially inhabited, e.g. `Int`{.haskell} or `Void`{.haskell}, but not `Maybe`. | |||||
Where does the kind annotation come from? Well, we know `List` will have a function kind since it has one argument, and we know its return kind will be `*`{.haskell} since all data types are in `*`{.haskell}. That means we kind-check the constructors with `List :: κ -> a`{.haskell} in scope, where `κ` is a fresh metavariable. The type of `Nil`{.haskell} doesn't fix `κ`, but the type of `Cons`{.haskell} - `a` is used on the left of an arrow, so it must have kind `*`. | |||||
* Haskell has definition by equations, but in $F_C$ we simply have type signatures and definitions. We can translate the equations into a case tree using a rather involved - but mechanical - process, and, to avoid that complication, the subset of Haskell our elaborator works will not support equations. It's mostly immaterial to elaboration, anyway. | |||||
* In Haskell, the type signature `map :: (a -> b) -> List a -> List b`{.haskell} is written with implicit binders for the type variables `a` and `b`, so that they're seemingly free. This is not the case, of course, and so in $F_C$ we must write out what `forall`{.haskell}s we mean. This is less relevant in this case, where there are no free type variables in the environment, but specifying `forall`{.haskell}s is essential when we have `ScopedTypeVariables`. | |||||
* Finally, all of the polymorphism implicit in the Haskell version of the program was made explicit in its elaboration into $F_C$. For instance, the type of the `map`{.haskell} function has two `forall`{.haskell}s, so its definition must begin with a corresponding number of `\@`{.haskell}s (which I moved onto the RHS for presentation purposes - don't want lines getting too wide). | |||||
Similarly, the list `Cons`{.haskell}tructors were used as expressions of type `List a` in Haskell, but their $F_C$ types start with a `forall`{.haskell}, meaning we have to instantiate them - `Nil @b`{.haskell}, `Cons @b`{.haskell} - at the return type of the `map` function. | |||||
We represent the language using a data type. Syntax productions in the language become constructors of our data type. For clarity of presentation, I'll use `Text`{.haskell}s for variable names. This is a bad idea, and it'll make a lot of you very angry - for good reason! Dealing with binders is _hard_, and using strings for identifiers is quite possibly the worst solution. It'd be more principled to use de Bruijn indices, or locally nameless, or something. But - that's a needless complication, so, in the interest of clarity, I'll just use strings. | |||||
Since our language contains type applications, we "need" to define types before expressions. Well, this is a Haskell program, so we don't _need_ to - Haskell programs are not lists of definitions, but rather _directed graphs_ of definitions, so that source order doesn't matter - but for clarity, we define the type of types before the type of expressions. | |||||
```haskell | |||||
module Core where | |||||
import qualified Data.Text as T | |||||
import Data.Text (Text) | |||||
data Kind | |||||
= TypeKi | |||||
-- ^ The kind '*' | |||||
| ConstraintKi | |||||
-- ^ The kind 'Constraint' | |||||
| FunKi Kind Kind | |||||
-- ^ κ → κ | |||||
deriving (Eq, Show) | |||||
data Type | |||||
= VarTy Text Kind | |||||
-- ^ Type variables α | |||||
| AppTy Type Type | |||||
-- ^ The type being applied is never a constructor, | |||||
-- always another AppTy or a VarTy. | |||||
| ConTy Text [Type] | |||||
-- ^ Type constructor applied to some arguments. | |||||
| ForAllTy Text Kind Type | |||||
-- ^ Polymorphic types | |||||
| FunTy Type Type | |||||
-- ^ Function types | |||||
deriving (Eq, Show) | |||||
``` | |||||
Throughout the language, variables (resp. type variables) are annotated with the type (resp. kind) with which they are introduced. More, our type of expressions unifies `\ @a`{.haskell} and `\ x`{.haskell}, as well as both application forms, by delegating to `Binder`{.haskell} and `Arg`{.haskell} types. | |||||
```{.haskell style="padding-bottom: 0"} | |||||
data Binder = TypeBinder Text | ExprBinder Text | |||||
deriving (Eq, Show) | |||||
data Arg = TypeArg Type | ExprArg Expr | |||||
deriving (Eq, Show) | |||||
data Expr | |||||
= Var Text Type | |||||
| App Expr Arg | |||||
| Lam Binder Expr | |||||
-- continues | |||||
``` | |||||
For `Let`{.haskell}, we introduce yet another auxiliary type. A `Bind`{.haskell} represents a _binding group_, a group of mutually recursive definitions. Binding groups do not correspond 1:1 with `let`{.haskell}s in Haskell, for instance, the Haskell program on the left is elaborated into the Core expression on the right: | |||||
<div class="mathpar"> | |||||
```haskell | |||||
let quux x = bar (x - 1) | |||||
foo = 1 | |||||
bar x = quux x + foo | |||||
in foo | |||||
``` | |||||
```haskell | |||||
Let (NonRec "foo" (Lit 1)) $ | |||||
Let (Rec [ ("quux", Lam (ExprBinder ...) ... | |||||
, ("bar", Lam (ExprBinder ...) ...) ] $ | |||||
Var "foo" | |||||
``` | |||||
</div> | |||||
As you can probably imagine, the way I arrived at this definition involves.. Graphs. Yes, it's unfortunate, but it's the only way to correctly describe how Haskell declaration blocks - that includes the top level - are type checked. The Haskell report mandates that declaration groups - in the top level, a `let`{.haskell} expression or a `where`{.haskell} clause - should be sorted into strongly connected components, and type-checked in dependency order. Each of these connected components becomes a `Rec`{.haskell} binding! | |||||
We define the auxiliary `Bind`{.haskell} type.. somewhere else, since we still have cases to add to the `Expr`{.haskell}. It's either a connected graph of mutually recursive binders, containing a list of pairs of names and expressions, or a single binder - in which case we unpack the pair. | |||||
```{.haskell style="padding-top: 0; padding-bottom: 0;"} | |||||
-- continued | |||||
| Let [Bind] Expr | |||||
data Bind | |||||
= NonRec Text Expr | |||||
| Rec [(Text, Expr)] | |||||
deriving (Eq, Show) | |||||
-- continues | |||||
``` |
@ -1,232 +0,0 @@ | |||||
This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2021/Arch Linux) (preloaded format=pdflatex 2021.5.25) 26 AUG 2021 19:58 | |||||
entering extended mode | |||||
restricted \write18 enabled. | |||||
%&-line parsing enabled. | |||||
**\nonstopmode \input{/home/me/Sync/Projects/blag/rubtmpjd3t_2bh.tex} | |||||
(/home/me/Sync/Projects/blag/rubtmpjd3t_2bh.tex | |||||
(/usr/share/texmf-dist/tex/latex/base/article.cls | |||||
Document Class: article 2020/04/10 v1.4m Standard LaTeX document class | |||||
(/usr/share/texmf-dist/tex/latex/base/size10.clo | |||||
File: size10.clo 2020/04/10 v1.4m Standard LaTeX file (size option) | |||||
) | |||||
\c@part=\count179 | |||||
\c@section=\count180 | |||||
\c@subsection=\count181 | |||||
\c@subsubsection=\count182 | |||||
\c@paragraph=\count183 | |||||
\c@subparagraph=\count184 | |||||
\c@figure=\count185 | |||||
\c@table=\count186 | |||||
\abovecaptionskip=\skip47 | |||||
\belowcaptionskip=\skip48 | |||||
\bibindent=\dimen138 | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/preview/preview.sty | |||||
Package: preview 2017/04/24 12.3 (AUCTeX/preview-latex) | |||||
(/usr/share/texmf-dist/tex/generic/luatex85/luatex85.sty | |||||
Package: luatex85 2016/06/15 v1.4 pdftex aliases for luatex | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/preview/prtightpage.def | |||||
\PreviewBorder=\dimen139 | |||||
) | |||||
\pr@snippet=\count187 | |||||
\pr@box=\box47 | |||||
\pr@output=\toks15 | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/amsmath/amsmath.sty | |||||
Package: amsmath 2020/09/23 v2.17i AMS math features | |||||
\@mathmargin=\skip49 | |||||
For additional information on amsmath, use the `?' option. | |||||
(/usr/share/texmf-dist/tex/latex/amsmath/amstext.sty | |||||
Package: amstext 2000/06/29 v2.01 AMS text | |||||
(/usr/share/texmf-dist/tex/latex/amsmath/amsgen.sty | |||||
File: amsgen.sty 1999/11/30 v2.0 generic functions | |||||
\@emptytoks=\toks16 | |||||
\ex@=\dimen140 | |||||
)) | |||||
(/usr/share/texmf-dist/tex/latex/amsmath/amsbsy.sty | |||||
Package: amsbsy 1999/11/29 v1.2d Bold Symbols | |||||
\pmbraise@=\dimen141 | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/amsmath/amsopn.sty | |||||
Package: amsopn 2016/03/08 v2.02 operator names | |||||
) | |||||
\inf@bad=\count188 | |||||
LaTeX Info: Redefining \frac on input line 234. | |||||
\uproot@=\count189 | |||||
\leftroot@=\count190 | |||||
LaTeX Info: Redefining \overline on input line 399. | |||||
\classnum@=\count191 | |||||
\DOTSCASE@=\count192 | |||||
LaTeX Info: Redefining \ldots on input line 496. | |||||
LaTeX Info: Redefining \dots on input line 499. | |||||
LaTeX Info: Redefining \cdots on input line 620. | |||||
\Mathstrutbox@=\box48 | |||||
\strutbox@=\box49 | |||||
\big@size=\dimen142 | |||||
LaTeX Font Info: Redeclaring font encoding OML on input line 743. | |||||
LaTeX Font Info: Redeclaring font encoding OMS on input line 744. | |||||
\macc@depth=\count193 | |||||
\c@MaxMatrixCols=\count194 | |||||
\dotsspace@=\muskip16 | |||||
\c@parentequation=\count195 | |||||
\dspbrk@lvl=\count196 | |||||
\tag@help=\toks17 | |||||
\row@=\count197 | |||||
\column@=\count198 | |||||
\maxfields@=\count199 | |||||
\andhelp@=\toks18 | |||||
\eqnshift@=\dimen143 | |||||
\alignsep@=\dimen144 | |||||
\tagshift@=\dimen145 | |||||
\tagwidth@=\dimen146 | |||||
\totwidth@=\dimen147 | |||||
\lineht@=\dimen148 | |||||
\@envbody=\toks19 | |||||
\multlinegap=\skip50 | |||||
\multlinetaggap=\skip51 | |||||
\mathdisplay@stack=\toks20 | |||||
LaTeX Info: Redefining \[ on input line 2923. | |||||
LaTeX Info: Redefining \] on input line 2924. | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/amsfonts/amssymb.sty | |||||
Package: amssymb 2013/01/14 v3.01 AMS font symbols | |||||
(/usr/share/texmf-dist/tex/latex/amsfonts/amsfonts.sty | |||||
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support | |||||
\symAMSa=\mathgroup4 | |||||
\symAMSb=\mathgroup5 | |||||
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98. | |||||
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold' | |||||
(Font) U/euf/m/n --> U/euf/b/n on input line 106. | |||||
)) | |||||
(/usr/share/texmf-dist/tex/latex/jknapltx/mathrsfs.sty | |||||
Package: mathrsfs 1996/01/01 Math RSFS package v1.0 (jk) | |||||
\symrsfs=\mathgroup6 | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty | |||||
(/usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty | |||||
(/usr/share/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty | |||||
(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex | |||||
\pgfutil@everybye=\toks21 | |||||
\pgfutil@tempdima=\dimen149 | |||||
\pgfutil@tempdimb=\dimen150 | |||||
(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex)) | |||||
(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def | |||||
\pgfutil@abb=\box50 | |||||
) | |||||
(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex | |||||
(/usr/share/texmf-dist/tex/generic/pgf/pgf.revision.tex) | |||||
Package: pgfrcs 2020/12/27 v3.1.8b (3.1.8b) | |||||
)) | |||||
Package: pgf 2020/12/27 v3.1.8b (3.1.8b) | |||||
(/usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty | |||||
(/usr/share/texmf-dist/tex/latex/graphics/graphicx.sty | |||||
Package: graphicx 2020/09/09 v1.2b Enhanced LaTeX Graphics (DPC,SPQR) | |||||
(/usr/share/texmf-dist/tex/latex/graphics/keyval.sty | |||||
Package: keyval 2014/10/28 v1.15 key=value parser (DPC) | |||||
\KV@toks@=\toks22 | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/graphics/graphics.sty | |||||
Package: graphics 2020/08/30 v1.4c Standard LaTeX Graphics (DPC,SPQR) | |||||
(/usr/share/texmf-dist/tex/latex/graphics/trig.sty | |||||
Package: trig 2016/01/03 v1.10 sin cos tan (DPC) | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg | |||||
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration | |||||
) | |||||
Package graphics Info: Driver file: pdftex.def on input line 105. | |||||
(/usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def | |||||
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex | |||||
)) | |||||
\Gin@req@height=\dimen151 | |||||
\Gin@req@width=\dimen152 | |||||
) | |||||
(/usr/share/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty | |||||
(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex | |||||
Package: pgfsys 2020/12/27 v3.1.8b (3.1.8b) | |||||
(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex | |||||
\pgfkeys@pathtoks=\toks23 | |||||
\pgfkeys@temptoks=\toks24 | |||||
(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex | |||||
\pgfkeys@tmptoks=\toks25 | |||||
)) | |||||
\pgf@x=\dimen153 | |||||
\pgf@y=\dimen154 | |||||
\pgf@xa=\dimen155 | |||||
\pgf@ya=\dimen156 | |||||
\pgf@xb=\dimen157 | |||||
\pgf@yb=\dimen158 | |||||
\pgf@xc=\dimen159 | |||||
\pgf@yc=\dimen160 | |||||
\pgf@xd=\dimen161 | |||||
\pgf@yd=\dimen162 | |||||
\w@pgf@writea=\write3 | |||||
\r@pgf@reada=\read2 | |||||
\c@pgf@counta=\count266 | |||||
\c@pgf@countb=\count267 | |||||
\c@pgf@countc=\count268 | |||||
\c@pgf@countd=\count269 | |||||
\t@pgf@toka=\toks26 | |||||
\t@pgf@tokb=\toks27 | |||||
\t@pgf@tokc=\toks28 | |||||
\pgf@sys@id@count=\count270 | |||||
(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg | |||||
File: pgf.cfg 2020/12/27 v3.1.8b (3.1.8b) | |||||
) | |||||
Driver file for pgf: pgfsys-pdftex.def | |||||
(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def | |||||
File: pgfsys-pdftex.def 2020/12/27 v3.1.8b (3.1.8b) | |||||
(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def | |||||
File: pgfsys-common-pdf.def 2020/12/27 v3.1.8b (3.1.8b) | |||||
))) | |||||
(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex | |||||
File: pgfsyssoftpath.code.tex 2020/12/27 v3.1.8b (3.1.8b) | |||||
\pgfsyssoftpath@smallbuffer@items=\count271 | |||||
\pgfsyssoftpath@bigbuffer@items=\count272 | |||||
) | |||||
(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex | |||||
File: pgfsysprotocol.code.tex 2020/12/27 v3.1.8b (3.1.8b) | |||||
)) | |||||
(/usr/share/texmf-dist/tex/latex/xcolor/xcolor.sty | |||||
Package: xcolor 2016/05/11 v2.12 LaTeX color extensions (UK) | |||||
(/usr/share/texmf-dist/tex/latex/graphics-cfg/color.cfg | |||||
File: color.cfg 2016/01/02 v1.6 sample color configuration | |||||
) | |||||
! Interruption. | |||||
<argument> ...sextension \else \@classoptionslist | |||||
,\fi \@curroptions , | |||||
l.216 \ProcessOptions\relax | |||||
? | |||||
! Emergency stop. | |||||
<argument> ...sextension \else \@classoptionslist | |||||
,\fi \@curroptions , | |||||
l.216 \ProcessOptions\relax | |||||
End of file on the terminal! | |||||
Here is how much of TeX's memory you used: | |||||
3149 strings out of 478994 | |||||
50546 string characters out of 5864751 | |||||
338722 words of memory out of 5000000 | |||||
20600 multiletter control sequences out of 15000+600000 | |||||
403430 words of font info for 27 fonts, out of 8000000 for 9000 | |||||
1141 hyphenation exceptions out of 8191 | |||||
117i,0n,119p,402b,36s stack positions out of 5000i,500n,10000p,200000b,80000s | |||||
! ==> Fatal error occurred, no output PDF file produced! |
@ -3,6 +3,8 @@ | |||||
{-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||
{-# LANGUAGE BlockArguments #-} | {-# LANGUAGE BlockArguments #-} | ||||
{-# LANGUAGE LambdaCase #-} | {-# LANGUAGE LambdaCase #-} | ||||
{-# LANGUAGE StandaloneDeriving #-} | |||||
{-# LANGUAGE DeriveAnyClass #-} | |||||
import Control.DeepSeq (rnf) | import Control.DeepSeq (rnf) | ||||
import Control.Concurrent | import Control.Concurrent | ||||
@ -51,6 +53,8 @@ import Data.Hashable (Hashable (hashWithSalt)) | |||||
import GHC.Stack | import GHC.Stack | ||||
import Text.Read (readMaybe) | import Text.Read (readMaybe) | ||||
import GHC.Show (showCommaSpace) | import GHC.Show (showCommaSpace) | ||||
import Data.Traversable | |||||
import qualified Data.Text.Lazy as LT | |||||
readerOpts :: ReaderOptions | readerOpts :: ReaderOptions | ||||
readerOpts = def { readerExtensions = pandocExtensions | readerOpts = def { readerExtensions = pandocExtensions | ||||
@ -73,11 +77,11 @@ writerOptions = do | |||||
rssfeed :: FeedConfiguration | rssfeed :: FeedConfiguration | ||||
rssfeed | rssfeed | ||||
= FeedConfiguration { feedTitle = "Abigail's Blag: Latest articles" | |||||
= FeedConfiguration { feedTitle = "Amelia's Blag: Latest articles" | |||||
, feedDescription = "" | , feedDescription = "" | ||||
, feedAuthorName = "Abigail Magalhães" | |||||
, feedAuthorEmail = "m[email protected]" | |||||
, feedRoot = "https://abby.how" | |||||
, feedAuthorName = "Amélia" | |||||
, feedAuthorEmail = "m[email protected]" | |||||
, feedRoot = "https://amelia.how" | |||||
} | } | ||||
conf :: Configuration | conf :: Configuration | ||||
@ -86,10 +90,9 @@ conf = def { destinationDirectory = ".site" | |||||
, tmpDirectory = ".store/tmp" | , tmpDirectory = ".store/tmp" | ||||
, deployCommand = "./sync" } | , deployCommand = "./sync" } | ||||
katexFilter :: IORef KatexCache -> Pandoc -> Compiler Pandoc | |||||
katexFilter :: MVar KatexCache -> Pandoc -> Compiler Pandoc | |||||
katexFilter cacheVar (Pandoc meta doc) = | katexFilter cacheVar (Pandoc meta doc) = | ||||
do | do | ||||
initCache <- unsafeCompiler (readIORef cacheVar) | |||||
id <- compilerUnderlying <$> compilerAsk | id <- compilerUnderlying <$> compilerAsk | ||||
t <- getMetadata id | t <- getMetadata id | ||||
@ -110,7 +113,7 @@ katexFilter cacheVar (Pandoc meta doc) = | |||||
go :: String -> [String] -> Inline -> Compiler Inline | go :: String -> [String] -> Inline -> Compiler Inline | ||||
go id abbrevs (Math kind math) = unsafeCompiler $ do | go id abbrevs (Math kind math) = unsafeCompiler $ do | ||||
cache <- readIORef cacheVar | |||||
cache <- readMVar cacheVar | |||||
case HMap.lookup (id, kind, math) (spanMap cache) of | case HMap.lookup (id, kind, math) (spanMap cache) of | ||||
Just x -> pure (RawInline "html" x) | Just x -> pure (RawInline "html" x) | ||||
Nothing -> do | Nothing -> do | ||||
@ -119,7 +122,7 @@ katexFilter cacheVar (Pandoc meta doc) = | |||||
(contents, _) <- readProcessBS "node_modules/.bin/katex" args . BS.fromStrict . T.encodeUtf8 $ math | (contents, _) <- readProcessBS "node_modules/.bin/katex" args . BS.fromStrict . T.encodeUtf8 $ math | ||||
let text = T.init . T.init . T.decodeUtf8 . BS.toStrict $ contents | let text = T.init . T.init . T.decodeUtf8 . BS.toStrict $ contents | ||||
atomicModifyIORef' cacheVar (\m -> (bumpCacheEntry cache id abbrevs kind text math, ())) | |||||
modifyMVar cacheVar (\m -> pure (bumpCacheEntry cache id abbrevs kind text math, ())) | |||||
pure $ RawInline "html" text | pure $ RawInline "html" text | ||||
go id _ x = pure x | go id _ x = pure x | ||||
@ -200,7 +203,7 @@ saveSynopsys (Pandoc meta doc) = | |||||
case dropWhile (not . isParagraph) doc of | case dropWhile (not . isParagraph) doc of | ||||
p:ps -> do | p:ps -> do | ||||
saveSnapshot "synopsys" =<< makeItem (map removeFootnotes (take n (p:ps))) | |||||
saveSnapshot "synopsys-block" =<< makeItem (map removeFootnotes (take n (p:ps))) | |||||
pure () | pure () | ||||
[] -> pure () | [] -> pure () | ||||
pure $ Pandoc meta doc | pure $ Pandoc meta doc | ||||
@ -254,23 +257,12 @@ saveTableOfContents (Pandoc meta input) = | |||||
toc :: Block | toc :: Block | ||||
toc = list (into headers) | toc = list (into headers) | ||||
sassImporter :: SassImporter | |||||
sassImporter = SassImporter 0 go where | |||||
go "normalize" _ = do | |||||
c <- readFile "node_modules/normalize.css/normalize.css" | |||||
pure [ SassImport { importPath = Nothing | |||||
, importAbsolutePath = Nothing | |||||
, importSource = Just c | |||||
, importSourceMap = Nothing | |||||
} ] | |||||
go _ _ = pure [] | |||||
setup :: IO (IORef KatexCache) | |||||
setup :: IO (MVar KatexCache) | |||||
setup = do | setup = do | ||||
setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/" | setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/" | ||||
loadCache | loadCache | ||||
compiler :: IORef KatexCache -> Compiler (Item String) | |||||
compiler :: MVar KatexCache -> Compiler (Item String) | |||||
compiler katexCache = do | compiler katexCache = do | ||||
wops <- writerOptions | wops <- writerOptions | ||||
pandocCompilerWithTransformM readerOpts wops $ | pandocCompilerWithTransformM readerOpts wops $ | ||||
@ -279,7 +271,6 @@ compiler katexCache = do | |||||
>=> saveSynopsys | >=> saveSynopsys | ||||
>=> saveWordCount | >=> saveWordCount | ||||
>=> saveTableOfContents | >=> saveTableOfContents | ||||
>=> pure . addLanguageTag | |||||
main :: IO () | main :: IO () | ||||
main = setup >>= \katexCache -> hakyllWith conf $ do | main = setup >>= \katexCache -> hakyllWith conf $ do | ||||
@ -301,9 +292,21 @@ main = setup >>= \katexCache -> hakyllWith conf $ do | |||||
match "css/*.scss" $ do | match "css/*.scss" $ do | ||||
route $ setExtension "css" | route $ setExtension "css" | ||||
compile $ sassCompilerWith def { sassOutputStyle = SassStyleCompressed | |||||
, sassImporters = Just [ sassImporter ] | |||||
} | |||||
compile $ do | |||||
imports <- unsafeCompiler $ newMVar ([] :: [(String, String)]) | |||||
let add f p = modifyMVar imports (\x -> pure ((f, p):x, [])) | |||||
body <- sassCompilerWith def | |||||
{ sassOutputStyle = SassStyleCompressed | |||||
, sassImporters = Just [ SassImporter 0 add ] | |||||
} | |||||
list <- unsafeCompiler $ takeMVar imports | |||||
for list $ \(req, path) -> do | |||||
load (fromFilePath ("css/" ++ reverse (dropWhile (/= '.') (reverse req)) ++ "scss")) | |||||
:: Compiler (Item String) | |||||
pure body | |||||
match "diagrams/**/*.tex" $ do | match "diagrams/**/*.tex" $ do | ||||
route $ setExtension "svg" | route $ setExtension "svg" | ||||
@ -326,12 +329,11 @@ main = setup >>= \katexCache -> hakyllWith conf $ do | |||||
>>= loadAndApplyTemplate "templates/default.html" postCtx | >>= loadAndApplyTemplate "templates/default.html" postCtx | ||||
>>= relativizeUrls | >>= relativizeUrls | ||||
loadSnapshot id "synopsys" | |||||
>>= saveSnapshot "synopsys" | |||||
loadSnapshot id "synopsys-block" | |||||
>>= saveSnapshot "synopsys-text" | |||||
. writePandocWith wops | . writePandocWith wops | ||||
. fmap (Pandoc mempty) | . fmap (Pandoc mempty) | ||||
pure r | pure r | ||||
match "pages/posts/*.lhs" $ version "raw" $ do | match "pages/posts/*.lhs" $ version "raw" $ do | ||||
@ -375,7 +377,7 @@ main = setup >>= \katexCache -> hakyllWith conf $ do | |||||
path <- toFilePath <$> getUnderlying | path <- toFilePath <$> getUnderlying | ||||
contents <- itemBody <$> getResourceBody | contents <- itemBody <$> getResourceBody | ||||
debugCompiler ("Loaded syntax definition from " ++ show path) | debugCompiler ("Loaded syntax definition from " ++ show path) | ||||
res <- unsafeCompiler (Sky.parseSyntaxDefinitionFromString path contents) | |||||
let res = Sky.parseSyntaxDefinitionFromText path (LT.pack contents) | |||||
_ <- saveSnapshot "syntax" =<< case res of | _ <- saveSnapshot "syntax" =<< case res of | ||||
Left e -> fail e | Left e -> fail e | ||||
Right x -> makeItem x | Right x -> makeItem x | ||||
@ -387,7 +389,7 @@ main = setup >>= \katexCache -> hakyllWith conf $ do | |||||
route idRoute | route idRoute | ||||
compile $ do | compile $ do | ||||
let feedCtx = postCtx <> bodyField "description" | let feedCtx = postCtx <> bodyField "description" | ||||
posts <- fmap (take 10) . recentFirst =<< onlyPublic =<< loadAllSnapshots ("pages/posts/*" .&&. hasNoVersion) "synopsys" | |||||
posts <- fmap (take 10) . recentFirst =<< onlyPublic =<< loadAllSnapshots ("pages/posts/*" .&&. hasNoVersion) "synopsys-text" | |||||
renderRss rssfeed feedCtx posts | renderRss rssfeed feedCtx posts | ||||
onlyPublic :: [Item String] -> Compiler [Item String] | onlyPublic :: [Item String] -> Compiler [Item String] | ||||
@ -401,7 +403,7 @@ onlyPublic = filterM isPublic where | |||||
postCtx :: Context String | postCtx :: Context String | ||||
postCtx = | postCtx = | ||||
dateField "date" "%B %e, %Y" | dateField "date" "%B %e, %Y" | ||||
<> snapshotField "synopsys" "synopsys" | |||||
<> snapshotField "synopsys" "synopsys-text" | |||||
<> snapshotField "words" "wc" | <> snapshotField "words" "wc" | ||||
<> snapshotField' render "toc" "table-of-contents" | <> snapshotField' render "toc" "table-of-contents" | ||||
<> defaultContext | <> defaultContext | ||||
@ -486,7 +488,7 @@ pathFromTitle meta = | |||||
foldMapM :: (Monad w, Monoid m, Foldable f) => (a -> w m) -> f a -> w m | foldMapM :: (Monad w, Monoid m, Foldable f) => (a -> w m) -> f a -> w m | ||||
foldMapM k = foldr (\x y -> do { m <- k x; (m <>) <$> y }) (pure mempty) | foldMapM k = foldr (\x y -> do { m <- k x; (m <>) <$> y }) (pure mempty) | ||||
loadCache :: HasCallStack => IO (IORef KatexCache) | |||||
loadCache :: HasCallStack => IO (MVar KatexCache) | |||||
loadCache = do | loadCache = do | ||||
t <- doesFileExist ".katex_cache" | t <- doesFileExist ".katex_cache" | ||||
let fixup (a, b, c) = KatexCache (HMap.fromList a) (HMap.fromList b) (HMap.fromList c) | let fixup (a, b, c) = KatexCache (HMap.fromList a) (HMap.fromList b) (HMap.fromList c) | ||||
@ -494,16 +496,16 @@ loadCache = do | |||||
then (fixup <$> decodeFile ".katex_cache") `catch` \e -> | then (fixup <$> decodeFile ".katex_cache") `catch` \e -> | ||||
const (print e *> pure (KatexCache mempty mempty mempty)) (e :: SomeException) | const (print e *> pure (KatexCache mempty mempty mempty)) (e :: SomeException) | ||||
else pure (KatexCache mempty mempty mempty) | else pure (KatexCache mempty mempty mempty) | ||||
var <- newIORef map | |||||
var <- newMVar map | |||||
pure var | pure var | ||||
flushCache :: IORef KatexCache -> IO () | |||||
flushCache :: MVar KatexCache -> IO () | |||||
flushCache var = do | flushCache var = do | ||||
KatexCache x y z <- readIORef var | |||||
Data.Binary.encodeFile ".katex_cache" (HMap.toList x, HMap.toList y, HMap.toList z) | |||||
withMVar var $ \(KatexCache x y z) -> do | |||||
Data.Binary.encodeFile ".katex_cache" (HMap.toList x, HMap.toList y, HMap.toList z) | |||||
invalidateCache :: Identifier -> [String] -> IORef KatexCache -> Compiler KatexCache | |||||
invalidateCache id abbrevs cacheVar = unsafeCompiler $ atomicModifyIORef' cacheVar (\x -> (go x, go x)) where | |||||
invalidateCache :: Identifier -> [String] -> MVar KatexCache -> Compiler KatexCache | |||||
invalidateCache id abbrevs cacheVar = unsafeCompiler $ modifyMVar cacheVar (\x -> pure (go x, go x)) where | |||||
currentValues = map (\x -> (T.pack (takeWhile (/= ':') (tail x)), T.pack (tail (dropWhile (/= ':') (tail x))))) | currentValues = map (\x -> (T.pack (takeWhile (/= ':') (tail x)), T.pack (tail (dropWhile (/= ':') (tail x))))) | ||||
$ filter (not . isPrefixOf "-") abbrevs | $ filter (not . isPrefixOf "-") abbrevs | ||||
ident = show id | ident = show id | ||||
@ -528,4 +530,25 @@ data KatexCache | |||||
instance Hashable MathType where | instance Hashable MathType where | ||||
hashWithSalt s DisplayMath = hashWithSalt s (0 :: Int) | hashWithSalt s DisplayMath = hashWithSalt s (0 :: Int) | ||||
hashWithSalt s InlineMath = hashWithSalt s (1 :: Int) | |||||
hashWithSalt s InlineMath = hashWithSalt s (1 :: Int) | |||||
deriving instance Binary Block | |||||
deriving instance Binary Inline | |||||
deriving instance Binary Format | |||||
deriving instance Binary ListNumberStyle | |||||
deriving instance Binary ListNumberDelim | |||||
deriving instance Binary Caption | |||||
deriving instance Binary Alignment | |||||
deriving instance Binary ColWidth | |||||
deriving instance Binary TableHead | |||||
deriving instance Binary TableBody | |||||
deriving instance Binary TableFoot | |||||
deriving instance Binary QuoteType | |||||
deriving instance Binary Citation | |||||
deriving instance Binary Row | |||||
deriving instance Binary MathType | |||||
deriving instance Binary RowHeadColumns | |||||
deriving instance Binary CitationMode | |||||
deriving instance Binary Cell | |||||
deriving instance Binary RowSpan | |||||
deriving instance Binary ColSpan |
@ -1,6 +1,5 @@ | |||||
resolver: lts-16.20 | |||||
resolver: lts-18.16 | |||||
extra-deps: | extra-deps: | ||||
- hakyll-4.15.1.0@sha256:df90fdb1e7e09e90557ad23a6d402ce042f820f690595e7e73db82220edcadcf,9924 | |||||
- hakyll-sass-0.2.4@sha256:4d2fbd8b63f5ef15483fc6dda09e10eefd63b4f873ad38dec2a3607eb504a8ba,939 | - hakyll-sass-0.2.4@sha256:4d2fbd8b63f5ef15483fc6dda09e10eefd63b4f873ad38dec2a3607eb504a8ba,939 | ||||
- hsass-0.8.0@sha256:05fb3d435dbdf9f66a98db4e1ee57a313170a677e52ab3a5a05ced1fc42b0834,2899 | |||||
- hlibsass-0.1.10.1@sha256:08db56c633e9a83a642d8ea57dffa93112b092d05bf8f3b07491cfee9ee0dfa5,2565 |
@ -1,10 +0,0 @@ | |||||
body{color:black;font-size:16px;margin:0px auto 0px | |||||
auto;width:80%;font-family:Fira Sans, sans-serif}div#header{border-bottom:2px | |||||
solid black;margin-bottom:30px;padding:12px 0px 12px 0px}div#logo | |||||
a{color:black;float:left;font-size:18px;font-weight:bold;font-family:Iosevka, | |||||
Iosevka Term, Fira Mono, Inconsolata, monospace;text-decoration:none}div > | |||||
span.kw{color:#007020}div > span.type{color:#902000}div#header | |||||
#navigation{text-align:right}div#header #navigation | |||||
a{color:black;font-size:18px;font-weight:bold;margin-left:12px;text-decoration:none;text-transform:uppercase}div#footer{border-top:solid | |||||
2px black;color:#555;font-size:12px;margin-top:30px;padding:12px 0px 12px | |||||
0px;text-align:right}div#content{text-align:justify;text-justify:inter-word}h1{font-size:24px}h2{font-size:20px}div.info{color:#555;font-size:14px;font-style:italic} |
@ -1,93 +0,0 @@ | |||||
Copyright (c) 2013-2017, Jany Belluz ([email protected]) | |||||
This Font Software is licensed under the SIL Open Font License, Version 1.1. | |||||
This license is copied below, and is also available with a FAQ at: | |||||
http://scripts.sil.org/OFL | |||||
----------------------------------------------------------- | |||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |||||
----------------------------------------------------------- | |||||
PREAMBLE | |||||
The goals of the Open Font License (OFL) are to stimulate worldwide | |||||
development of collaborative font projects, to support the font creation | |||||
efforts of academic and linguistic communities, and to provide a free and | |||||
open framework in which fonts may be shared and improved in partnership | |||||
with others. | |||||
The OFL allows the licensed fonts to be used, studied, modified and | |||||
redistributed freely as long as they are not sold by themselves. The | |||||
fonts, including any derivative works, can be bundled, embedded, | |||||
redistributed and/or sold with any software provided that any reserved | |||||
names are not used by derivative works. The fonts and derivatives, | |||||
however, cannot be released under any other type of license. The | |||||
requirement for fonts to remain under this license does not apply | |||||
to any document created using the fonts or their derivatives. | |||||
DEFINITIONS | |||||
"Font Software" refers to the set of files released by the Copyright | |||||
Holder(s) under this license and clearly marked as such. This may | |||||
include source files, build scripts and documentation. | |||||
"Reserved Font Name" refers to any names specified as such after the | |||||
copyright statement(s). | |||||
"Original Version" refers to the collection of Font Software components as | |||||
distributed by the Copyright Holder(s). | |||||
"Modified Version" refers to any derivative made by adding to, deleting, | |||||
or substituting -- in part or in whole -- any of the components of the | |||||
Original Version, by changing formats or by porting the Font Software to a | |||||
new environment. | |||||
"Author" refers to any designer, engineer, programmer, technical | |||||
writer or other person who contributed to the Font Software. | |||||
PERMISSION & CONDITIONS | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of the Font Software, to use, study, copy, merge, embed, modify, | |||||
redistribute, and sell modified and unmodified copies of the Font | |||||
Software, subject to the following conditions: | |||||
1) Neither the Font Software nor any of its individual components, | |||||
in Original or Modified Versions, may be sold by itself. | |||||
2) Original or Modified Versions of the Font Software may be bundled, | |||||
redistributed and/or sold with any software, provided that each copy | |||||
contains the above copyright notice and this license. These can be | |||||
included either as stand-alone text files, human-readable headers or | |||||
in the appropriate machine-readable metadata fields within text or | |||||
binary files as long as those fields can be easily viewed by the user. | |||||
3) No Modified Version of the Font Software may use the Reserved Font | |||||
Name(s) unless explicit written permission is granted by the corresponding | |||||
Copyright Holder. This restriction only applies to the primary font name as | |||||
presented to the users. | |||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |||||
Software shall not be used to promote, endorse or advertise any | |||||
Modified Version, except to acknowledge the contribution(s) of the | |||||
Copyright Holder(s) and the Author(s) or with their explicit written | |||||
permission. | |||||
5) The Font Software, modified or unmodified, in part or in whole, | |||||
must be distributed entirely under this license, and must not be | |||||
distributed under any other license. The requirement for fonts to | |||||
remain under this license does not apply to any document created | |||||
using the Font Software. | |||||
TERMINATION | |||||
This license becomes null and void if any of the above conditions are | |||||
not met. | |||||
DISCLAIMER | |||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |||||
OTHER DEALINGS IN THE FONT SOFTWARE. |
@ -0,0 +1,110 @@ | |||||
Copyright 2015-2021, Renzhi Li (aka. Belleve Invis, [email protected]) | |||||
This Font Software is licensed under the SIL Open Font License, Version 1.1. | |||||
This license is copied below, and is also available with a FAQ at: | |||||
http://scripts.sil.org/OFL | |||||
-------------------------- | |||||
SIL Open Font License v1.1 | |||||
==================================================== | |||||
Preamble | |||||
---------- | |||||
The goals of the Open Font License (OFL) are to stimulate worldwide | |||||
development of collaborative font projects, to support the font creation | |||||
efforts of academic and linguistic communities, and to provide a free and | |||||
open framework in which fonts may be shared and improved in partnership | |||||
with others. | |||||
The OFL allows the licensed fonts to be used, studied, modified and | |||||
redistributed freely as long as they are not sold by themselves. The | |||||
fonts, including any derivative works, can be bundled, embedded, | |||||
redistributed and/or sold with any software provided that any reserved | |||||
names are not used by derivative works. The fonts and derivatives, | |||||
however, cannot be released under any other type of license. The | |||||
requirement for fonts to remain under this license does not apply | |||||
to any document created using the fonts or their derivatives. | |||||
Definitions | |||||
------------- | |||||
`"Font Software"` refers to the set of files released by the Copyright | |||||
Holder(s) under this license and clearly marked as such. This may | |||||
include source files, build scripts and documentation. | |||||
`"Reserved Font Name"` refers to any names specified as such after the | |||||
copyright statement(s). | |||||
`"Original Version"` refers to the collection of Font Software components as | |||||
distributed by the Copyright Holder(s). | |||||
`"Modified Version"` refers to any derivative made by adding to, deleting, | |||||
or substituting -- in part or in whole -- any of the components of the | |||||
Original Version, by changing formats or by porting the Font Software to a | |||||
new environment. | |||||
`"Author"` refers to any designer, engineer, programmer, technical | |||||
writer or other person who contributed to the Font Software. | |||||
Permission & Conditions | |||||
------------------------ | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of the Font Software, to use, study, copy, merge, embed, modify, | |||||
redistribute, and sell modified and unmodified copies of the Font | |||||
Software, subject to the following conditions: | |||||
1. Neither the Font Software nor any of its individual components, | |||||
in Original or Modified Versions, may be sold by itself. | |||||
2. Original or Modified Versions of the Font Software may be bundled, | |||||
redistributed and/or sold with any software, provided that each copy | |||||
contains the above copyright notice and this license. These can be | |||||
included either as stand-alone text files, human-readable headers or | |||||
in the appropriate machine-readable metadata fields within text or | |||||
binary files as long as those fields can be easily viewed by the user. | |||||
3. No Modified Version of the Font Software may use the Reserved Font | |||||
Name(s) unless explicit written permission is granted by the corresponding | |||||
Copyright Holder. This restriction only applies to the primary font name as | |||||
presented to the users. | |||||
4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |||||
Software shall not be used to promote, endorse or advertise any | |||||
Modified Version, except to acknowledge the contribution(s) of the | |||||
Copyright Holder(s) and the Author(s) or with their explicit written | |||||
permission. | |||||
5. The Font Software, modified or unmodified, in part or in whole, | |||||
must be distributed entirely under this license, and must not be | |||||
distributed under any other license. The requirement for fonts to | |||||
remain under this license does not apply to any document created | |||||
using the Font Software. | |||||
Termination | |||||
----------- | |||||
This license becomes null and void if any of the above conditions are | |||||
not met. | |||||
DISCLAIMER | |||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |||||
OTHER DEALINGS IN THE FONT SOFTWARE. |
@ -0,0 +1,200 @@ | |||||
Version 2.0, January 2004 | |||||
http://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
APPENDIX: How to apply the Apache License to your work. | |||||
To apply the Apache License to your work, attach the following | |||||
boilerplate notice, with the fields enclosed by brackets "[]" | |||||
replaced with your own identifying information. (Don't include | |||||
the brackets!) The text should be enclosed in the appropriate | |||||
comment syntax for the file format. We also recommend that a | |||||
file or class name and description of purpose be included on the | |||||
same "printed page" as the copyright notice for easier | |||||
identification within third-party archives. | |||||
Copyright [yyyy] [name of copyright owner] | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,10 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-13 -14 26 26"> | |||||
<path style="fill:#5fbcd3" d="M 9.51057,-3.09017 -1.83697e-15,-10 -9.51057,-3.09017 -5.87785,8.09017 5.87785,8.09017 9.51057,-3.09017 z" /> | |||||
<path style="fill:#216778" d="M 10.7181,-2.47446 12.6668,-2.92436 8.52877,9.81122 7.21665,8.30181 z" /> | |||||
<path style="fill:#216778" d="M 0.958713,-10.9581 1.13302,-12.9505 11.9666,-5.0795 10.1256,-4.29804 z" /> | |||||
<path style="fill:#216778" d="M -10.1256,-4.29804 -11.9666,-5.0795 -1.13302,-12.9505 -0.958713,-10.9581 z" /> | |||||
<path style="fill:#216778" d="M -7.21665,8.30181 -8.52877,9.81122 -12.6668,-2.92436 -10.7181,-2.47446 z" /> | |||||
<path style="fill:#216778" d="M 5.66542,9.42884 6.69549,11.1432 -6.69549,11.1432 -5.66542,9.42884 z" /> | |||||
<path style="fill:#216778" d="M 10.7181,-2.47446 12.6668,-2.92436 8.52877,9.81122 7.21665,8.30181 z" /> | |||||
<path style="fill:#ffffff" d="M 0,4.5424805 2.0141602,-0.89941406 H -2.0068359 Z M -0.83496094,6 -5.0097656,-4.9350586 h 1.5600586 l 0.9960937,2.8051758 H 2.4755859 L 3.4716797,-4.9350586 H 5.0097656 L 0.84228516,6 Z" /> | |||||
</svg> |
@ -0,0 +1,31 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" | |||||
y="0px" viewBox="-40 -40 720 720" style="enable-background:new 0 0 640 640;" xml:space="preserve"> | |||||
<g> | |||||
<path id="teabag" style="fill:#609926" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 | |||||
c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 | |||||
c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/> | |||||
<g> | |||||
<g> | |||||
<path style="fill:#fff" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 | |||||
c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 | |||||
c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 | |||||
c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 | |||||
c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 | |||||
C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 | |||||
c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 | |||||
S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 | |||||
c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 | |||||
l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/> | |||||
<path style="fill:#fff" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 | |||||
c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 | |||||
c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 | |||||
c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 | |||||
c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 | |||||
c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 | |||||
c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 | |||||
C343.2,346.5,335,363.3,326.8,380.1z"/> | |||||
</g> | |||||
</g> | |||||
</g> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg viewBox="0 0 32.58 31.77" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"> | |||||
<path d="M7.953 6.754c-.177.008-.264.035-.264.035a5.94 5.94 0 0 0-.16 4.31 6.3 6.3 0 0 0-1.67 4.372c0 6.19 3.8 7.59 7.42 8a3.46 3.46 0 0 0-1 2.18 3.48 3.48 0 0 1-4.74-1.362 3.43 3.43 0 0 0-2.488-1.68s-1.591 0-.121 1A4.36 4.36 0 0 1 6.74 26s.96 3.18 5.51 2.19v2.771c0 .376-.221.813-.826.816l9.69-.048.019-.026c-.563-.006-.793-.346-.793-.793v-4.47a3.85 3.85 0 0 0-1.11-3c3.63-.37 7.44-1.74 7.44-8A6.3 6.3 0 0 0 25 11.07a5.91 5.91 0 0 0-.2-4.28s-1.36-.439-4.47 1.671a15.41 15.41 0 0 0-8.16 0C9.837 6.88 8.484 6.73 7.953 6.754zm3.445 25.02.004.003h.02l-.024-.002z" fill="#fff" /> | |||||
</svg> |
@ -0,0 +1,46 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<svg | |||||
width="600pt" | |||||
height="600pt" | |||||
viewBox="0 0 600 600" | |||||
version="1.2" | |||||
id="svg7" | |||||
sodipodi:docname="kofi.svg" | |||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:svg="http://www.w3.org/2000/svg"> | |||||
<defs | |||||
id="defs11" /> | |||||
<sodipodi:namedview | |||||
id="namedview9" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pagecheckerboard="0" | |||||
inkscape:document-units="pt" | |||||
showgrid="false" | |||||
inkscape:zoom="0.78" | |||||
inkscape:cx="399.35897" | |||||
inkscape:cy="399.35897" | |||||
inkscape:window-width="1920" | |||||
inkscape:window-height="1080" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="1" | |||||
inkscape:current-layer="surface477" /> | |||||
<g | |||||
id="surface477"> | |||||
<path | |||||
style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" | |||||
d="M 548.425781 254.132812 C 542.109375 220.757812 524.140625 199.96875 505.730469 187.113281 C 486.6875 173.820312 463.882812 166.988281 440.660156 166.988281 L 111.074219 166.988281 C 99.605469 166.988281 95.214844 178.183594 95.171875 183.789062 C 95.167969 184.519531 95.195312 187.445312 95.195312 187.445312 C 95.195312 187.445312 94.652344 333.257812 95.675781 411.140625 C 98.785156 457.117188 144.851562 457.101562 144.851562 457.101562 C 144.851562 457.101562 295.257812 456.660156 367.386719 456.214844 C 370.769531 456.191406 374.148438 455.828125 377.429688 455.007812 C 418.5 444.730469 422.75 406.578125 422.308594 385.308594 C 504.859375 389.894531 563.101562 331.644531 548.425781 254.132812 M 455.695312 329.808594 C 438.09375 332.007812 423.789062 330.355469 423.789062 330.355469 L 423.789062 222.5625 L 445.449219 222.5625 C 459.765625 222.5625 473.578125 228.523438 482.917969 239.371094 C 489.496094 247.007812 494.757812 257.757812 494.757812 272.609375 C 494.757812 308.90625 476.050781 323.207031 455.695312 329.808594 " | |||||
id="path2" /> | |||||
<path | |||||
style=" stroke:none;fill-rule:nonzero;fill:rgb(94.343567%,26.055908%,33.364868%);fill-opacity:1;" | |||||
d="M 256.613281 396.632812 C 260.199219 398.4375 262.488281 396.195312 262.488281 396.195312 C 262.488281 396.195312 314.945312 348.316406 338.578125 320.742188 C 359.597656 296.078125 360.96875 254.511719 324.871094 238.976562 C 288.773438 223.445312 259.074219 257.25 259.074219 257.25 C 233.320312 228.925781 194.34375 230.359375 176.3125 249.527344 C 158.285156 268.699219 164.582031 301.601562 178.03125 319.910156 C 190.65625 337.101562 246.148438 386.5625 254.558594 394.925781 C 254.558594 394.925781 255.171875 395.566406 256.613281 396.632812 " | |||||
id="path4" /> | |||||
</g> | |||||
</svg> |
@ -0,0 +1,3 @@ | |||||
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"> | |||||
<path fill="#fff" d="M163.4 305.5c88.7 0 137.2-73.5 137.2-137.2 0-2.1 0-4.2-.1-6.2 9.4-6.8 17.6-15.3 24.1-25-8.6 3.8-17.9 6.4-27.7 7.6 10-6 17.6-15.4 21.2-26.7-9.3 5.5-19.6 9.5-30.6 11.7-8.8-9.4-21.3-15.2-35.2-15.2-26.6 0-48.2 21.6-48.2 48.2 0 3.8.4 7.5 1.3 11-40.1-2-75.6-21.2-99.4-50.4-4.1 7.1-6.5 15.4-6.5 24.2 0 16.7 8.5 31.5 21.5 40.1-7.9-.2-15.3-2.4-21.8-6v.6c0 23.4 16.6 42.8 38.7 47.3-4 1.1-8.3 1.7-12.7 1.7-3.1 0-6.1-.3-9.1-.9 6.1 19.2 23.9 33.1 45 33.5-16.5 12.9-37.3 20.6-59.9 20.6-3.9 0-7.7-.2-11.5-.7 21.1 13.8 46.5 21.8 73.7 21.8" /> | |||||
</svg> |