Compare commits
2 Commits
b5e22cca62
...
a6c32df1ff
Author | SHA1 | Date | |
---|---|---|---|
a6c32df1ff | |||
5814b145fe |
13
.prettierrc.mjs
Normal file
13
.prettierrc.mjs
Normal file
@ -0,0 +1,13 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
export default {
|
||||
plugins: ["prettier-plugin-astro"],
|
||||
useTabs: true,
|
||||
overrides: [
|
||||
{
|
||||
files: "*.astro",
|
||||
options: {
|
||||
parser: "astro",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@ -1,12 +1,12 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import { defineConfig } from "astro/config";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind(), mdx()],
|
||||
markdown: {
|
||||
syntaxHighlight: "prism",
|
||||
},
|
||||
integrations: [tailwind(), mdx()],
|
||||
markdown: {
|
||||
syntaxHighlight: "prism",
|
||||
},
|
||||
});
|
@ -20,4 +20,3 @@ networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
"build": "astro build",
|
||||
"test": "vitest run",
|
||||
"preview": "astro preview",
|
||||
"prettier": "prettier \"./src/**/*.astro\" --write",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -19,5 +20,9 @@
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-astro": "^0.14.1"
|
||||
}
|
||||
}
|
5208
pnpm-lock.yaml
5208
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,110 +1,110 @@
|
||||
/* Iosevka web */
|
||||
@font-face {
|
||||
font-family: "Iosevka Fixed Web";
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
src: url("/Iosevka/Regular.woff2") format("woff2");
|
||||
font-family: "Iosevka Fixed Web";
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
src: url("/Iosevka/Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Iosevka Fixed Web';
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
src: url('/Iosevka/Bold.woff2') format('woff2');
|
||||
font-family: "Iosevka Fixed Web";
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
src: url("/Iosevka/Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--c-thp: #f472b6;
|
||||
--c-thp: #f472b6;
|
||||
|
||||
--font-display: 'Atkinson Hyperlegible', sans-serif;
|
||||
--font-body: 'Atkinson Hyperlegible', sans-serif;
|
||||
--font-code: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace;
|
||||
--font-display: "Atkinson Hyperlegible", sans-serif;
|
||||
--font-body: "Atkinson Hyperlegible", sans-serif;
|
||||
--font-code: "Iosevka Fixed Web", "Iosevka Nerd Font", Iosevka, monospace;
|
||||
}
|
||||
|
||||
:root {
|
||||
--c-bg: #121212;
|
||||
--c-text: rgb(200, 200, 200);
|
||||
--c-text-2: white;
|
||||
--c-primary: #884b6a;
|
||||
--c-purple: #7F669D;
|
||||
--c-purple-light: #BA94D1;
|
||||
--c-box-shadow: #FBFACD;
|
||||
--c-pink: #AE508D;
|
||||
--c-link: #38bdf8;
|
||||
--c-bg: #121212;
|
||||
--c-text: rgb(200, 200, 200);
|
||||
--c-text-2: white;
|
||||
--c-primary: #884b6a;
|
||||
--c-purple: #7f669d;
|
||||
--c-purple-light: #ba94d1;
|
||||
--c-box-shadow: #fbfacd;
|
||||
--c-pink: #ae508d;
|
||||
--c-link: #38bdf8;
|
||||
|
||||
--c-nav-bg: rgb(18, 18, 18, 0.5);
|
||||
--c-secondary: rgba(136, 75, 106, 0.5);
|
||||
--c-nav-bg: rgb(18, 18, 18, 0.5);
|
||||
--c-secondary: rgba(136, 75, 106, 0.5);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--c-bg: rgb(255, 253, 255);
|
||||
--c-text: #121212;
|
||||
--c-text-2: black;
|
||||
--c-purple: #374259;
|
||||
--c-purple-light: #BA94D1;
|
||||
--c-box-shadow: #374259;
|
||||
--c-primary: rgb(255, 180, 180);
|
||||
--c-pink: #374259;
|
||||
--c-link: #0284c7;
|
||||
--c-border-1: #909090;
|
||||
:root {
|
||||
--c-bg: rgb(255, 253, 255);
|
||||
--c-text: #121212;
|
||||
--c-text-2: black;
|
||||
--c-purple: #374259;
|
||||
--c-purple-light: #ba94d1;
|
||||
--c-box-shadow: #374259;
|
||||
--c-primary: rgb(255, 180, 180);
|
||||
--c-pink: #374259;
|
||||
--c-link: #0284c7;
|
||||
--c-border-1: #909090;
|
||||
|
||||
--c-nav-bg: rgb(255, 247, 255, 0.5);
|
||||
--c-secondary: rgba(255, 255, 240, 0.5);
|
||||
}
|
||||
--c-nav-bg: rgb(255, 247, 255, 0.5);
|
||||
--c-secondary: rgba(255, 255, 240, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1600px) {
|
||||
html {
|
||||
font-size: 17px;
|
||||
}
|
||||
html {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
#editor {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
#editor {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
font-family: var(--font-code);
|
||||
font-family: var(--font-code);
|
||||
}
|
||||
|
||||
.button-effect-receiver {
|
||||
transition: transform 150ms;
|
||||
transition: transform 150ms;
|
||||
}
|
||||
|
||||
.button-effect:hover .button-effect-receiver {
|
||||
transform: translateX(-3px) translateY(-3px);
|
||||
transform: translateX(-3px) translateY(-3px);
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 0.75rem 0;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
/* Used by headers generated from markdown */
|
||||
.heading-linked :hover::after {
|
||||
color: var(--c-primary);
|
||||
content: "#";
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
line-height: 1;
|
||||
margin-left: 0.5rem;
|
||||
color: var(--c-primary);
|
||||
content: "#";
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
line-height: 1;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.heading-linked :hover {
|
||||
text-decoration: underline var(--c-primary);
|
||||
text-decoration: underline var(--c-primary);
|
||||
}
|
@ -1,105 +1,103 @@
|
||||
|
||||
.markdown > h1 {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 2rem;
|
||||
font-family: var(--font-display);
|
||||
opacity: 0.9;
|
||||
font-weight: 700;
|
||||
color: var(--c-text-2);
|
||||
scroll-margin-top: 50px;
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 2rem;
|
||||
font-family: var(--font-display);
|
||||
opacity: 0.9;
|
||||
font-weight: 700;
|
||||
color: var(--c-text-2);
|
||||
scroll-margin-top: 50px;
|
||||
}
|
||||
|
||||
.markdown > h2 {
|
||||
/* use these tailwind classes: text-2xl mt-10 mb-4 font-display opacity-90 font-bold text-c-text-2 */
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 2.5rem;
|
||||
font-family: var(--font-display);
|
||||
opacity: 0.9;
|
||||
font-weight: 600;
|
||||
color: var(--c-text-2);
|
||||
scroll-margin-top: 5rem;
|
||||
/* use these tailwind classes: text-2xl mt-10 mb-4 font-display opacity-90 font-bold text-c-text-2 */
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 2.5rem;
|
||||
font-family: var(--font-display);
|
||||
opacity: 0.9;
|
||||
font-weight: 600;
|
||||
color: var(--c-text-2);
|
||||
scroll-margin-top: 5rem;
|
||||
}
|
||||
|
||||
.markdown > h2::before {
|
||||
content: "#";
|
||||
display: block;
|
||||
height: 0;
|
||||
position: relative;
|
||||
right: 1.5rem;
|
||||
opacity: 0.2;
|
||||
content: "#";
|
||||
display: block;
|
||||
height: 0;
|
||||
position: relative;
|
||||
right: 1.5rem;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.markdown > h2:target::before {
|
||||
color: var(--c-thp);
|
||||
opacity: 1;
|
||||
color: var(--c-thp);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.markdown > h3 {
|
||||
font-size: 1.35rem;
|
||||
line-height: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1.75rem;
|
||||
font-family: var(--font-display);
|
||||
opacity: 0.9;
|
||||
font-weight: 400;
|
||||
color: var(--c-text-2);
|
||||
scroll-margin-top: 5rem;
|
||||
font-size: 1.35rem;
|
||||
line-height: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1.75rem;
|
||||
font-family: var(--font-display);
|
||||
opacity: 0.9;
|
||||
font-weight: 400;
|
||||
color: var(--c-text-2);
|
||||
scroll-margin-top: 5rem;
|
||||
}
|
||||
|
||||
.markdown > h3::before {
|
||||
content: "##";
|
||||
display: block;
|
||||
height: 0;
|
||||
position: relative;
|
||||
right: 2.25rem;
|
||||
opacity: 0.2;
|
||||
content: "##";
|
||||
display: block;
|
||||
height: 0;
|
||||
position: relative;
|
||||
right: 2.25rem;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.markdown > h3:target::before {
|
||||
color: var(--c-thp);
|
||||
opacity: 1;
|
||||
color: var(--c-thp);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.markdown ul {
|
||||
list-style-type: disc;
|
||||
list-style-position: inside;
|
||||
list-style-type: disc;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.markdown ul li {
|
||||
padding: 0.5rem 0;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.markdown p > code {
|
||||
border: solid 1px var(--c-border-1);
|
||||
border: solid 1px var(--c-border-1);
|
||||
}
|
||||
|
||||
.markdown > pre {
|
||||
margin: 0.5em 0;
|
||||
padding: 0.75em 0.75em;
|
||||
color: var(--code-theme-color);
|
||||
background: var(--code-theme-bg-color);
|
||||
margin: 0.5em 0;
|
||||
padding: 0.75em 0.75em;
|
||||
color: var(--code-theme-color);
|
||||
background: var(--code-theme-bg-color);
|
||||
}
|
||||
|
||||
.markdown > p > a {
|
||||
color: var(--c-link);
|
||||
text-decoration: underline;
|
||||
color: var(--c-link);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.two-column a {
|
||||
color: #2563eb;
|
||||
text-decoration: underline;
|
||||
display: inline-block;
|
||||
color: #2563eb;
|
||||
text-decoration: underline;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.two-column h3 {
|
||||
margin: 0.75rem 0;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
|
117
public/css/prism.min.css
vendored
117
public/css/prism.min.css
vendored
@ -1,3 +1,118 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism */
|
||||
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #000;
|
||||
background: 0 0;
|
||||
text-shadow: 0 1px #fff;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
code[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
pre[class*="language-"]::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
code[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
pre[class*="language-"]::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.1em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
.token.cdata,
|
||||
.token.comment,
|
||||
.token.doctype,
|
||||
.token.prolog {
|
||||
color: #708090;
|
||||
}
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
.token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.token.boolean,
|
||||
.token.constant,
|
||||
.token.deleted,
|
||||
.token.number,
|
||||
.token.property,
|
||||
.token.symbol,
|
||||
.token.tag {
|
||||
color: #905;
|
||||
}
|
||||
.token.attr-name,
|
||||
.token.builtin,
|
||||
.token.char,
|
||||
.token.inserted,
|
||||
.token.selector,
|
||||
.token.string {
|
||||
color: #690;
|
||||
}
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.entity,
|
||||
.token.operator,
|
||||
.token.url {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, 0.5);
|
||||
}
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
.token.class-name,
|
||||
.token.function {
|
||||
color: #dd4a68;
|
||||
}
|
||||
.token.important,
|
||||
.token.regex,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
.token.bold,
|
||||
.token.important {
|
||||
font-weight: 700;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
@ -1,270 +1,270 @@
|
||||
/* colors from: https://raw.githubusercontent.com/WhiteVermouth/XcodeTheme/master/assets/color-palette.png */
|
||||
:root {
|
||||
--code-theme-color: #dedede;
|
||||
--code-theme-bg-color: #1f1f24;
|
||||
--code-theme-bg-color_selection: #515b70;
|
||||
--code-theme-color_selection: inherit;
|
||||
--code-theme-color: #dedede;
|
||||
--code-theme-bg-color: #1f1f24;
|
||||
--code-theme-bg-color_selection: #515b70;
|
||||
--code-theme-color_selection: inherit;
|
||||
|
||||
--code-theme-comment: #6c7986;
|
||||
--code-theme-comment: #6c7986;
|
||||
|
||||
/* number */
|
||||
--code-theme-c1: #d0bf69;
|
||||
/* keyword */
|
||||
--code-theme-c2: #fc5fa3;
|
||||
/* ? */
|
||||
--code-theme-c3: #39adb5;
|
||||
/* string */
|
||||
--code-theme-c4: rgb(124, 201, 117);
|
||||
/* #fc6a5d; */
|
||||
/* declaration */
|
||||
--code-theme-c5: #5dd8ff;
|
||||
/* proyect function */
|
||||
--code-theme-c6: #67b7a4;
|
||||
/*#e53935;*/
|
||||
/* function */
|
||||
--code-theme-c7: rgb(179, 146, 240);
|
||||
/* number */
|
||||
--code-theme-c1: #d0bf69;
|
||||
/* keyword */
|
||||
--code-theme-c2: #fc5fa3;
|
||||
/* ? */
|
||||
--code-theme-c3: #39adb5;
|
||||
/* string */
|
||||
--code-theme-c4: rgb(124, 201, 117);
|
||||
/* #fc6a5d; */
|
||||
/* declaration */
|
||||
--code-theme-c5: #5dd8ff;
|
||||
/* proyect function */
|
||||
--code-theme-c6: #67b7a4;
|
||||
/*#e53935;*/
|
||||
/* function */
|
||||
--code-theme-c7: rgb(179, 146, 240);
|
||||
|
||||
--code-theme-punctuation: #dedede;
|
||||
--code-theme-punctuation: #dedede;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--code-theme-color: #3a3a3a;
|
||||
--code-theme-bg-color: #ffffff;
|
||||
--code-theme-bg-color_selection: #a4cdff;
|
||||
--code-theme-color_selection: inherit;
|
||||
--code-theme-border-color: #c2c2c2;
|
||||
:root {
|
||||
--code-theme-color: #3a3a3a;
|
||||
--code-theme-bg-color: #ffffff;
|
||||
--code-theme-bg-color_selection: #a4cdff;
|
||||
--code-theme-color_selection: inherit;
|
||||
--code-theme-border-color: #c2c2c2;
|
||||
|
||||
--code-theme-comment: #5d6c79;
|
||||
--code-theme-comment: #5d6c79;
|
||||
|
||||
/* number */
|
||||
--code-theme-c1: #1c00cf;
|
||||
/* keyword */
|
||||
--code-theme-c2: #9b2393;
|
||||
--code-theme-c3: #39adb5;
|
||||
/* string */
|
||||
--code-theme-c4: #c41a16;
|
||||
/* declaration */
|
||||
--code-theme-c5: #0f68a0;
|
||||
--code-theme-c6: #326d74;
|
||||
/*#e53935;*/
|
||||
/* function */
|
||||
--code-theme-c7: rgb(95, 74, 134);
|
||||
/* number */
|
||||
--code-theme-c1: #1c00cf;
|
||||
/* keyword */
|
||||
--code-theme-c2: #9b2393;
|
||||
--code-theme-c3: #39adb5;
|
||||
/* string */
|
||||
--code-theme-c4: #c41a16;
|
||||
/* declaration */
|
||||
--code-theme-c5: #0f68a0;
|
||||
--code-theme-c6: #326d74;
|
||||
/*#e53935;*/
|
||||
/* function */
|
||||
--code-theme-c7: rgb(95, 74, 134);
|
||||
|
||||
--code-theme-punctuation: #202020;
|
||||
}
|
||||
--code-theme-punctuation: #202020;
|
||||
}
|
||||
}
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
color: var(--code-theme-color);
|
||||
background: var(--code-theme-bg-color);
|
||||
line-height: 1.5em;
|
||||
border-radius: 0.3em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
color: var(--code-theme-color);
|
||||
background: var(--code-theme-bg-color);
|
||||
line-height: 1.5em;
|
||||
border-radius: 0.3em;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
code[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection {
|
||||
background: var(--code-theme-bg-color_selection);
|
||||
color: var(--code-theme-color_selection);
|
||||
background: var(--code-theme-bg-color_selection);
|
||||
color: var(--code-theme-color_selection);
|
||||
}
|
||||
|
||||
code[class*="language-"]::selection,
|
||||
pre[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection,
|
||||
pre[class*="language-"] ::selection {
|
||||
background: var(--code-theme-bg-color_selection);
|
||||
color: var(--code-theme-color_selection);
|
||||
background: var(--code-theme-bg-color_selection);
|
||||
color: var(--code-theme-color_selection);
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"] {
|
||||
white-space: normal;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.1em;
|
||||
:not(pre) > code[class*="language-"] {
|
||||
white-space: normal;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
:not(pre)>code {
|
||||
background-color: var(--code-theme-bg-color);
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 5px;
|
||||
:not(pre) > code {
|
||||
background-color: var(--code-theme-bg-color);
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
pre[class*="language-"] {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
margin: 0.5em 0;
|
||||
padding: 0.75em 0.75em;
|
||||
border: 1px solid var(--code-theme-border-color);
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
margin: 0.5em 0;
|
||||
padding: 0.75em 0.75em;
|
||||
border: 1px solid var(--code-theme-border-color);
|
||||
}
|
||||
|
||||
.language-css>code,
|
||||
.language-sass>code,
|
||||
.language-scss>code {
|
||||
color: var(--code-theme-c1);
|
||||
.language-css > code,
|
||||
.language-sass > code,
|
||||
.language-scss > code {
|
||||
color: var(--code-theme-c1);
|
||||
}
|
||||
|
||||
[class*="language-"] .namespace {
|
||||
opacity: 0.7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.atrule {
|
||||
color: var(--code-theme-c2);
|
||||
color: var(--code-theme-c2);
|
||||
}
|
||||
|
||||
.token.attr-name {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.attr-value {
|
||||
color: var(--code-theme-c4);
|
||||
color: var(--code-theme-c4);
|
||||
}
|
||||
|
||||
.token.attribute {
|
||||
color: var(--code-theme-c4);
|
||||
color: var(--code-theme-c4);
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: var(--code-theme-c2);
|
||||
color: var(--code-theme-c2);
|
||||
}
|
||||
|
||||
.token.builtin {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.cdata {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.char {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.class {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.class-name {
|
||||
color: var(--code-theme-c5);
|
||||
color: var(--code-theme-c5);
|
||||
}
|
||||
|
||||
.token.comment {
|
||||
color: var(--code-theme-comment);
|
||||
color: var(--code-theme-comment);
|
||||
}
|
||||
|
||||
.token.constant {
|
||||
color: var(--code-theme-c2);
|
||||
color: var(--code-theme-c2);
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: var(--code-theme-c6);
|
||||
color: var(--code-theme-c6);
|
||||
}
|
||||
|
||||
.token.doctype {
|
||||
color: var(--code-theme-comment);
|
||||
color: var(--code-theme-comment);
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: var(--code-theme-c6);
|
||||
color: var(--code-theme-c6);
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: var(--code-theme-c7);
|
||||
color: var(--code-theme-c7);
|
||||
}
|
||||
|
||||
.token.hexcode {
|
||||
color: var(--code-theme-c1);
|
||||
color: var(--code-theme-c1);
|
||||
}
|
||||
|
||||
.token.id {
|
||||
color: var(--code-theme-c2);
|
||||
font-weight: bold;
|
||||
color: var(--code-theme-c2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
color: var(--code-theme-c2);
|
||||
font-weight: bold;
|
||||
color: var(--code-theme-c2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: var(--code-theme-c2);
|
||||
color: var(--code-theme-c2);
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: var(--code-theme-c1);
|
||||
color: var(--code-theme-c1);
|
||||
}
|
||||
|
||||
.token.operator {
|
||||
color: var(--code-theme-punctuation);
|
||||
color: var(--code-theme-punctuation);
|
||||
}
|
||||
|
||||
.token.prolog {
|
||||
color: var(--code-theme-comment);
|
||||
color: var(--code-theme-comment);
|
||||
}
|
||||
|
||||
.token.property {
|
||||
color: var(--code-theme-c3);
|
||||
color: var(--code-theme-c3);
|
||||
}
|
||||
|
||||
.token.pseudo-class {
|
||||
color: var(--code-theme-c4);
|
||||
color: var(--code-theme-c4);
|
||||
}
|
||||
|
||||
.token.pseudo-element {
|
||||
color: var(--code-theme-c4);
|
||||
color: var(--code-theme-c4);
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: var(--code-theme-punctuation);
|
||||
color: var(--code-theme-punctuation);
|
||||
}
|
||||
|
||||
.token.regex {
|
||||
color: var(--code-theme-c5);
|
||||
color: var(--code-theme-c5);
|
||||
}
|
||||
|
||||
.token.selector {
|
||||
color: var(--code-theme-c6);
|
||||
color: var(--code-theme-c6);
|
||||
}
|
||||
|
||||
.token.string {
|
||||
color: var(--code-theme-c4);
|
||||
color: var(--code-theme-c4);
|
||||
}
|
||||
|
||||
.token.symbol {
|
||||
color: var(--code-theme-c2);
|
||||
color: var(--code-theme-c2);
|
||||
}
|
||||
|
||||
.token.tag {
|
||||
color: var(--code-theme-c6);
|
||||
color: var(--code-theme-c6);
|
||||
}
|
||||
|
||||
.token.unit {
|
||||
color: var(--code-theme-c1);
|
||||
color: var(--code-theme-c1);
|
||||
}
|
||||
|
||||
.token.url {
|
||||
color: var(--code-theme-c6);
|
||||
color: var(--code-theme-c6);
|
||||
}
|
||||
|
||||
.token.variable {
|
||||
color: var(--code-theme-c6);
|
||||
color: var(--code-theme-c6);
|
||||
}
|
2855
public/js/alpine-3.14.0.min.js
vendored
2855
public/js/alpine-3.14.0.min.js
vendored
File diff suppressed because one or more lines are too long
@ -5,54 +5,54 @@ import { splitAndLast } from "../utils";
|
||||
const hierarchy: Hierarchy = Astro.props.hierarchy;
|
||||
|
||||
function postComparison(a: Post, b: Post): number {
|
||||
const s1 = splitAndLast(a.url);
|
||||
const s2 = splitAndLast(b.url);
|
||||
const s1 = splitAndLast(a.url);
|
||||
const s2 = splitAndLast(b.url);
|
||||
|
||||
return s1 > s2 ? 0 : 1;
|
||||
return s1 > s2 ? 0 : 1;
|
||||
}
|
||||
|
||||
function appendSlash(s: string): string {
|
||||
if (s.endsWith("/")) {
|
||||
return s;
|
||||
} else {
|
||||
return s + "/";
|
||||
}
|
||||
if (s.endsWith("/")) {
|
||||
return s;
|
||||
} else {
|
||||
return s + "/";
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
Object.entries(hierarchy.children).map(
|
||||
([folderName, [folderPost, children]]) => (
|
||||
<>
|
||||
{folderPost !== null ? (
|
||||
<a
|
||||
class="inline-block rounded-2xl w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-2"
|
||||
href={appendSlash(folderPost.url)}
|
||||
>
|
||||
{splitAndLast(folderPost.url)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="mt-6 px-2 py-1 uppercase font-display text-c-text-2 font-medium">
|
||||
{folderName}
|
||||
</div>
|
||||
)}
|
||||
Object.entries(hierarchy.children).map(
|
||||
([folderName, [folderPost, children]]) => (
|
||||
<>
|
||||
{folderPost !== null ? (
|
||||
<a
|
||||
class="inline-block rounded-2xl w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-2"
|
||||
href={appendSlash(folderPost.url)}
|
||||
>
|
||||
{splitAndLast(folderPost.url)}
|
||||
</a>
|
||||
) : (
|
||||
<div class="mt-6 px-2 py-1 uppercase font-display text-c-text-2 font-medium">
|
||||
{folderName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="pl-2 my-1">
|
||||
<ul class="border-l border-c-border-1">
|
||||
<Astro.self hierarchy={children} />
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
)
|
||||
<div class="pl-2 my-1">
|
||||
<ul class="border-l border-c-border-1">
|
||||
<Astro.self hierarchy={children} />
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
)
|
||||
}
|
||||
{
|
||||
hierarchy.posts.sort(postComparison).map((p) => (
|
||||
<a
|
||||
class="inline-block rounded-2xl w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-2"
|
||||
href={appendSlash(p.url)}
|
||||
>
|
||||
{splitAndLast(p.url)}
|
||||
</a>
|
||||
))
|
||||
hierarchy.posts.sort(postComparison).map((p) => (
|
||||
<a
|
||||
class="inline-block rounded-2xl w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-2"
|
||||
href={appendSlash(p.url)}
|
||||
>
|
||||
{splitAndLast(p.url)}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
@ -5,13 +5,16 @@ import CodeError from "./docs/CodeError.astro";
|
||||
|
||||
const { thpcode, no_warnings, level } = Astro.props;
|
||||
|
||||
const [native_html, error_message] = await native_highlighter(thpcode, level as HighlightLevel);
|
||||
const [native_html, error_message] = await native_highlighter(
|
||||
thpcode,
|
||||
level as HighlightLevel,
|
||||
);
|
||||
---
|
||||
|
||||
<pre
|
||||
class="language-thp"><code class="language-thp" set:html={native_html} /><span class="absolute top-1 right-1 text-right inline-block text-sm select-none opacity-75">thp</span></pre>
|
||||
class="language-thp"><code class="language-thp" set:html={native_html} /><span class="absolute top-1 right-1 text-right inline-block text-sm select-none opacity-75">thp</span></pre>
|
||||
{
|
||||
no_warnings !== true && error_message !== null && (
|
||||
<CodeError error_message={error_message} />
|
||||
)
|
||||
no_warnings !== true && error_message !== null && (
|
||||
<CodeError error_message={error_message} />
|
||||
)
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
---
|
||||
import Code from "./Code.astro"
|
||||
import Code from "./Code.astro";
|
||||
|
||||
const { thpcode, no_warnings, level } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="flex h-full md:py-8 items-center">
|
||||
<div class="p-4 w-full">
|
||||
<Code thpcode={thpcode} no_warnings={no_warnings} level={level} />
|
||||
</div>
|
||||
<div class="p-4 w-full">
|
||||
<Code thpcode={thpcode} no_warnings={no_warnings} level={level} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,35 +5,35 @@ import CodeEditor from "./CodeEditor.astro";
|
||||
const { title, thpcode } = Astro.props;
|
||||
|
||||
if (!thpcode) {
|
||||
throw new Error("thpcode is required");
|
||||
throw new Error("thpcode is required");
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
throw new Error("title is required");
|
||||
throw new Error("title is required");
|
||||
}
|
||||
---
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="md:bg-c-thp md:text-c-bg text-c-thp border-t-4 border-b-4 border-c-thp"
|
||||
>
|
||||
<h1
|
||||
class="container mx-auto font-medium md:py-8 py-4 px-4 md:text-3xl text-2xl font-display"
|
||||
>
|
||||
✅ <span class="font-black">{title}</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="md:bg-c-thp md:text-c-bg text-c-thp border-t-4 border-b-4 border-c-thp"
|
||||
>
|
||||
<h1
|
||||
class="container mx-auto font-medium md:py-8 py-4 px-4 md:text-3xl text-2xl font-display"
|
||||
>
|
||||
✅ <span class="font-black">{title}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto lg:grid lg:grid-cols-2 gap-4">
|
||||
<div class="md:px-8 md:py-12 px-4 pt-6 flex h-full items-center">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<CodeEditor
|
||||
thpcode={thpcode}
|
||||
no_warnings={true}
|
||||
level={HighlightLevel.Lexic}
|
||||
/>
|
||||
</div>
|
||||
<div class="container mx-auto lg:grid lg:grid-cols-2 gap-4">
|
||||
<div class="md:px-8 md:py-12 px-4 pt-6 flex h-full items-center">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<CodeEditor
|
||||
thpcode={thpcode}
|
||||
no_warnings={true}
|
||||
level={HighlightLevel.Lexic}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,63 +1,58 @@
|
||||
---
|
||||
const { showSidebarButton = true } = Astro.props;
|
||||
const { showSidebarButton = true, version = "latest" } = Astro.props;
|
||||
---
|
||||
|
||||
<nav
|
||||
class="fixed w-full top-0 h-12 border-b border-[rgba(150,150,150,0.25)] bg-c-nav-bg backdrop-blur-md z-20"
|
||||
class="fixed w-full top-0 h-12 border-b border-[rgba(150,150,150,0.25)] bg-c-nav-bg backdrop-blur-md z-20"
|
||||
>
|
||||
<div class="container mx-auto h-full w-full flex items-center">
|
||||
{
|
||||
showSidebarButton && (
|
||||
<button id="sidebar-toggle" class="w-10 h-full lg:hidden">
|
||||
<i class="ph-bold ph-list text-xl inline-block" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
<div class="container mx-auto h-full w-full flex items-center">
|
||||
{
|
||||
showSidebarButton && (
|
||||
<button id="sidebar-toggle" class="w-10 h-full lg:hidden">
|
||||
<i class="ph-bold ph-list text-xl inline-block" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
<a
|
||||
href="/"
|
||||
class="px-4 flex gap-2 items-center"
|
||||
>
|
||||
<img
|
||||
class="inline-block h-10"
|
||||
src="/img/thp_logo_exp.svg"
|
||||
alt="thp"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="/en/latest/learn/"
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="/api/std/"
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Standard Library
|
||||
</a>
|
||||
<a
|
||||
href="/spec/"
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Language spec
|
||||
</a>
|
||||
</div>
|
||||
<a href="/" class="px-4 flex gap-2 items-center">
|
||||
<img class="inline-block h-10" src="/img/thp_logo_exp.svg" alt="thp" />
|
||||
</a>
|
||||
<a
|
||||
href={`/en/${version}/learn/`}
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="/api/std/"
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Standard Library
|
||||
</a>
|
||||
<a
|
||||
href="/spec/"
|
||||
class="hidden sm:inline-block px-4 font-display font-bold-text-xl hover:underline"
|
||||
>
|
||||
Language spec
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
const sidebarToggle = document.getElementById("sidebar-toggle");
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
const sidebarToggle = document.getElementById("sidebar-toggle");
|
||||
|
||||
if (!sidebar || !sidebarToggle) {
|
||||
console.log("Sidebar or Sidebar toggle not found. Not enabling sidebar on mobile");
|
||||
return;
|
||||
}
|
||||
if (!sidebar || !sidebarToggle) {
|
||||
console.log(
|
||||
"Sidebar or Sidebar toggle not found. Not enabling sidebar on mobile",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sidebarToggle.addEventListener("click", () => {
|
||||
sidebar.classList.toggle("-translate-x-64");
|
||||
console.log(sidebar.classList.contains("-translate-x-64"));
|
||||
});
|
||||
});
|
||||
sidebarToggle.addEventListener("click", () => {
|
||||
sidebar.classList.toggle("-translate-x-64");
|
||||
console.log(sidebar.classList.contains("-translate-x-64"));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -7,38 +7,33 @@ const post_url = entry.url + (entry.url.endsWith("/") ? "" : "/");
|
||||
// this may deal with folders.
|
||||
// if so, it will turn any `-` into whitespace,
|
||||
// and remove any leading number
|
||||
const entry_title = entry.title
|
||||
.replaceAll("-", " ")
|
||||
.replaceAll(/\d+_/g, "");
|
||||
|
||||
const entry_title = entry.title.replaceAll("-", " ").replaceAll(/\d+_/g, "");
|
||||
---
|
||||
|
||||
{
|
||||
!entry.children && (
|
||||
<li>
|
||||
<a
|
||||
class="inline-block rounded-lg w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-1"
|
||||
href={post_url}
|
||||
>
|
||||
{entry.title}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
!entry.children && (
|
||||
<li>
|
||||
<a
|
||||
class="inline-block rounded-lg w-full hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-3 py-1"
|
||||
href={post_url}
|
||||
>
|
||||
{entry.title}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
entry.children && (
|
||||
<>
|
||||
<div class="mt-6 px-3 py-1 uppercase font-display text-c-text-2 font-medium">
|
||||
{entry_title}
|
||||
</div>
|
||||
entry.children && (
|
||||
<>
|
||||
<div class="mt-6 px-3 py-1 uppercase font-display text-c-text-2 font-medium">
|
||||
{entry_title}
|
||||
</div>
|
||||
|
||||
<ul class="my-1">
|
||||
{entry.children!.map((nextEntry) => (
|
||||
<Astro.self
|
||||
entry={nextEntry}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
<ul class="my-1">
|
||||
{entry.children!.map((nextEntry) => (
|
||||
<Astro.self entry={nextEntry} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -7,37 +7,36 @@ const { headings } = Astro.props;
|
||||
const toc = buildHierarchy(headings);
|
||||
|
||||
function buildHierarchy(headings: any) {
|
||||
const toc: any[] = [];
|
||||
const parentHeadings = new Map();
|
||||
const toc: any[] = [];
|
||||
const parentHeadings = new Map();
|
||||
|
||||
if (!headings) return toc;
|
||||
if (!headings) return toc;
|
||||
|
||||
headings.forEach((h: any) => {
|
||||
const heading = { ...h, subheadings: [] };
|
||||
parentHeadings.set(heading.depth, heading);
|
||||
// Change 2 to 1 if your markdown includes your <h1>
|
||||
if (heading.depth === 2) {
|
||||
toc.push(heading);
|
||||
} else if (heading.depth === 1) {
|
||||
/** empty */
|
||||
}
|
||||
else {
|
||||
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
|
||||
}
|
||||
});
|
||||
headings.forEach((h: any) => {
|
||||
const heading = { ...h, subheadings: [] };
|
||||
parentHeadings.set(heading.depth, heading);
|
||||
// Change 2 to 1 if your markdown includes your <h1>
|
||||
if (heading.depth === 2) {
|
||||
toc.push(heading);
|
||||
} else if (heading.depth === 1) {
|
||||
/** empty */
|
||||
} else {
|
||||
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
|
||||
}
|
||||
});
|
||||
|
||||
return toc;
|
||||
return toc;
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
toc && toc.length > 0 && (
|
||||
<nav class="article-toc opacity-80">
|
||||
<ul>
|
||||
{toc.map((heading) => (
|
||||
<TOCHeading heading={heading} />
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
toc && toc.length > 0 && (
|
||||
<nav class="article-toc opacity-80">
|
||||
<ul>
|
||||
{toc.map((heading) => (
|
||||
<TOCHeading heading={heading} />
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
@ -5,20 +5,23 @@ const { heading, parentMono } = Astro.props;
|
||||
// If a heading starts with `API: `
|
||||
// then its children should be rendered with a mono font
|
||||
const isMono = heading.text.startsWith("API: ");
|
||||
const monoClass = parentMono? " font-mono" : "";
|
||||
const monoClass = parentMono ? " font-mono" : "";
|
||||
---
|
||||
|
||||
<li>
|
||||
<a class={"inline-block py-1 hover:underline" + monoClass} href={"#" + heading.slug}>
|
||||
{heading.text}
|
||||
</a>
|
||||
{
|
||||
heading.subheadings.length > 0 && (
|
||||
<ul class="px-2">
|
||||
{heading.subheadings.map((subheading: any) => (
|
||||
<Astro.self heading={subheading} parentMono={isMono} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
<a
|
||||
class={"inline-block py-1 hover:underline" + monoClass}
|
||||
href={"#" + heading.slug}
|
||||
>
|
||||
{heading.text}
|
||||
</a>
|
||||
{
|
||||
heading.subheadings.length > 0 && (
|
||||
<ul class="px-2">
|
||||
{heading.subheadings.map((subheading: any) => (
|
||||
<Astro.self heading={subheading} parentMono={isMono} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</li>
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
const {cols} = Astro.props;
|
||||
const { cols } = Astro.props;
|
||||
const grid_cols = cols ?? "grid-cols-[10rem_auto]";
|
||||
---
|
||||
|
||||
<div class={`two-column grid ${grid_cols}`}>
|
||||
<slot />
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -3,6 +3,6 @@ const { error_message } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="px-4 py-2 rounded bg-red-200 dark:bg-red-950">
|
||||
<span class="inline-block font-bold">Compilation error:</span>
|
||||
<span class="whitespace-pre-wrap">{error_message}</span>
|
||||
<span class="inline-block font-bold">Compilation error:</span>
|
||||
<span class="whitespace-pre-wrap">{error_message}</span>
|
||||
</div>
|
||||
|
@ -6,4 +6,8 @@ const { thpcode, href } = Astro.props;
|
||||
const [native_html] = await native_highlighter(thpcode);
|
||||
---
|
||||
|
||||
<a href={href} class="inline-block w-full py-2 font-mono whitespace-pre" set:html={native_html} />
|
||||
<a
|
||||
href={href}
|
||||
class="inline-block w-full py-2 font-mono whitespace-pre"
|
||||
set:html={native_html}
|
||||
/>
|
||||
|
@ -1,4 +1,6 @@
|
||||
<div class="my-6 px-4 py-2 rounded bg-[#f8e287] text-[#221b00] dark:bg-[#dbc66e] dark:text-[#3a3000]">
|
||||
<div class="font-bold pt-2">Warning</div>
|
||||
<slot />
|
||||
<div
|
||||
class="my-6 px-4 py-2 rounded bg-[#f8e287] text-[#221b00] dark:bg-[#dbc66e] dark:text-[#3a3000]"
|
||||
>
|
||||
<div class="font-bold pt-2">Warning</div>
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="my-6 px-4 py-2 rounded bg-red-200 dark:bg-red-950">
|
||||
<div class="font-bold pt-2">Warning</div>
|
||||
<slot />
|
||||
<div class="font-bold pt-2">Warning</div>
|
||||
<slot />
|
||||
</div>
|
@ -1,133 +1,97 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { leftTrimDedent } from "./utils"
|
||||
import { expect, test } from "vitest";
|
||||
import { leftTrimDedent } from "./utils";
|
||||
|
||||
test("should trim empty string", () => {
|
||||
const input = ``;
|
||||
const input = ``;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([""]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual([""]);
|
||||
});
|
||||
|
||||
test("should work on a single line", () => {
|
||||
const input = `hello`
|
||||
const input = `hello`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello"
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello"]);
|
||||
});
|
||||
|
||||
test("should trim a single line", () => {
|
||||
const input = ` hello`
|
||||
const input = ` hello`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello"
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello"]);
|
||||
});
|
||||
|
||||
test("should trim multiple lines", () => {
|
||||
const input = ` hello\n world`
|
||||
const input = ` hello\n world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world"
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
test("should trim multiple lines without indentation", () => {
|
||||
const input = `hello\nworld`
|
||||
const input = `hello\nworld`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world"
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
test("should consume only whitespace", () => {
|
||||
const input = ` hello\nworld`;
|
||||
const input = ` hello\nworld`;
|
||||
|
||||
try {
|
||||
const res = leftTrimDedent(input);
|
||||
expect(res).not.toEqual([
|
||||
"hello",
|
||||
"rld",
|
||||
]);
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect(e).toHaveProperty("message", "Invalid indentation while trimming: Expected 2 spaces, got 0");
|
||||
}
|
||||
})
|
||||
try {
|
||||
const res = leftTrimDedent(input);
|
||||
expect(res).not.toEqual(["hello", "rld"]);
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect(e).toHaveProperty(
|
||||
"message",
|
||||
"Invalid indentation while trimming: Expected 2 spaces, got 0",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("should preserve deeper indentation", () => {
|
||||
const input = ` hello\n world`
|
||||
const input = ` hello\n world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
" world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", " world"]);
|
||||
});
|
||||
|
||||
test("should ignore empty lines", () => {
|
||||
const input = ` hello\n\n world`
|
||||
const input = ` hello\n\n world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"",
|
||||
"world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "", "world"]);
|
||||
});
|
||||
|
||||
test("should ignore lines with only whitespace", () => {
|
||||
const input = ` hello\n \n \n world`
|
||||
const input = ` hello\n \n \n world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"",
|
||||
" ",
|
||||
"world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "", " ", "world"]);
|
||||
});
|
||||
|
||||
test("should trim multiple without indentation", () => {
|
||||
const input = `hello\nworld\n!`
|
||||
const input = `hello\nworld\n!`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world",
|
||||
"!",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world", "!"]);
|
||||
});
|
||||
|
||||
test("should ignore empty first line", () => {
|
||||
const input = `
|
||||
const input = `
|
||||
hello
|
||||
world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
test("should ignore empty first line 2", () => {
|
||||
const input = `
|
||||
const input = `
|
||||
hello
|
||||
|
||||
world`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"",
|
||||
"world",
|
||||
]);
|
||||
})
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "", "world"]);
|
||||
});
|
||||
|
||||
test("should ignore empty last line", () => {
|
||||
const input = `
|
||||
const input = `
|
||||
hello
|
||||
world
|
||||
`;
|
||||
|
||||
expect(leftTrimDedent(input)).toEqual([
|
||||
"hello",
|
||||
"world",
|
||||
]);
|
||||
expect(leftTrimDedent(input)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
@ -1,64 +1,65 @@
|
||||
|
||||
/**
|
||||
* Performs the following:
|
||||
* - Removes the first & last line, if they are empty
|
||||
* - Picks the indentation level from the first non-white line
|
||||
* - Dedents the following lines
|
||||
*/
|
||||
*/
|
||||
export function leftTrimDedent(input: string): Array<string> {
|
||||
let lines = input.split("\n");
|
||||
let output: Array<string> = [];
|
||||
let lines = input.split("\n");
|
||||
let output: Array<string> = [];
|
||||
|
||||
// Ignore first line
|
||||
if (lines[0] === "" && lines.length > 1) {
|
||||
lines = lines.slice(1);
|
||||
// Ignore first line
|
||||
if (lines[0] === "" && lines.length > 1) {
|
||||
lines = lines.slice(1);
|
||||
}
|
||||
|
||||
// Get indentation level of the first line
|
||||
let indentationLevel = 0;
|
||||
for (const char of lines[0]!) {
|
||||
if (char === " " || char === "\n") {
|
||||
indentationLevel += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get indentation level of the first line
|
||||
let indentationLevel = 0;
|
||||
for (const char of lines[0]!) {
|
||||
if (char === " " || char === "\n") {
|
||||
indentationLevel += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
for (const line of lines) {
|
||||
// Ignore empty lines
|
||||
if (line === "") {
|
||||
output.push("");
|
||||
continue;
|
||||
}
|
||||
output.push(trimWhitespace(line, indentationLevel));
|
||||
}
|
||||
|
||||
for (const line of lines) {
|
||||
// Ignore empty lines
|
||||
if (line === "") {
|
||||
output.push("");
|
||||
continue;
|
||||
}
|
||||
output.push(trimWhitespace(line, indentationLevel));
|
||||
}
|
||||
if (output.length > 1 && output[output.length - 1] === "") {
|
||||
output = output.slice(0, -1);
|
||||
}
|
||||
|
||||
if (output.length > 1 && output[output.length - 1] === "") {
|
||||
output = output.slice(0, -1);
|
||||
}
|
||||
|
||||
return output;
|
||||
return output;
|
||||
}
|
||||
|
||||
function trimWhitespace(input: string, count: number): string {
|
||||
let indentCount = 0;
|
||||
let indentCount = 0;
|
||||
|
||||
for (const char of input) {
|
||||
if (char === " ") {
|
||||
indentCount += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indentCount >= count || indentCount == input.length) {
|
||||
return input.slice(count);
|
||||
for (const char of input) {
|
||||
if (char === " ") {
|
||||
indentCount += 1;
|
||||
} else {
|
||||
throw new Error(`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (indentCount >= count || indentCount == input.length) {
|
||||
return input.slice(count);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid indentation while trimming: Expected ${count} spaces, got ${indentCount}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function splitAndLast(s: string): string {
|
||||
const segments = s.split("/");
|
||||
return segments[segments.length - 1]!;
|
||||
const segments = s.split("/");
|
||||
return segments[segments.length - 1]!;
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ import { splitAndLast } from "../components/utils";
|
||||
const { headings } = Astro.props;
|
||||
|
||||
export type Post = {
|
||||
frontmatter: any;
|
||||
getHeadings: any;
|
||||
url: string;
|
||||
file: any;
|
||||
Content: any;
|
||||
default: any;
|
||||
frontmatter: any;
|
||||
getHeadings: any;
|
||||
url: string;
|
||||
file: any;
|
||||
Content: any;
|
||||
default: any;
|
||||
};
|
||||
type Posts = Array<Readonly<Post>>;
|
||||
|
||||
@ -21,128 +21,128 @@ const basePath = "/api/std";
|
||||
const posts: Posts = await Astro.glob("../pages/api/std/**/*.{md,mdx}");
|
||||
|
||||
export type Hierarchy = {
|
||||
posts: Array<Post>;
|
||||
children: Record<string, [Post | null, Hierarchy]>;
|
||||
posts: Array<Post>;
|
||||
children: Record<string, [Post | null, Hierarchy]>;
|
||||
};
|
||||
|
||||
function createHierarchy(posts: Posts): Hierarchy {
|
||||
const hierarchy: Hierarchy = {
|
||||
posts: [],
|
||||
children: {},
|
||||
};
|
||||
const hierarchy: Hierarchy = {
|
||||
posts: [],
|
||||
children: {},
|
||||
};
|
||||
|
||||
for (const post of posts) {
|
||||
const postUrl: string = post.url;
|
||||
const urlAfterBase = postUrl.split(basePath)[1] ?? "";
|
||||
for (const post of posts) {
|
||||
const postUrl: string = post.url;
|
||||
const urlAfterBase = postUrl.split(basePath)[1] ?? "";
|
||||
|
||||
// handle / (index)
|
||||
if (urlAfterBase === "") {
|
||||
hierarchy.posts.push(post);
|
||||
continue;
|
||||
}
|
||||
// handle / (index)
|
||||
if (urlAfterBase === "") {
|
||||
hierarchy.posts.push(post);
|
||||
continue;
|
||||
}
|
||||
|
||||
const urlSegments = urlAfterBase.split("/").slice(1);
|
||||
const urlSegments = urlAfterBase.split("/").slice(1);
|
||||
|
||||
// top level urls
|
||||
if (urlSegments.length === 1) {
|
||||
hierarchy.posts.push(post);
|
||||
continue;
|
||||
}
|
||||
// top level urls
|
||||
if (urlSegments.length === 1) {
|
||||
hierarchy.posts.push(post);
|
||||
continue;
|
||||
}
|
||||
|
||||
// folders
|
||||
const folders = urlSegments.slice(0, -1);
|
||||
// folders
|
||||
const folders = urlSegments.slice(0, -1);
|
||||
|
||||
// traverse the hierarchy until the neccesary hierarchy is found
|
||||
let currentHierarchy = hierarchy;
|
||||
for (const folderName of folders) {
|
||||
// check if folder exists, create otherwise
|
||||
if (!hierarchy.children[folderName]) {
|
||||
// create if doesnt exist
|
||||
hierarchy.children[folderName] = [
|
||||
null,
|
||||
{
|
||||
posts: [],
|
||||
children: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
// traverse the hierarchy until the neccesary hierarchy is found
|
||||
let currentHierarchy = hierarchy;
|
||||
for (const folderName of folders) {
|
||||
// check if folder exists, create otherwise
|
||||
if (!hierarchy.children[folderName]) {
|
||||
// create if doesnt exist
|
||||
hierarchy.children[folderName] = [
|
||||
null,
|
||||
{
|
||||
posts: [],
|
||||
children: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const [_, childrenHierarchy] = hierarchy.children[folderName]!;
|
||||
currentHierarchy = childrenHierarchy;
|
||||
}
|
||||
const [_, childrenHierarchy] = hierarchy.children[folderName]!;
|
||||
currentHierarchy = childrenHierarchy;
|
||||
}
|
||||
|
||||
// add the page
|
||||
currentHierarchy.posts.push(post);
|
||||
}
|
||||
// add the page
|
||||
currentHierarchy.posts.push(post);
|
||||
}
|
||||
|
||||
return hierarchy;
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
function normalizeHierarchy(h: Hierarchy): Hierarchy {
|
||||
let posts = h.posts;
|
||||
for (const folderName in h.children) {
|
||||
// search if there is a post with the same name
|
||||
// as the folder
|
||||
const postIdx = h.posts.findIndex(
|
||||
(post) => splitAndLast(post.url) === folderName,
|
||||
);
|
||||
let posts = h.posts;
|
||||
for (const folderName in h.children) {
|
||||
// search if there is a post with the same name
|
||||
// as the folder
|
||||
const postIdx = h.posts.findIndex(
|
||||
(post) => splitAndLast(post.url) === folderName,
|
||||
);
|
||||
|
||||
if (postIdx !== -1) {
|
||||
const post = h.posts[postIdx]!;
|
||||
h.children[folderName]![0] = post;
|
||||
posts.splice(postIdx, 1);
|
||||
}
|
||||
}
|
||||
if (postIdx !== -1) {
|
||||
const post = h.posts[postIdx]!;
|
||||
h.children[folderName]![0] = post;
|
||||
posts.splice(postIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// do the same to all children
|
||||
// TODO
|
||||
// do the same to all children
|
||||
// TODO
|
||||
|
||||
return {
|
||||
children: h.children,
|
||||
posts,
|
||||
};
|
||||
return {
|
||||
children: h.children,
|
||||
posts,
|
||||
};
|
||||
}
|
||||
|
||||
const hierarchy = normalizeHierarchy(createHierarchy(posts));
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<Navbar />
|
||||
<Navbar />
|
||||
|
||||
<div class="lg:grid lg:grid-cols-[14rem_auto_12rem] lg:container mx-auto">
|
||||
<div
|
||||
id="sidebar"
|
||||
class="pt-12 h-screen lg:sticky top-0 fixed z-10 bg-c-bg w-60 lg:w-auto border-r-2 lg:border-0
|
||||
<div class="lg:grid lg:grid-cols-[14rem_auto_12rem] lg:container mx-auto">
|
||||
<div
|
||||
id="sidebar"
|
||||
class="pt-12 h-screen lg:sticky top-0 fixed z-10 bg-c-bg w-60 lg:w-auto border-r-2 lg:border-0
|
||||
border-c-border-1 -translate-x-64 lg:translate-x-0 transition-transform"
|
||||
>
|
||||
<nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]">
|
||||
<Sidebar hierarchy={hierarchy} />
|
||||
</nav>
|
||||
</div>
|
||||
>
|
||||
<nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]">
|
||||
<Sidebar hierarchy={hierarchy} />
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<main
|
||||
class="py-[3.5rem] lg:pl-12 lg:pr-4 markdown min-w-0 small-container mx-auto"
|
||||
>
|
||||
<slot />
|
||||
<div class="h-32"></div>
|
||||
</main>
|
||||
<main
|
||||
class="py-[3.5rem] lg:pl-12 lg:pr-4 markdown min-w-0 small-container mx-auto"
|
||||
>
|
||||
<slot />
|
||||
<div class="h-32"></div>
|
||||
</main>
|
||||
|
||||
<div
|
||||
class="lg:pt-12 hidden lg:block pt-4 max-h-screen overflow-x-scroll sticky top-0"
|
||||
>
|
||||
<nav class="rounded-md lg:mt-10">
|
||||
<h2 class="font-display font-medium pb-2 text-c-text-2">
|
||||
On this page
|
||||
</h2>
|
||||
<div
|
||||
class="lg:pt-12 hidden lg:block pt-4 max-h-screen overflow-x-scroll sticky top-0"
|
||||
>
|
||||
<nav class="rounded-md lg:mt-10">
|
||||
<h2 class="font-display font-medium pb-2 text-c-text-2">
|
||||
On this page
|
||||
</h2>
|
||||
|
||||
<TOC headings={headings} />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<TOC headings={headings} />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { sidebarHighlight } from "./utils";
|
||||
// Highlight the current url of the sidebar
|
||||
document.addEventListener("DOMContentLoaded", sidebarHighlight);
|
||||
</script>
|
||||
<script>
|
||||
import { sidebarHighlight } from "./utils";
|
||||
// Highlight the current url of the sidebar
|
||||
document.addEventListener("DOMContentLoaded", sidebarHighlight);
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
@ -4,38 +4,40 @@ const { title } = Astro.props;
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title ?? "THP: Typed Hypertext Processor"}</title>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title ?? "THP: Typed Hypertext Processor"}</title>
|
||||
|
||||
<link rel="stylesheet" href="/css/global.css" />
|
||||
<link rel="stylesheet" href="/css/pages.css" />
|
||||
<link rel="stylesheet" href="/css/xcode-colors.css" />
|
||||
<link rel="stylesheet" href="/css/global.css" />
|
||||
<link rel="stylesheet" href="/css/pages.css" />
|
||||
<link rel="stylesheet" href="/css/xcode-colors.css" />
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://unpkg.com/@phosphor-icons/web@2.1.1/src/bold/style.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://unpkg.com/@phosphor-icons/web@2.1.1/src/bold/style.css"
|
||||
/>
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<body class="bg-c-bg text-c-text">
|
||||
<slot />
|
||||
<script src="//unpkg.com/alpinejs" defer></script>
|
||||
</head>
|
||||
|
||||
<script src="/js/alpine-3.14.0.min.js" defer></script>
|
||||
<script>
|
||||
import { highlightOnDom } from "./thpHighlighter";
|
||||
document.addEventListener("DOMContentLoaded", highlightOnDom);
|
||||
</script>
|
||||
</body>
|
||||
<body class="bg-c-bg text-c-text">
|
||||
<slot />
|
||||
|
||||
<script src="/js/alpine-3.14.0.min.js" defer></script>
|
||||
<script>
|
||||
import { highlightOnDom } from "./thpHighlighter";
|
||||
document.addEventListener("DOMContentLoaded", highlightOnDom);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,44 +5,46 @@ import TOC from "../components/TOC.astro";
|
||||
import Sidebar from "../components/Sidebar.astro";
|
||||
|
||||
export type PageEntry = {
|
||||
path: string;
|
||||
title?: string;
|
||||
children?: Array<PageEntry>;
|
||||
path: string;
|
||||
title?: string;
|
||||
children?: Array<PageEntry>;
|
||||
};
|
||||
|
||||
export interface AstroFile {
|
||||
frontmatter: Frontmatter
|
||||
__usesAstroImage: boolean
|
||||
url: string
|
||||
file: string
|
||||
relative_file: string
|
||||
frontmatter: Frontmatter;
|
||||
__usesAstroImage: boolean;
|
||||
url: string;
|
||||
file: string;
|
||||
relative_file: string;
|
||||
}
|
||||
|
||||
export interface Frontmatter {
|
||||
layout: string
|
||||
title: string
|
||||
order: number
|
||||
layout: string;
|
||||
title: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
/** Base url. It is used to later build a tree file system */
|
||||
base_url: string,
|
||||
frontmatter: Frontmatter;
|
||||
headings: any;
|
||||
posts: Array<AstroFile>;
|
||||
/** Base url. It is used to later build a tree file system */
|
||||
base_url: string;
|
||||
frontmatter: Frontmatter;
|
||||
headings: any;
|
||||
posts: Array<AstroFile>;
|
||||
version: string;
|
||||
};
|
||||
|
||||
const {
|
||||
base_url,
|
||||
frontmatter,
|
||||
headings,
|
||||
posts
|
||||
posts,
|
||||
version = "latest",
|
||||
}: Props = Astro.props;
|
||||
|
||||
const base_len = base_url.length;
|
||||
|
||||
const posts_2 = posts
|
||||
.map(post => ({
|
||||
.map((post) => ({
|
||||
...post,
|
||||
title: post.frontmatter.title,
|
||||
// this should be a path relative to the base url.
|
||||
@ -50,11 +52,11 @@ const posts_2 = posts
|
||||
// being `/spec/ast/tokens` would be `/ast/tokens`
|
||||
path: post.url.substring(base_len),
|
||||
}))
|
||||
.sort((p1, p2) => p1.frontmatter.order > p2.frontmatter.order? 1 : -1);
|
||||
.sort((p1, p2) => (p1.frontmatter.order > p2.frontmatter.order ? 1 : -1));
|
||||
|
||||
// build a hierarchy of the files
|
||||
const second_level: Record<string, Array<AstroFile>> = {
|
||||
"_": [],
|
||||
_: [],
|
||||
};
|
||||
for (const post of posts_2) {
|
||||
const fragments = post.path.split("/");
|
||||
@ -66,8 +68,7 @@ for (const post of posts_2) {
|
||||
second_level[folder_name] = [];
|
||||
}
|
||||
second_level[folder_name].push(post);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// add to root folder
|
||||
second_level["_"]!.push(post);
|
||||
}
|
||||
@ -88,8 +89,7 @@ for (const levels_key of levels_keys) {
|
||||
if (levels_key === "_") {
|
||||
// top level, already inserted
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const posts = second_level[levels_key]!;
|
||||
const sorted_posts = posts.toSorted(sort_posts);
|
||||
const parentEntry = {
|
||||
@ -106,69 +106,104 @@ function sort_posts(p1, p2) {
|
||||
if (!!p1.frontmatter.order && !!p2.frontmatter.order) {
|
||||
return p1.frontmatter.order > p2.frontmatter.order ? 1 : -1;
|
||||
} else {
|
||||
return p1.title > p2.title? 1 : -1;
|
||||
return p1.title > p2.title ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
const versions = ["latest", "v0.0.1"].filter((x) => x !== version);
|
||||
const url_components = base_url.split(version);
|
||||
const lc = url_components[0] ?? "";
|
||||
const rc = url_components[1] ?? "";
|
||||
---
|
||||
|
||||
<BaseLayout title={frontmatter.title}>
|
||||
<Navbar />
|
||||
<Navbar />
|
||||
|
||||
<div
|
||||
class="lg:grid lg:grid-cols-[14rem_auto_12rem] lg:container mx-auto font-display"
|
||||
>
|
||||
<div
|
||||
id="sidebar"
|
||||
class="pt-12 h-screen lg:sticky top-0 fixed z-10 bg-c-bg w-60 lg:w-auto border-r-2 lg:border-0
|
||||
<div
|
||||
class="lg:grid lg:grid-cols-[14rem_auto_12rem] lg:container mx-auto font-display"
|
||||
>
|
||||
<div
|
||||
id="sidebar"
|
||||
class="pt-12 h-screen lg:sticky top-0 fixed z-10 bg-c-bg w-60 lg:w-auto border-r-2 lg:border-0
|
||||
border-c-border-1 -translate-x-64 lg:translate-x-0 transition-transform"
|
||||
>
|
||||
<nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]">
|
||||
>
|
||||
<nav class="py-4 pr-2 overflow-x-scroll h-[calc(100vh-3rem)]">
|
||||
<div class="mb-2 relative" x-data="{open: false}">
|
||||
<div>
|
||||
<span> Language version: </span>
|
||||
</div>
|
||||
<button
|
||||
class="bg-c-bg text-c-text border border-c-primary rounded px-3 w-full font-mono mt-2 flex items-center hover:dark:bg-zinc-800 transition-colors cursor-pointer text-left"
|
||||
@click="open = !open"
|
||||
>
|
||||
<span class="inline-block w-full">
|
||||
{version}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
fill="var(--c-primary)"
|
||||
viewBox="0 0 256 256"
|
||||
style="padding-bottom: 4px"
|
||||
><path
|
||||
d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
x-bind:class="open? '': 'hidden'"
|
||||
class="bottom-0 w-full absolute"
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 w-full bg-c-bg border border-c-primary rounded"
|
||||
>
|
||||
{
|
||||
versions.map((x) => (
|
||||
<a
|
||||
class="px-3 py-1 hover:dark:bg-zinc-800 transition-colors font-mono inline-block w-full text-left"
|
||||
href={`${lc}${x}${rc}`}
|
||||
>
|
||||
{x}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="pb-8 px-1">
|
||||
<label for="version-select">THP version:</label>
|
||||
<select
|
||||
id="version-select"
|
||||
class="bg-c-bg text-c-on-bg border border-pink-700 rounded px-3 p-1 w-full font-mono"
|
||||
>
|
||||
<option selected>latest</option>
|
||||
<option>v0.0.1</option>
|
||||
</select>
|
||||
</form>
|
||||
<hr class="my-6" />
|
||||
|
||||
{
|
||||
entries.map((entry) => (
|
||||
<Sidebar entry={entry} />
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
{entries.map((entry) => <Sidebar entry={entry} />)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<main
|
||||
class="pt-[3.5rem] pb-[10rem] lg:pl-12 lg:pr-4 markdown min-w-0 small-container mx-auto"
|
||||
>
|
||||
<slot />
|
||||
</main>
|
||||
<main
|
||||
class="pt-[3.5rem] pb-[10rem] lg:pl-12 lg:pr-4 markdown min-w-0 small-container mx-auto"
|
||||
>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<div
|
||||
class="lg:pt-12 hidden lg:block pt-4 max-h-screen overflow-x-scroll sticky top-0"
|
||||
>
|
||||
<nav class="rounded-md lg:mt-10">
|
||||
<h2 class="font-display font-medium pb-2 text-c-text-2">
|
||||
On this page
|
||||
</h2>
|
||||
<div
|
||||
class="lg:pt-12 hidden lg:block pt-4 max-h-screen overflow-x-scroll sticky top-0"
|
||||
>
|
||||
<nav class="rounded-md lg:mt-10">
|
||||
<h2 class="font-display font-medium pb-2 text-c-text-2">
|
||||
On this page
|
||||
</h2>
|
||||
|
||||
<TOC headings={headings} />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<TOC headings={headings} />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { highlightOnDom } from "./thpHighlighter";
|
||||
document.addEventListener("DOMContentLoaded", highlightOnDom);
|
||||
</script>
|
||||
<script>
|
||||
import { sidebarHighlight } from "./utils";
|
||||
// Highlight the current url of the sidebar
|
||||
document.addEventListener("DOMContentLoaded", sidebarHighlight);
|
||||
</script>
|
||||
<script>
|
||||
import { highlightOnDom } from "./thpHighlighter";
|
||||
document.addEventListener("DOMContentLoaded", highlightOnDom);
|
||||
</script>
|
||||
<script>
|
||||
import { sidebarHighlight } from "./utils";
|
||||
// Highlight the current url of the sidebar
|
||||
document.addEventListener("DOMContentLoaded", sidebarHighlight);
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
@ -1,19 +1,23 @@
|
||||
|
||||
export function highlightOnDom() {
|
||||
const pre_elements = document.querySelectorAll("pre");
|
||||
for (const pre_el of pre_elements) {
|
||||
const language = pre_el.getAttribute("data-language");
|
||||
if (language === null) { continue; }
|
||||
|
||||
// Create a visual indicador
|
||||
const indicator = document.createElement("span");
|
||||
|
||||
let indicator_bg_class = "";
|
||||
if (language === "php") { indicator_bg_class = "bg-[#4f5b93]"; }
|
||||
else if (language === "html") { indicator_bg_class = "bg-[#dc4a20]"; }
|
||||
|
||||
indicator.className = `absolute top-1 right-0 inline-block text-sm select-none opacity-85 ${indicator_bg_class} px-2 rounded-full`;
|
||||
indicator.innerText = language;
|
||||
pre_el.appendChild(indicator);
|
||||
const pre_elements = document.querySelectorAll("pre");
|
||||
for (const pre_el of pre_elements) {
|
||||
const language = pre_el.getAttribute("data-language");
|
||||
if (language === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a visual indicador
|
||||
const indicator = document.createElement("span");
|
||||
|
||||
let indicator_bg_class = "";
|
||||
if (language === "php") {
|
||||
indicator_bg_class = "bg-[#4f5b93]";
|
||||
} else if (language === "html") {
|
||||
indicator_bg_class = "bg-[#dc4a20]";
|
||||
}
|
||||
|
||||
indicator.className = `absolute top-1 right-0 inline-block text-sm select-none opacity-85 ${indicator_bg_class} px-2 rounded-full`;
|
||||
indicator.innerText = language;
|
||||
pre_el.appendChild(indicator);
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
|
||||
export function sidebarHighlight() {
|
||||
let current_uri = window.location.pathname;
|
||||
let current_uri = window.location.pathname;
|
||||
|
||||
const sidebar = document.getElementById("sidebar")!
|
||||
.children[0]! as HTMLElement;
|
||||
const links = sidebar.querySelectorAll("a");
|
||||
for (const link of [...links]) {
|
||||
if (link.getAttribute("href") === current_uri) {
|
||||
sidebar.scrollTop =
|
||||
link.offsetTop - sidebar.offsetTop - 250;
|
||||
const sidebar = document.getElementById("sidebar")!
|
||||
.children[0]! as HTMLElement;
|
||||
const links = sidebar.querySelectorAll("a");
|
||||
for (const link of [...links]) {
|
||||
if (link.getAttribute("href") === current_uri) {
|
||||
sidebar.scrollTop = link.offsetTop - sidebar.offsetTop - 250;
|
||||
|
||||
link.classList.add("bg-pink-200", "dark:bg-pink-950");
|
||||
break;
|
||||
}
|
||||
link.classList.add("bg-pink-200", "dark:bg-pink-950");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { leftTrimDedent } from "../components/utils";
|
||||
import { HighlightLevel } from "./types";
|
||||
import type { ErrorLabel, MistiErr, Token, TokenizeResult, TokenType } from "./types";
|
||||
import type {
|
||||
ErrorLabel,
|
||||
MistiErr,
|
||||
Token,
|
||||
TokenizeResult,
|
||||
TokenType,
|
||||
} from "./types";
|
||||
|
||||
const error_classes = "underline underline-offset-4 decoration-wavy decoration-red-500";
|
||||
const error_classes =
|
||||
"underline underline-offset-4 decoration-wavy decoration-red-500";
|
||||
|
||||
/**
|
||||
* Highlights code using the compiler
|
||||
@ -12,15 +19,18 @@ const error_classes = "underline underline-offset-4 decoration-wavy decoration-r
|
||||
* - The tokens as a list of <span /> elements
|
||||
* - An error message, if any
|
||||
*/
|
||||
export async function native_highlighter(code: string, level = HighlightLevel.Lexic): Promise<[string, string | null]> {
|
||||
let formatted_code = leftTrimDedent(code).join("\n");
|
||||
export async function native_highlighter(
|
||||
code: string,
|
||||
level = HighlightLevel.Lexic,
|
||||
): Promise<[string, string | null]> {
|
||||
let formatted_code = leftTrimDedent(code).join("\n");
|
||||
|
||||
try {
|
||||
let result = await native_lex(formatted_code, level);
|
||||
return highlight_syntax(formatted_code, result);
|
||||
} catch (error) {
|
||||
return compiler_error(formatted_code, error as MistiErr);
|
||||
}
|
||||
try {
|
||||
let result = await native_lex(formatted_code, level);
|
||||
return highlight_syntax(formatted_code, result);
|
||||
} catch (error) {
|
||||
return compiler_error(formatted_code, error as MistiErr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,35 +40,38 @@ export async function native_highlighter(code: string, level = HighlightLevel.Le
|
||||
* - The tokens as a list of <span /> elements
|
||||
* - An error message, if any
|
||||
*/
|
||||
function highlight_syntax(code: string, result: TokenizeResult): [string, string | null] {
|
||||
if (result.Ok) {
|
||||
const tokens_html = render_tokens(code, result.Ok);
|
||||
function highlight_syntax(
|
||||
code: string,
|
||||
result: TokenizeResult,
|
||||
): [string, string | null] {
|
||||
if (result.Ok) {
|
||||
const tokens_html = render_tokens(code, result.Ok);
|
||||
|
||||
return [tokens_html, null];
|
||||
} else if (result.MixedErr) {
|
||||
const [tokens, errors] = result.MixedErr;
|
||||
// TODO: Implement error rendering, based on the new error schema
|
||||
return [tokens_html, null];
|
||||
} else if (result.MixedErr) {
|
||||
const [tokens, errors] = result.MixedErr;
|
||||
// TODO: Implement error rendering, based on the new error schema
|
||||
|
||||
const tokens_html = render_tokens(code, tokens, errors.labels);
|
||||
return [tokens_html, `error code ${errors.error_code}`];
|
||||
} else if (result.Err) {
|
||||
// TODO: Implement error rendering, based on the new error schema
|
||||
const tokens_html = render_tokens(code, tokens, errors.labels);
|
||||
return [tokens_html, `error code ${errors.error_code}`];
|
||||
} else if (result.Err) {
|
||||
// TODO: Implement error rendering, based on the new error schema
|
||||
|
||||
return [code, `lexical error ${result.Err.error_code}`]
|
||||
} else {
|
||||
console.error(result);
|
||||
throw new Error("Web page error: The compiler returned a case that wasn't handled.");
|
||||
}
|
||||
return [code, `lexical error ${result.Err.error_code}`];
|
||||
} else {
|
||||
console.error(result);
|
||||
throw new Error(
|
||||
"Web page error: The compiler returned a case that wasn't handled.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** A fatal error with the THP compiler */
|
||||
function compiler_error(code: string, error: MistiErr): [string, string] {
|
||||
console.log(error);
|
||||
return [code, "Fatal compiler error"];
|
||||
console.log(error);
|
||||
return [code, "Fatal compiler error"];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a list of tokens into colored HTML, and underlines present errors
|
||||
*
|
||||
@ -68,71 +81,91 @@ function compiler_error(code: string, error: MistiErr): [string, string] {
|
||||
* @param error_end Absolute position to where the error ends.
|
||||
* @returns
|
||||
*/
|
||||
function render_tokens(input: string, tokens: Array<Token>, error_labels: Array<ErrorLabel> = []): string {
|
||||
const input_chars = input.split("");
|
||||
let output = "";
|
||||
function render_tokens(
|
||||
input: string,
|
||||
tokens: Array<Token>,
|
||||
error_labels: Array<ErrorLabel> = [],
|
||||
): string {
|
||||
const input_chars = input.split("");
|
||||
let output = "";
|
||||
|
||||
// Collects all the token ranges in all error labels
|
||||
const error_ranges: Array<[number, number]> = error_labels.map(l => [l.start, l.end]);
|
||||
// Collects all the token ranges in all error labels
|
||||
const error_ranges: Array<[number, number]> = error_labels.map((l) => [
|
||||
l.start,
|
||||
l.end,
|
||||
]);
|
||||
|
||||
let current_pos = 0;
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
const t = tokens[i]!;
|
||||
const token_start = t.position;
|
||||
const token_end = t.position + t.value.length;
|
||||
let current_pos = 0;
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
const t = tokens[i]!;
|
||||
const token_start = t.position;
|
||||
const token_end = t.position + t.value.length;
|
||||
|
||||
// check if the current token is in any error label
|
||||
let is_errored = false;
|
||||
for (const range of error_ranges) {
|
||||
const [error_start, error_end] = range;
|
||||
// check if the current token is in any error label
|
||||
let is_errored = false;
|
||||
for (const range of error_ranges) {
|
||||
const [error_start, error_end] = range;
|
||||
|
||||
if (token_start >= error_start && token_end <= error_end) {
|
||||
is_errored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Some tokens require processing (like multiline comments)
|
||||
|
||||
// There are some tokens that are empty, ignore them
|
||||
if (t.value == "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append all characters before the token
|
||||
output += input_chars.slice(current_pos, token_start).join("");
|
||||
|
||||
// Append the token
|
||||
const [token_value, new_token_end] = process_token_value_and_end(t.value, t.token_type, token_end);
|
||||
const token_type = translate_token_type(t.token_type, token_value);
|
||||
output += `<span class="token ${token_type} ${is_errored ? error_classes : ""}">${token_value}</span>`;
|
||||
|
||||
current_pos = new_token_end;
|
||||
if (token_start >= error_start && token_end <= error_end) {
|
||||
is_errored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point `output` is a string with tokens
|
||||
// now i want to append the label messages:
|
||||
// - split the output by newlines
|
||||
// - for every label, append a new line after each error
|
||||
// Some tokens require processing (like multiline comments)
|
||||
|
||||
const lines = output.split("\n");
|
||||
let offset = 0;
|
||||
for (const label of error_labels) {
|
||||
// get the line number of the label
|
||||
const [line_number, col_number] = absolute_to_line_column(input, label.start);
|
||||
let spaces_len = col_number - 1;
|
||||
if (spaces_len < 0) { spaces_len = 0 }
|
||||
|
||||
const spaces = new Array(spaces_len).fill(" ").join("");
|
||||
lines.splice(line_number + offset, 0, create_inline_error_message(spaces, label.message));
|
||||
offset += 1;
|
||||
// There are some tokens that are empty, ignore them
|
||||
if (t.value == "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
// Append all characters before the token
|
||||
output += input_chars.slice(current_pos, token_start).join("");
|
||||
|
||||
// Append the token
|
||||
const [token_value, new_token_end] = process_token_value_and_end(
|
||||
t.value,
|
||||
t.token_type,
|
||||
token_end,
|
||||
);
|
||||
const token_type = translate_token_type(t.token_type, token_value);
|
||||
output += `<span class="token ${token_type} ${is_errored ? error_classes : ""}">${token_value}</span>`;
|
||||
|
||||
current_pos = new_token_end;
|
||||
}
|
||||
|
||||
// at this point `output` is a string with tokens
|
||||
// now i want to append the label messages:
|
||||
// - split the output by newlines
|
||||
// - for every label, append a new line after each error
|
||||
|
||||
const lines = output.split("\n");
|
||||
let offset = 0;
|
||||
for (const label of error_labels) {
|
||||
// get the line number of the label
|
||||
const [line_number, col_number] = absolute_to_line_column(
|
||||
input,
|
||||
label.start,
|
||||
);
|
||||
let spaces_len = col_number - 1;
|
||||
if (spaces_len < 0) {
|
||||
spaces_len = 0;
|
||||
}
|
||||
|
||||
const spaces = new Array(spaces_len).fill(" ").join("");
|
||||
lines.splice(
|
||||
line_number + offset,
|
||||
0,
|
||||
create_inline_error_message(spaces, label.message),
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function create_inline_error_message(spaces: string, message: string): string {
|
||||
return `<span class="relative inline-block w-full before:h-full before:block before:absolute before:left-0 before:w-[calc(100%+1.5rem)] before:-translate-x-3 before:bg-red-200 before:dark:bg-red-950" style="white-space: initial"><span class="relative dark:text-red-200 text-red-900 font-bold">${spaces}╰╴${message}</span></span>`;
|
||||
return `<span class="relative inline-block w-full before:h-full before:block before:absolute before:left-0 before:w-[calc(100%+1.5rem)] before:-translate-x-3 before:bg-red-200 before:dark:bg-red-950" style="white-space: initial"><span class="relative dark:text-red-200 text-red-900 font-bold">${spaces}╰╴${message}</span></span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,23 +176,26 @@ function create_inline_error_message(spaces: string, message: string): string {
|
||||
* @param input the source code
|
||||
* @param absolute the absolute position
|
||||
*/
|
||||
function absolute_to_line_column(input: string, absolute: number): [number, number] {
|
||||
let line_count = 1;
|
||||
let last_newline_pos = 0;
|
||||
function absolute_to_line_column(
|
||||
input: string,
|
||||
absolute: number,
|
||||
): [number, number] {
|
||||
let line_count = 1;
|
||||
let last_newline_pos = 0;
|
||||
|
||||
// Count lines
|
||||
for (let i = 0; i < input.length; i += 1) {
|
||||
if (i === absolute) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (input[i] === "\n") {
|
||||
line_count += 1;
|
||||
last_newline_pos = i;
|
||||
}
|
||||
// Count lines
|
||||
for (let i = 0; i < input.length; i += 1) {
|
||||
if (i === absolute) {
|
||||
break;
|
||||
}
|
||||
|
||||
return [line_count, absolute - last_newline_pos];
|
||||
if (input[i] === "\n") {
|
||||
line_count += 1;
|
||||
last_newline_pos = i;
|
||||
}
|
||||
}
|
||||
|
||||
return [line_count, absolute - last_newline_pos];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,72 +208,110 @@ function absolute_to_line_column(input: string, absolute: number): [number, numb
|
||||
* @param first_end The position where the token ends according to the token value
|
||||
* @returns
|
||||
*/
|
||||
function process_token_value_and_end(value: string, token_type: TokenType, first_end: number): [string, number] {
|
||||
let token_value = value;
|
||||
let new_end = first_end;
|
||||
if (token_type === "MultilineComment") {
|
||||
token_value = `/*${token_value}*/`;
|
||||
new_end += 4;
|
||||
} else if (token_type === "String") {
|
||||
token_value = `"${token_value}"`;
|
||||
new_end += 2;
|
||||
}
|
||||
function process_token_value_and_end(
|
||||
value: string,
|
||||
token_type: TokenType,
|
||||
first_end: number,
|
||||
): [string, number] {
|
||||
let token_value = value;
|
||||
let new_end = first_end;
|
||||
if (token_type === "MultilineComment") {
|
||||
token_value = `/*${token_value}*/`;
|
||||
new_end += 4;
|
||||
} else if (token_type === "String") {
|
||||
token_value = `"${token_value}"`;
|
||||
new_end += 2;
|
||||
}
|
||||
|
||||
// Escape html and return
|
||||
return [
|
||||
token_value.replaceAll(/</g, "<").replaceAll(/>/g, ">"),
|
||||
new_end
|
||||
];
|
||||
// Escape html and return
|
||||
return [
|
||||
token_value.replaceAll(/</g, "<").replaceAll(/>/g, ">"),
|
||||
new_end,
|
||||
];
|
||||
}
|
||||
|
||||
function translate_token_type(tt: TokenType, value: string): string {
|
||||
const keywords = ["throws", "extends", "constructor", "static", "const",
|
||||
"enum", "union", "use", "break", "catch", "continue", "as", "do",
|
||||
"finally", "fun", "fn", "nil", "return", "throw",
|
||||
"try", "type", "with", "of", "abstract", "class", "interface",
|
||||
"private", "protected", "pub", "override", "open", "init", "val", "var", "mut", "clone"];
|
||||
const keywords = [
|
||||
"throws",
|
||||
"extends",
|
||||
"constructor",
|
||||
"static",
|
||||
"const",
|
||||
"enum",
|
||||
"union",
|
||||
"use",
|
||||
"break",
|
||||
"catch",
|
||||
"continue",
|
||||
"as",
|
||||
"do",
|
||||
"finally",
|
||||
"fun",
|
||||
"fn",
|
||||
"nil",
|
||||
"return",
|
||||
"throw",
|
||||
"try",
|
||||
"type",
|
||||
"with",
|
||||
"of",
|
||||
"abstract",
|
||||
"class",
|
||||
"interface",
|
||||
"private",
|
||||
"protected",
|
||||
"pub",
|
||||
"override",
|
||||
"open",
|
||||
"init",
|
||||
"val",
|
||||
"var",
|
||||
"mut",
|
||||
"clone",
|
||||
];
|
||||
|
||||
switch (tt) {
|
||||
case "Datatype":
|
||||
return "class-name";
|
||||
case "Identifier": {
|
||||
if (keywords.includes(value)) {
|
||||
return "keyword";
|
||||
}
|
||||
switch (tt) {
|
||||
case "Datatype":
|
||||
return "class-name";
|
||||
case "Identifier": {
|
||||
if (keywords.includes(value)) {
|
||||
return "keyword";
|
||||
}
|
||||
|
||||
return "identifier";
|
||||
}
|
||||
case "Int":
|
||||
return "number";
|
||||
case "Float":
|
||||
return "number";
|
||||
case "String":
|
||||
return "string";
|
||||
case "Comment":
|
||||
case "MultilineComment":
|
||||
return "comment";
|
||||
// keywords:
|
||||
case "VAL":
|
||||
case "VAR":
|
||||
case "FUN":
|
||||
case "IF":
|
||||
case "ELSE":
|
||||
case "FOR":
|
||||
case "IN":
|
||||
case "WHILE":
|
||||
case "MATCH":
|
||||
case "CASE":
|
||||
return "keyword";
|
||||
default:
|
||||
return tt;
|
||||
return "identifier";
|
||||
}
|
||||
case "Int":
|
||||
return "number";
|
||||
case "Float":
|
||||
return "number";
|
||||
case "String":
|
||||
return "string";
|
||||
case "Comment":
|
||||
case "MultilineComment":
|
||||
return "comment";
|
||||
// keywords:
|
||||
case "VAL":
|
||||
case "VAR":
|
||||
case "FUN":
|
||||
case "IF":
|
||||
case "ELSE":
|
||||
case "FOR":
|
||||
case "IN":
|
||||
case "WHILE":
|
||||
case "MATCH":
|
||||
case "CASE":
|
||||
return "keyword";
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
}
|
||||
|
||||
const native_lex = (code: string, level: HighlightLevel) => new Promise<TokenizeResult>((resolve, reject) => {
|
||||
const native_lex = (code: string, level: HighlightLevel) =>
|
||||
new Promise<TokenizeResult>((resolve, reject) => {
|
||||
// Get binary path from .env
|
||||
const binary = import.meta.env.THP_BINARY;
|
||||
if (!binary) {
|
||||
throw new Error("THP_BINARY not set in .env");
|
||||
throw new Error("THP_BINARY not set in .env");
|
||||
}
|
||||
|
||||
const subprocess = spawn(binary, ["tokenize", "-l", level.toString()]);
|
||||
@ -248,18 +322,18 @@ const native_lex = (code: string, level: HighlightLevel) => new Promise<Tokenize
|
||||
subprocess.stdin.end();
|
||||
|
||||
subprocess.stdout.on("data", (data) => {
|
||||
response += data.toString();
|
||||
response += data.toString();
|
||||
});
|
||||
|
||||
subprocess.stderr.on("data", (data) => {
|
||||
error += data.toString();
|
||||
error += data.toString();
|
||||
});
|
||||
|
||||
subprocess.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve(JSON.parse(response));
|
||||
} else {
|
||||
reject(new Error(error));
|
||||
}
|
||||
if (code === 0) {
|
||||
resolve(JSON.parse(response));
|
||||
} else {
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@ -1,73 +1,72 @@
|
||||
export type ReferenceItem = {
|
||||
symbol_start: number
|
||||
symbol_end: number
|
||||
reference: string
|
||||
}
|
||||
symbol_start: number;
|
||||
symbol_end: number;
|
||||
reference: string;
|
||||
};
|
||||
|
||||
export interface Token {
|
||||
token_type: TokenType
|
||||
value: string
|
||||
position: number
|
||||
token_type: TokenType;
|
||||
value: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export type TokenType =
|
||||
| "Identifier"
|
||||
| "Datatype"
|
||||
| "Int"
|
||||
| "Float"
|
||||
| "String"
|
||||
| "Operator"
|
||||
| "LeftParen"
|
||||
| "RightParen"
|
||||
| "LeftBracket"
|
||||
| "RightBracket"
|
||||
| "LeftBrace"
|
||||
| "RightBrace"
|
||||
| "NewLine"
|
||||
| "Comment"
|
||||
| "MultilineComment"
|
||||
| "Comma"
|
||||
| "INDENT"
|
||||
| "DEDENT"
|
||||
| "VAL"
|
||||
| "VAR"
|
||||
| "EOF"
|
||||
| "FUN"
|
||||
| "IF"
|
||||
| "ELSE"
|
||||
| "ELSE"
|
||||
| "FOR"
|
||||
| "IN"
|
||||
| "WHILE"
|
||||
| "MATCH"
|
||||
| "CASE"
|
||||
;
|
||||
| "Identifier"
|
||||
| "Datatype"
|
||||
| "Int"
|
||||
| "Float"
|
||||
| "String"
|
||||
| "Operator"
|
||||
| "LeftParen"
|
||||
| "RightParen"
|
||||
| "LeftBracket"
|
||||
| "RightBracket"
|
||||
| "LeftBrace"
|
||||
| "RightBrace"
|
||||
| "NewLine"
|
||||
| "Comment"
|
||||
| "MultilineComment"
|
||||
| "Comma"
|
||||
| "INDENT"
|
||||
| "DEDENT"
|
||||
| "VAL"
|
||||
| "VAR"
|
||||
| "EOF"
|
||||
| "FUN"
|
||||
| "IF"
|
||||
| "ELSE"
|
||||
| "ELSE"
|
||||
| "FOR"
|
||||
| "IN"
|
||||
| "WHILE"
|
||||
| "MATCH"
|
||||
| "CASE";
|
||||
|
||||
export interface MistiErr {
|
||||
error_code: number
|
||||
error_offset: number
|
||||
labels: Array<ErrorLabel>
|
||||
note: string | null,
|
||||
help: string | null,
|
||||
error_code: number;
|
||||
error_offset: number;
|
||||
labels: Array<ErrorLabel>;
|
||||
note: string | null;
|
||||
help: string | null;
|
||||
}
|
||||
|
||||
export interface ErrorLabel {
|
||||
message: string
|
||||
start: number
|
||||
end: number
|
||||
message: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export interface TokenizeResult {
|
||||
/** All checks passed */
|
||||
Ok?: Array<Token>,
|
||||
/** A non lexic error was found */
|
||||
MixedErr?: [Array<Token>, MistiErr],
|
||||
/** A lexic error was found */
|
||||
Err?: MistiErr,
|
||||
/** All checks passed */
|
||||
Ok?: Array<Token>;
|
||||
/** A non lexic error was found */
|
||||
MixedErr?: [Array<Token>, MistiErr];
|
||||
/** A lexic error was found */
|
||||
Err?: MistiErr;
|
||||
}
|
||||
|
||||
export enum HighlightLevel {
|
||||
Lexic = 0,
|
||||
Syntactic = 1,
|
||||
Semantic = 2,
|
||||
Lexic = 0,
|
||||
Syntactic = 1,
|
||||
Semantic = 2,
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
export function is_digit(c: string): boolean {
|
||||
return c >= '0' && c <= '9';
|
||||
return c >= "0" && c <= "9";
|
||||
}
|
||||
|
||||
export function is_lowercase(c: string): boolean {
|
||||
return c >= 'a' && c <= 'z';
|
||||
return c >= "a" && c <= "z";
|
||||
}
|
||||
|
||||
export function is_uppercase(c: string): boolean {
|
||||
return c >= 'A' && c <= 'Z';
|
||||
return c >= "A" && c <= "Z";
|
||||
}
|
||||
|
||||
export function is_identifier_char(c: string): boolean {
|
||||
return is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === '_';
|
||||
return is_lowercase(c) || is_uppercase(c) || is_digit(c) || c === "_";
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
---
|
||||
layout: ../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import TwoColumn from "../../../components/TwoColumn.astro"
|
||||
import Code from "../../../components/Code.astro"
|
||||
import CodeMin from "../../../components/docs/CodeMin.astro"
|
||||
import Warning from "../../../components/docs/Warning.astro"
|
||||
|
||||
import TwoColumn from "../../../components/TwoColumn.astro";
|
||||
import Code from "../../../components/Code.astro";
|
||||
import CodeMin from "../../../components/docs/CodeMin.astro";
|
||||
import Warning from "../../../components/docs/Warning.astro";
|
||||
|
||||
# Array
|
||||
|
||||
@ -19,28 +20,32 @@ THP arrays are 0-indexed.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
type Array[T] = Map[Int, T]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Where `T` is the datatype that the Array stores. For example:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Array[Int] // An array of integers
|
||||
Array[Float] // An array of floats
|
||||
Array[Array[String]] // A 2-dimensional array of strings
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## PHP array internals
|
||||
|
||||
<Warning>
|
||||
TL;DR: **Never** assign to an array using an invalid index. If you do
|
||||
the program will not crash, instead it will not behaved as expected
|
||||
[(this is a common problem in PHP)](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/).
|
||||
THP tries its best to solve such behavior.
|
||||
TL;DR: **Never** assign to an array using an invalid index. If you do the
|
||||
program will not crash, instead it will not behaved as expected [(this is a
|
||||
common problem in
|
||||
PHP)](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/). THP tries
|
||||
its best to solve such behavior.
|
||||
</Warning>
|
||||
|
||||
|
||||
Since THP compiles down to PHP, it's important to understand how PHP
|
||||
represents arrays internally.
|
||||
PHP doesn't have arrays. Instead, PHP has ordered maps and syntax sugar
|
||||
@ -48,9 +53,11 @@ to make them look like arrays.
|
||||
|
||||
When declaring an array like:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
var arr = ["a", "b", "c"]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
in reality what goes into memory is a map with numbers as keys:
|
||||
|
||||
@ -83,51 +90,51 @@ numbers[-10] = "?" // Assign to a negative index
|
||||
|
||||
// Now the array will be (using thp notation):
|
||||
// .{
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// }
|
||||
|
||||
numbers[7] = "!" // Out of bounds assignment
|
||||
numbers[7] = "!" // Out of bounds assignment
|
||||
|
||||
// Now the array will be:
|
||||
// .{
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 7: "!",
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 7: "!",
|
||||
// }
|
||||
|
||||
// In this loop, values will be printed following when
|
||||
// they were inserted, not by order of the index.
|
||||
for #(_, value) in numbers
|
||||
for #(\_, value) in numbers
|
||||
{
|
||||
print("{value} ") // Will print: a b c ? !
|
||||
print("{value} ") // Will print: a b c ? !
|
||||
}
|
||||
|
||||
numbers[-4] = "???"
|
||||
// .{
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 7: "!",
|
||||
// -4: "???",
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 7: "!",
|
||||
// -4: "???",
|
||||
// }
|
||||
|
||||
// When pushing, the value will be assigned to the highest key + 1
|
||||
numbers.push("/") // This will be at position 8
|
||||
numbers.push("/") // This will be at position 8
|
||||
|
||||
// .{
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 7: "!",
|
||||
// -4: "???",
|
||||
// 8: "/",
|
||||
// 0: "a",
|
||||
// 1: "b",
|
||||
// 2: "c",
|
||||
// -10: "?",
|
||||
// 7: "!",
|
||||
// -4: "???",
|
||||
// 8: "/",
|
||||
// }
|
||||
`} />
|
||||
|
||||
@ -135,12 +142,9 @@ This is one of many fundamental flaws with PHP. The only way to solve it
|
||||
would be to check every insertion at runtime, and this would have a
|
||||
performance penalty.
|
||||
|
||||
|
||||
From now on, the documentation will continue to work with the Array
|
||||
abstraction, as if all indexes were valid.
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Empty array
|
||||
@ -148,31 +152,36 @@ abstraction, as if all indexes were valid.
|
||||
To create an empty array use square brackets.
|
||||
If you create an empty array, you need to specify the datatype.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Array[Int] empty = []
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
### Creation
|
||||
|
||||
To create an array use square brackets notation:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val numbers = [0, 1, 2, 3, 4, 5]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When the array is not empty, you don't need to specify a datatype.
|
||||
|
||||
When the Array is declared over many lines, the last
|
||||
item should have a trailing comma:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val colors = [
|
||||
"red",
|
||||
"blue",
|
||||
"green", // trailing comma
|
||||
]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
If it doesn't, the code formatter will automatically
|
||||
insert one for you.
|
||||
@ -198,13 +207,14 @@ mutable[0] = 322
|
||||
|
||||
To append an element to an array, use the method `push()`:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
mutable.push(4)
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Do not insert into an invalid index. See [PHP array internals](#php-array-internals) to learn why.
|
||||
|
||||
|
||||
### Iteration
|
||||
|
||||
Use a `for` loop to iterate over the elements of an array:
|
||||
@ -214,7 +224,7 @@ val colors = ["red", "green", "blue"]
|
||||
|
||||
for c in colors
|
||||
{
|
||||
print("{c} ")
|
||||
print("{c} ")
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -230,12 +240,11 @@ val colors = ["red", "green", "blue"]
|
||||
|
||||
for c in colors
|
||||
{
|
||||
c = "orange" // Compile error: Can't assign to an immutable variable
|
||||
print("{c} ")
|
||||
c = "orange" // Compile error: Can't assign to an immutable variable
|
||||
print("{c} ")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
You can also declare an index along with the value:
|
||||
|
||||
<Code thpcode={`
|
||||
@ -243,7 +252,7 @@ val colors = ["red", "green", "blue"]
|
||||
|
||||
for index, color in colors
|
||||
{
|
||||
println("item {index}: {c}")
|
||||
println("item {index}: {c}")
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -253,93 +262,89 @@ item 1: green
|
||||
item 2: blue
|
||||
```
|
||||
|
||||
|
||||
### Access
|
||||
|
||||
To access a value of the array use square brackets notation:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print(colors[0])
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Since the index might not exist, this will return a
|
||||
[nullable type](/learn/error-handling/null/) that you have to handle.
|
||||
|
||||
|
||||
### Destructuring
|
||||
|
||||
THP arrays don't have destructuring, since the values can all be `null`.
|
||||
If you know that the number of elements is fixed and valid, use Tuples instead.
|
||||
|
||||
|
||||
### Operators
|
||||
|
||||
While PHP allows using certain operators with arrays, THP disallows that.
|
||||
Methods that perform comparisons should be used instead.
|
||||
|
||||
|
||||
### Assignment
|
||||
|
||||
// TODO: Detail that assignment of arrays is copy on write
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
In the parameters, <code class="token keyword">self</code> is the array to operate on.
|
||||
|
||||
|
||||
<table class="table-fixed w-full border-collapse border-2 dark:border-zinc-900 border-stone-400">
|
||||
<thead>
|
||||
<td class="p-2">Method</td>
|
||||
<td class="py-2">Description</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin href="./concat" thpcode={`
|
||||
<thead>
|
||||
<td class="p-2">Method</td>
|
||||
<td class="py-2">Description</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin
|
||||
href="./concat"
|
||||
thpcode={`
|
||||
fun concat[T](
|
||||
self,
|
||||
Array[T]... arrays,
|
||||
) -> Array[T]
|
||||
`} />
|
||||
</td>
|
||||
<td>
|
||||
Concatenate with other arrays, and return the result
|
||||
as a new array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode={`
|
||||
`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
Concatenate with other arrays, and return the result as a new array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin
|
||||
thpcode={`
|
||||
fun filter[T](
|
||||
self,
|
||||
(T) -> (Bool) callback,
|
||||
) -> Array[T]
|
||||
`} />
|
||||
</td>
|
||||
<td>
|
||||
Filters elements using a callback function, and returns
|
||||
them in a new array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode="fun push[T](self, T... elements) -> Int" />
|
||||
</td>
|
||||
<td>
|
||||
Appends one or more elements to the end of the array.
|
||||
Returns the new length of the array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode="fun pop[T](self) -> T" />
|
||||
</td>
|
||||
<td>
|
||||
Removes the last value of the array, and returns it.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
Filters elements using a callback function, and returns them in a new
|
||||
array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode="fun push[T](self, T... elements) -> Int" />
|
||||
</td>
|
||||
<td>
|
||||
Appends one or more elements to the end of the array. Returns the new
|
||||
length of the array.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="dark:odd:bg-zinc-900 odd:bg-stone-200">
|
||||
<td class="px-2">
|
||||
<CodeMin thpcode="fun pop[T](self) -> T" />
|
||||
</td>
|
||||
<td>Removes the last value of the array, and returns it.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
layout: ../../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import Code from "../../../../components/Code.astro"
|
||||
|
||||
import Code from "../../../../components/Code.astro";
|
||||
|
||||
# `Array.concat`
|
||||
|
||||
@ -9,12 +10,14 @@ Concatenate with other arrays, and return the result as a new array.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun concat[T](
|
||||
self,
|
||||
Array[T]... arrays,
|
||||
) -> Array[T]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Parameters
|
||||
|
||||
@ -25,7 +28,6 @@ Concatenate with other arrays, and return the result as a new array.
|
||||
|
||||
`Array[T]`: A new array that contains the elements from all arrays.
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
Concatenates the elements of the callee and the elements of each array in the
|
||||
@ -49,6 +51,7 @@ Example concatenating 2 arrays:
|
||||
|
||||
val result = first.concat(second)
|
||||
assert_eq(result, [1, 2, 3, 4, 5, 6])
|
||||
|
||||
`} />
|
||||
|
||||
Example concatenating 3 arrays:
|
||||
@ -60,6 +63,7 @@ Example concatenating 3 arrays:
|
||||
|
||||
val result = first.concat(second, third)
|
||||
assert_eq(result, ["a", "b", "c", "d", "e", "f"])
|
||||
|
||||
`} />
|
||||
|
||||
Example concatenating without any other array. In this case
|
||||
@ -70,6 +74,7 @@ Example concatenating without any other array. In this case
|
||||
|
||||
val result = first.concat()
|
||||
assert_eq(result, [1, 2, 3])
|
||||
|
||||
`} />
|
||||
|
||||
Example concatenating an empty array with a filled array:
|
||||
@ -80,8 +85,8 @@ Example concatenating an empty array with a filled array:
|
||||
|
||||
val result = first.concat(second)
|
||||
assert_eq(result, [10, 20, 30])
|
||||
`} />
|
||||
|
||||
`} />
|
||||
|
||||
## PHP interop
|
||||
|
||||
@ -107,6 +112,3 @@ array(3) {
|
||||
[2]=> string(1) "c"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
layout: ../../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import Code from "../../../../components/Code.astro"
|
||||
|
||||
import Code from "../../../../components/Code.astro";
|
||||
|
||||
# `Array.fold`
|
||||
|
||||
@ -11,13 +12,15 @@ the left.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun fold[A, B](
|
||||
self[A],
|
||||
B init,
|
||||
(B acc, A next) -> (B) transform_function,
|
||||
) -> B
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Parameters
|
||||
|
||||
@ -31,7 +34,6 @@ the left.
|
||||
- Otherwise, returns the result of applying `transform_function` to the accumulator `acc`,
|
||||
and the next element `next`.
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
Fold allows you to transform an array of `A` into a single `B`, following
|
||||
@ -39,20 +41,21 @@ an arbitraty function.
|
||||
|
||||
For example, let's say that you have an array of numbers:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val digits = [1, 9, 9, 5]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
And you want to join all digits into a String like `"1985"`. You can
|
||||
achieve this with a fold.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
val digits = [1, 9, 8, 5]
|
||||
val init = ""
|
||||
fun f(String acc, Int next) = acc ++ next
|
||||
|
||||
digits.fold("", f) // "1985"
|
||||
digits.fold("", f) // "1985"
|
||||
`} />
|
||||
|
||||
- `f` is a function that takes a previous value (known as the accumulator)
|
||||
@ -93,10 +96,12 @@ The `transform_function` can be any function, and can operate over any type.
|
||||
For example, you could sum all numbers instead of concatenating
|
||||
like this:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val digits = [1, 9, 8, 5]
|
||||
digits.fold(0, fun(acc, next) = acc + next) // 23
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```php
|
||||
f( f( f( f(0, 1), 9), 8), 5)
|
||||
@ -118,8 +123,3 @@ val result = builder.build()
|
||||
`} />
|
||||
|
||||
Or anything.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
layout: ../../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import Code from "../../../../components/Code.astro"
|
||||
|
||||
import Code from "../../../../components/Code.astro";
|
||||
|
||||
# `Array.map`
|
||||
|
||||
@ -19,16 +20,15 @@ and returns their result in a new array.
|
||||
self[],
|
||||
(A) -> (B) map_function
|
||||
) -> Array[B]
|
||||
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
|
||||
fun multi_def
|
||||
| ("prosor prosor", 322) -> Float
|
||||
{
|
||||
fmt.Println("prosor prosor %d", 322)
|
||||
fmt.Println("prosor prosor %d", 322)
|
||||
}
|
||||
| (String a, Int b) -> Float
|
||||
{
|
||||
@ -40,4 +40,3 @@ fun multi_def
|
||||
}
|
||||
|
||||
`} />
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
layout: ../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import TwoColumn from "../../../components/TwoColumn.astro"
|
||||
import Code from "../../../components/Code.astro"
|
||||
|
||||
import TwoColumn from "../../../components/TwoColumn.astro";
|
||||
import Code from "../../../components/Code.astro";
|
||||
|
||||
# module `std`
|
||||
|
||||
@ -28,12 +29,14 @@ if (str_contains("abc", "a")) {
|
||||
In THP there is no `str_contains` function. Instead, you'd call the
|
||||
`contains` method on the string:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
if "abc".contains("a")
|
||||
{
|
||||
// ...
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## On naming
|
||||
|
||||
@ -56,10 +59,11 @@ If you need to use a PHP class with a lowercase name you can use the following s
|
||||
class animal {}
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val my_animal = 'animal()
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## API: Datatypes
|
||||
|
||||
@ -96,5 +100,3 @@ A [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) double precision floating p
|
||||
Prints text into stdout.
|
||||
|
||||
</TwoColumn>
|
||||
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
---
|
||||
layout: ../../../layouts/ApiLayout.astro
|
||||
---
|
||||
import TwoColumn from "../../../components/TwoColumn.astro"
|
||||
import Code from "../../../components/Code.astro"
|
||||
|
||||
import TwoColumn from "../../../components/TwoColumn.astro";
|
||||
import Code from "../../../components/Code.astro";
|
||||
|
||||
# `print`
|
||||
|
||||
@ -10,9 +11,11 @@ Prints to stdout.
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun print(String value) {}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Description
|
||||
|
||||
@ -20,10 +23,8 @@ Prints a single `String` into stdout. Doesn't return anything.
|
||||
|
||||
## Examples
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("Hello world!")
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -3,13 +3,13 @@ layout: "../_wrapper.astro"
|
||||
title: Comments
|
||||
order: 2
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Comments
|
||||
|
||||
THP supports single and multi line comments:
|
||||
|
||||
|
||||
## Single line
|
||||
|
||||
Begin with double slash `//` and continue until the end of the line.
|
||||
@ -21,33 +21,37 @@ print("hello!")
|
||||
print("the result is {5 + 5}") // This will print 10
|
||||
`} />
|
||||
|
||||
|
||||
## Multi line
|
||||
|
||||
These begin with `/*` and end with `*/`. Everything in between is ignored.
|
||||
|
||||
Multi line comments can be nested in THP.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
/*
|
||||
This is a
|
||||
multiline comment
|
||||
*/
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
/*
|
||||
Multiline comments
|
||||
can be /* nested */
|
||||
*/
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Documentation comments
|
||||
|
||||
Documentation comments use triple slashes `///`.
|
||||
These use [CommonMark Markdown](https://commonmark.org/) syntax.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
/// Transforms the format from A to B...
|
||||
///
|
||||
/// ## Errors
|
||||
@ -57,6 +61,5 @@ These use [CommonMark Markdown](https://commonmark.org/) syntax.
|
||||
fun transform() {
|
||||
// ...
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -3,7 +3,8 @@ layout: "../_wrapper.astro"
|
||||
title: Datatypes
|
||||
order: 4
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Datatypes
|
||||
|
||||
@ -28,50 +29,54 @@ Int char_code = 0b01000110
|
||||
// IMPORTANT!
|
||||
// Since Octal starts with \`0o\`, using just a leading 0
|
||||
// will result in a decimal!
|
||||
Int not_octal = 032 // This is 32, not 26
|
||||
Int not_octal = 032 // This is 32, not 26
|
||||
`} />
|
||||
|
||||
// TODO: Make it a compile error to have leading zeroes,
|
||||
and force users to use `0o` for octal
|
||||
|
||||
|
||||
## Float
|
||||
|
||||
Same as php float
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Float pi = 3.141592
|
||||
Float light = 2.99e+8
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## String
|
||||
|
||||
THP strings use **only** double quotes. Single quotes are
|
||||
used elsewhere.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
String name = "Rose"
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Strings have interpolation with `{}`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("Hello, {name}") // Hello, Rose
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Unlike PHP, THP strings are concatenated with `++`, not with `.`.
|
||||
This new operator implicitly converts any operator into a string.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val name = "John" ++ " " ++ "Doe"
|
||||
val greeting = "My name is " ++ name ++ " and I'm " ++ 32 ++ " years old"
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The plus operator `+` is reserved for numbers.
|
||||
|
||||
|
||||
## Bool
|
||||
|
||||
THP booleans are `true` and `false`. They are case sensitive,
|
||||
@ -84,4 +89,3 @@ Bool is_false = false
|
||||
// This is a compile error
|
||||
val invalid = TRUE
|
||||
`} />
|
||||
|
||||
|
@ -3,8 +3,9 @@ layout: "../_wrapper.astro"
|
||||
title: Hello world
|
||||
order: 1
|
||||
---
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro"
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Hello, world!
|
||||
|
||||
@ -18,13 +19,14 @@ detailed later on.
|
||||
|
||||
To write a hello world program write the following code in a file:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("Hello, world!")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Then run `thp hello.thp` from your terminal.
|
||||
|
||||
|
||||
## Instruction separation
|
||||
|
||||
THP uses whitespace to determine when a statement is over. In short,
|
||||
@ -36,11 +38,12 @@ echo("B");
|
||||
echo("C");
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print("A")
|
||||
print("B")
|
||||
print("C")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
As a consequence of this, there can only be 1 statement per line.
|
||||
|
||||
|
@ -3,11 +3,11 @@ layout: "../_wrapper.astro"
|
||||
title: Operators
|
||||
order: 5
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Operators
|
||||
|
||||
|
||||
Most of the PHP operators are present in THP.
|
||||
|
||||
## Numbers
|
||||
@ -17,13 +17,13 @@ var number = 322
|
||||
|
||||
number + 1
|
||||
number - 1
|
||||
number * 1
|
||||
number \* 1
|
||||
number / 1
|
||||
number % 2
|
||||
|
||||
number += 1
|
||||
number -= 1
|
||||
number *= 1
|
||||
number \*= 1
|
||||
number /= 1
|
||||
number %= 2
|
||||
`} />
|
||||
@ -36,7 +36,7 @@ use `+=` or `-=` instead.
|
||||
number += 1
|
||||
|
||||
// instead of
|
||||
number++ // This is a compile error
|
||||
number++ // This is a compile error
|
||||
`} />
|
||||
|
||||
### Comparison
|
||||
@ -44,62 +44,71 @@ number++ // This is a compile error
|
||||
These operators will not do implicit type conversion. They can
|
||||
only be used with same datatypes.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
v1 < v2
|
||||
v1 <= v2
|
||||
v1 > v2
|
||||
v1 >= v2
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
There is only `==` and `!=`. They are equivalent to `===` and `!==`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
v1 == v2
|
||||
v1 != v2
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
### Bitwise
|
||||
|
||||
TBD
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
number and 1
|
||||
number or 2
|
||||
number xor 1
|
||||
number xand 1
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Strings
|
||||
|
||||
Strings **do not use `.`** for concatenation. They use `++`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
"Hello " ++ "world."
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This new operator **implicitly converts** types to string
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
"Hello " ++ 322 // 322 will be converted to "322"
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Boolean
|
||||
|
||||
These operators work **only with booleans**, they do not perform
|
||||
type coercion.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
c1 && c2
|
||||
c1 || c2
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Ternary
|
||||
|
||||
There is no ternary operator. See [Conditionals](/learn/flow-control/conditionals) for alternatives.
|
||||
|
||||
|
||||
## Null
|
||||
|
||||
These are detailed in their section: [Nullable types](/learn/error-handling/null)
|
||||
@ -112,9 +121,6 @@ person?.name ?? "Jane"
|
||||
person?.greet?.()
|
||||
|
||||
if person? {
|
||||
person.name
|
||||
person.name
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -3,7 +3,8 @@ layout: "../_wrapper.astro"
|
||||
title: Variables
|
||||
order: 3
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Variables
|
||||
|
||||
@ -24,47 +25,59 @@ As a regex: `[a-z_][a-zA-Z0-9_]*`
|
||||
|
||||
Defined with `val`, followed by a variable name and a value.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
val surname = "Doe"
|
||||
val year_of_birth = 1984
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
It's a compile error to attempt to modify it
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
val surname = "Doe"
|
||||
surname = "Dane" // Error
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
### Datatype annotation
|
||||
|
||||
Written after the `val` keyword but before the variable name.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
val String surname = "Doe"
|
||||
val Int year_of_birth = 1984
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When annotating an immutable variable the `val` keyword is optional
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
// Equivalent to the previous code
|
||||
String surname = "Doe"
|
||||
Int year_of_birth = 1984
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This means that if a variable only has a datatype, it is immutable.
|
||||
|
||||
It is a compile error to declare a variable of a datatype,
|
||||
but use another.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
// Declare the variable as a String, but use a Float as its value
|
||||
String capital = 123.456
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Mutable variables
|
||||
|
||||
@ -81,18 +94,21 @@ age = 33
|
||||
|
||||
Written after the `var` keywords but before the variable name.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
var String name = "John"
|
||||
var Int age = 32
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When annotating a mutable variable the keyword `var` is still **required**.
|
||||
|
||||
<Code level={2} thpcode={`
|
||||
<Code
|
||||
level={2}
|
||||
thpcode={`
|
||||
// Equivalent to the previous code
|
||||
var String name = "John"
|
||||
var Int age = 32
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Arrays
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Arrays
|
||||
|
||||
@ -17,29 +18,25 @@ Arrays only store values of a single datatype.
|
||||
val fruits = ["apple", "banana", "cherry"]
|
||||
val apple = fruits[0]
|
||||
|
||||
print(apple) // apple
|
||||
print(apple) // apple
|
||||
|
||||
// To mutate an array, you need to declare it as var
|
||||
var numbers = [0, 1, 2, 3]
|
||||
|
||||
numbers[3] = 5
|
||||
|
||||
print(numbers[3]) // 5
|
||||
print(numbers[3]) // 5
|
||||
`} />
|
||||
|
||||
|
||||
## Type signature
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Array[String]
|
||||
Array[Int]
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The Array signature __requires__ the word `Array`.
|
||||
The Array signature **requires** the word `Array`.
|
||||
There is no `Int[]` or `[Int]` signature, since that would cause
|
||||
problems with the language's grammar.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Enums
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Enums
|
||||
|
||||
@ -33,7 +34,8 @@ val suit = Suit::Hearts
|
||||
Backed enums can have a scalar for each case. The scalar values can only be
|
||||
`String` or `Int`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
enum Suit(String)
|
||||
{
|
||||
Hearts = "H",
|
||||
@ -41,8 +43,7 @@ enum Suit(String)
|
||||
Clubs = "C",
|
||||
Spades = "S",
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
All cases must explicitly define their value, there is no automatic generation.
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Maps
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Maps
|
||||
|
||||
@ -25,57 +26,64 @@ map Person {
|
||||
|
||||
// Here we declare an instance of a Person.
|
||||
val john_doe = Person {
|
||||
name: "John",
|
||||
surname: "Doe",
|
||||
age: 33,
|
||||
name: "John",
|
||||
surname: "Doe",
|
||||
age: 33,
|
||||
}
|
||||
|
||||
// If the compiler can infer the type of a Map,
|
||||
// we can omit its type
|
||||
var Person mary_jane = .{
|
||||
name: "Mary",
|
||||
surname: "Jane",
|
||||
age: 27,
|
||||
name: "Mary",
|
||||
surname: "Jane",
|
||||
age: 27,
|
||||
}
|
||||
`} />
|
||||
|
||||
To access the fields of a map we use square braces `[]`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
mary_jane["age"] += 1
|
||||
print(mary_jane["name"]) // Mary
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Or dot access `.` if the field's name is a valid identifier.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
mary_jane.age += 1
|
||||
print(mary_jane.name)
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Anonymous maps
|
||||
|
||||
An anonymous map allows us to store and retrieve any key of any datatype.
|
||||
They are declared as `Map`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val car = Map {
|
||||
brand: "Toyota",
|
||||
model: "Corolla",
|
||||
year: 2012,
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Anonymous maps can also can have their type omitted.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
var car = .{
|
||||
brand: "Toyota",
|
||||
model: "Corolla",
|
||||
year: 2012,
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
If the compiler encounters a map without a type (that is, `.{}`)
|
||||
and doesn't expect a specific type, it will assume it is an
|
||||
@ -83,12 +91,14 @@ anonymous map.
|
||||
|
||||
We can freely assign fields to an anonymous map:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// Modify an existing field
|
||||
car["year"] = 2015
|
||||
// Create a new field
|
||||
car["status"] = "used"
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
However, if we try to access a field of an anonymous map we'll get
|
||||
a nullable type, and we must annotate it.
|
||||
@ -104,9 +114,11 @@ var car_status = car["status"]
|
||||
Instead, we can use the `get` function of the map, which expects a
|
||||
datatype and returns that type as nullable
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val car_status = car.get[String]("status")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Both ways to get a value will check that the key exists in the map,
|
||||
and that it has the correct datatype. If either the key doesn't exist
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Tuples
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Tuples
|
||||
|
||||
@ -16,13 +17,10 @@ val person = #("John", "Doe", 32)
|
||||
val #(name, surname, age) = person
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
## Signature
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
#(String, String, Int)
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Tagged unions
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Tagged unions
|
||||
|
||||
@ -16,14 +17,15 @@ union Shape
|
||||
Rectangle(Int, Int),
|
||||
}
|
||||
|
||||
val dot = Shape::Dot
|
||||
val square1 = Shape::Square(10)
|
||||
val dot = Shape::Dot
|
||||
val square1 = Shape::Square(10)
|
||||
val rectangle1 = Shape::Rectangle(5, 15)
|
||||
`} />
|
||||
|
||||
## Pattern matching
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
match shape_1
|
||||
case ::Square(side)
|
||||
{
|
||||
@ -33,7 +35,8 @@ case ::Rectangle(length, height)
|
||||
{
|
||||
print("Area of the rectangle: {length * height}")
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Internal representation
|
||||
|
||||
@ -42,7 +45,6 @@ When compiled down to PHP, tagged unions are a combination of an enum and an arr
|
||||
THP creates an enum of the same name and with the same cases, and the values
|
||||
are contained as part of an array.
|
||||
|
||||
|
||||
```php
|
||||
// The first snippet is compiled to:
|
||||
enum Shape
|
||||
@ -56,4 +58,3 @@ $dot = [Shape::Dot];
|
||||
$square1 = [Shape::Square, 10];
|
||||
$rectangle1 = [Shape::Rectangle, 5, 15]
|
||||
```
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Blocks
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Blocks
|
||||
|
||||
@ -17,8 +18,8 @@ val result = {
|
||||
val temp = 161
|
||||
|
||||
temp * 2 // This will be assigned to \`result\`
|
||||
|
||||
}
|
||||
|
||||
print(result) // 322
|
||||
print(result) // 322
|
||||
`} />
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Conditionals
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Conditionals
|
||||
|
||||
@ -12,7 +13,6 @@ import Code from "@/components/Code.astro"
|
||||
- Paretheses for the condition are not required.
|
||||
- There's no ternary operator
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
if condition {
|
||||
// code
|
||||
@ -24,22 +24,20 @@ else {
|
||||
// even more code
|
||||
}
|
||||
|
||||
|
||||
val result = if condition { value1 } else { value2 }
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
## Check for datatypes
|
||||
|
||||
TBD
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
if variable is Datatype {
|
||||
// code using variable
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## If variable is of enum
|
||||
|
||||
@ -49,13 +47,10 @@ TBD
|
||||
val user_id = POST::get("user_id")
|
||||
|
||||
if Some(user_id) = user_id {
|
||||
print("user_id exists: {user_id}")
|
||||
print("user_id exists: {user_id}")
|
||||
}
|
||||
|
||||
if Some(user_id) = user_id && user_id > 0 {
|
||||
print("user_id is greater than 0: {user_id}")
|
||||
print("user_id is greater than 0: {user_id}")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Loops
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Loops
|
||||
|
||||
@ -18,7 +19,7 @@ Braces are required.
|
||||
val numbers = [0, 1, 2, 3]
|
||||
|
||||
for number in numbers {
|
||||
print(number)
|
||||
print(number)
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -30,18 +31,17 @@ val dict = .{
|
||||
}
|
||||
|
||||
for value in dict {
|
||||
print("{value}")
|
||||
print("{value}")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
### Loop over keys and values
|
||||
|
||||
<Code thpcode={`
|
||||
val numbers = [0, 1, 2, 3]
|
||||
|
||||
for index, number in numbers {
|
||||
print("{index} : {number}")
|
||||
print("{index} : {number}")
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -53,11 +53,10 @@ val dict = .{
|
||||
}
|
||||
|
||||
for key, value in dict {
|
||||
print("{key} => {value}")
|
||||
print("{key} => {value}")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## While loop
|
||||
|
||||
<Code thpcode={`
|
||||
@ -65,25 +64,24 @@ val colors = ["red", "green", "blue"]
|
||||
var index = 0
|
||||
|
||||
while index < colors.size() {
|
||||
print("{colors[index]}")
|
||||
index += 1
|
||||
print("{colors[index]}")
|
||||
index += 1
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Labelled loops
|
||||
|
||||
TBD
|
||||
|
||||
You can give labels to loops, allowing you to `break` and `continue` in nested loops.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
:top for i in values_1 {
|
||||
for j in values_2 {
|
||||
// ...
|
||||
break :top
|
||||
}
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Match
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Match
|
||||
|
||||
@ -13,45 +14,42 @@ Braces are **required**.
|
||||
<Code thpcode={`
|
||||
val user_id = POST::get("user_id")
|
||||
|
||||
|
||||
match user_id
|
||||
case Some(id) { print("user_id exists: {id}") }
|
||||
case None { print("user_id doesn't exist") }
|
||||
|
||||
match user_id
|
||||
case Some(id) {
|
||||
print("user_id exists: {id}")
|
||||
print("user_id exists: {id}")
|
||||
}
|
||||
case None {
|
||||
print("user_id doesn't exist")
|
||||
print("user_id doesn't exist")
|
||||
}
|
||||
|
||||
|
||||
match user_id
|
||||
case Some(id) if id > 0 {
|
||||
print("user_id exists: {id}")
|
||||
print("user_id exists: {id}")
|
||||
}
|
||||
else {
|
||||
print("user_id has other values ")
|
||||
print("user_id has other values ")
|
||||
}
|
||||
|
||||
match customer_id
|
||||
case 1, 2, 3 {
|
||||
print("special discount for our first 3 customers!")
|
||||
print("special discount for our first 3 customers!")
|
||||
}
|
||||
else {
|
||||
print("hello dear")
|
||||
print("hello dear")
|
||||
}
|
||||
|
||||
match customer_id
|
||||
match customer*id
|
||||
| 1 | 2 | 3 {
|
||||
print("ehhhh")
|
||||
print("ehhhh")
|
||||
}
|
||||
| 4 | 5 {
|
||||
print("ohhh")
|
||||
print("ohhh")
|
||||
}
|
||||
| _ {
|
||||
print("???")
|
||||
| * {
|
||||
print("???")
|
||||
}
|
||||
`} />
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Pipes
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Pipes
|
||||
|
||||
@ -11,24 +12,30 @@ expression as input to another.
|
||||
|
||||
For example, instead of writing:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val result = third_function(second_function(first_function(my_value)))
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
You can use pipes:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val result = my_value
|
||||
|> first_function
|
||||
|> second_function
|
||||
|> third_function
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Or use it to group expressions:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
print <| (2 + 3 * 4) / 7
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
TBD: How to handle piping to functions with more than 1 param
|
||||
|
||||
@ -40,13 +47,11 @@ fun add_one(x: Int) -> Int {
|
||||
}
|
||||
|
||||
fun times_two(x: Int) -> Int {
|
||||
x * 2
|
||||
x \* 2
|
||||
}
|
||||
|
||||
// (Int) -> (Int)
|
||||
val plus_one_times_2 = add_one >> times_two
|
||||
|
||||
print(plus_one_times_2(5)) // 12
|
||||
print(plus_one_times_2(5)) // 12
|
||||
`} />
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Declaration
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Declaration
|
||||
|
||||
@ -10,7 +11,6 @@ Functions in THP have a different syntax than PHP.
|
||||
|
||||
Function names **must** begin with a lowercase letter.
|
||||
|
||||
|
||||
## Minimal function
|
||||
|
||||
The following code shows a function without parameters
|
||||
@ -27,14 +27,12 @@ say_hello()
|
||||
|
||||
Functions are called the same way as in PHP.
|
||||
|
||||
|
||||
## With return type
|
||||
|
||||
If your function return any value, annotating the return
|
||||
type is __mandatory__, and it's done by placing an
|
||||
type is **mandatory**, and it's done by placing an
|
||||
arrow `->` followed by the return datatype:
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun get_random_number() -> Int
|
||||
{
|
||||
@ -44,38 +42,38 @@ fun get_random_number() -> Int
|
||||
val number = get_random_number()
|
||||
`} />
|
||||
|
||||
|
||||
It's an error to return from a function that doesn't declare
|
||||
a return type:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun get_random_number()
|
||||
{
|
||||
// Error: the function does not define a return type
|
||||
return Random::get(0, 35_222)
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
You can omit the `return` keyword if it's placed on the last
|
||||
expression on the function:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun get_random_number() -> Int
|
||||
{
|
||||
// The last expression of a function is
|
||||
// automatically returned
|
||||
Random::get(0, 35_222)
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Function parameters
|
||||
|
||||
Parameters are declared like C-style languages:
|
||||
`Type name`, separated by commas.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun add(Int a, Int b) -> Int
|
||||
{
|
||||
@ -89,8 +87,6 @@ THP has different semantics on parameters concerning
|
||||
pass by value, pass by reference and copy on write.
|
||||
This is detailed in another chapter.
|
||||
|
||||
|
||||
|
||||
## Generic types
|
||||
|
||||
Functions can declare generic types by using the syntax
|
||||
@ -99,7 +95,6 @@ Functions can declare generic types by using the syntax
|
||||
The following example declares a generic `T` and uses it
|
||||
in the parameters and return type:
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun get_first_item[T](Array[T] array) -> T
|
||||
{
|
||||
@ -112,27 +107,24 @@ val first = get_first_item[Int](numbers)
|
||||
val first = get_first_item(numbers)
|
||||
`} />
|
||||
|
||||
|
||||
## Default arguments
|
||||
|
||||
TBD
|
||||
|
||||
|
||||
## Variadic arguments
|
||||
|
||||
TBD
|
||||
|
||||
|
||||
## Signature
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
() -> ()
|
||||
() -> (Int)
|
||||
(Int, Int) -> (Int)
|
||||
[T](Array[T]) -> (T)
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Named arguments
|
||||
|
||||
@ -165,8 +157,3 @@ fun greet(
|
||||
|
||||
greet("John", from: "LA")
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Higher Order Functions
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Higher Order functions
|
||||
|
||||
|
||||
## Function as parameters
|
||||
|
||||
<Code thpcode={`
|
||||
@ -28,10 +28,6 @@ fun generate_generator() -> () -> Int
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val generator = generate_generator() // A function
|
||||
val value = generate_generator()() // An Int
|
||||
val generator = generate_generator() // A function
|
||||
val value = generate_generator()() // An Int
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -2,40 +2,45 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Lambdas
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Lambdas / Anonymous functions
|
||||
|
||||
|
||||
## Anonymous function
|
||||
|
||||
An anonymous function is declared with `fn`.
|
||||
Other than not having a name, it can declare parameter
|
||||
and return types.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fn(Int x, Int y) -> Int {
|
||||
x + y
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Anonymous function can omit declaring those types as well:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
numbers.map(fun(x) {
|
||||
x * 2
|
||||
})
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Anonymous function short form
|
||||
|
||||
If you need an anonymous function that returns a single
|
||||
expression you can write it like this:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun(x, y) = x + y
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
It uses an equal `=` instead of an arrow. It can contain
|
||||
only a single expression.
|
||||
@ -43,56 +48,54 @@ only a single expression.
|
||||
This is common when declaring functions that immediately
|
||||
return a map.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fn(value) = .{
|
||||
value
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Closure types
|
||||
|
||||
By default closures **always** capture variables as **references**.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
var x = 20
|
||||
|
||||
val f = fun() {
|
||||
print(x)
|
||||
print(x)
|
||||
}
|
||||
|
||||
f() // 20
|
||||
f() // 20
|
||||
|
||||
x = 30
|
||||
f() // 30
|
||||
f() // 30
|
||||
`} />
|
||||
|
||||
|
||||
You can force a closure to capture variables by value.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun(parameters) clone(variables) {
|
||||
// code
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Code thpcode={`
|
||||
var x = 20
|
||||
|
||||
val f = fun() clone(x) {
|
||||
print(x)
|
||||
print(x)
|
||||
}
|
||||
|
||||
f() // 20
|
||||
f() // 20
|
||||
|
||||
x = 30
|
||||
f() // 20
|
||||
f() // 20
|
||||
`} />
|
||||
|
||||
|
||||
## Lambdas
|
||||
|
||||
Lambdas are a short form of anonymous functions. They are declared with `#{}`.
|
||||
@ -109,9 +112,6 @@ numbers.map() #{
|
||||
// the above lambda is equivalent to:
|
||||
|
||||
numbers.map(fun(param1) {
|
||||
$1 * 2
|
||||
$1 \* 2
|
||||
})
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
@ -2,18 +2,20 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Function parameters
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Function parameters
|
||||
|
||||
|
||||
## Immutable reference
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun add_25(Array[Int] numbers) {
|
||||
numbers.push(25) // Error: \`numbers\` is immutable
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When using a regular type as a parameter, only it's immutable
|
||||
properties can be used
|
||||
@ -23,19 +25,21 @@ fun count(Array[Int] numbers) -> Int {
|
||||
val items_count = numbers.size() // Ok, \`size\` is pure
|
||||
|
||||
items_count
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
To use immutable properties you must use a mutable reference.
|
||||
|
||||
|
||||
## Mutable reference
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun push_25(mut Array[Int] numbers) {
|
||||
numbers.push(25) // Ok, will also mutate the original array
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Placing a `mut` before the type makes the parameter a mutable
|
||||
reference. Mutable methods can be used, and the original
|
||||
@ -47,33 +51,30 @@ must use `mut`.
|
||||
<Code thpcode={`
|
||||
var numbers = Array(0, 1, 2, 3)
|
||||
|
||||
push_25(mut numbers) // Pass \`numbers\` as reference.
|
||||
push_25(mut numbers) // Pass \`numbers\` as reference.
|
||||
|
||||
print(numbers(4)) // \`Some(25)\`
|
||||
print(numbers(4)) // \`Some(25)\`
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
## Clone
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun add_25(clone Array[Int] numbers) {
|
||||
numbers.push(25) // Ok, the original array is unaffected
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Using the `clone` keyword before the type creates a mutable copy
|
||||
of the parameter (CoW). The original data will **not** be mutated.
|
||||
|
||||
The caller must also use `clone` when calling the function.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
val numbers = Array(1, 2, 3, 4)
|
||||
|
||||
add_25(clone numbers) // Pass \`numbers\` as clone.
|
||||
add_25(clone numbers) // Pass \`numbers\` as clone.
|
||||
|
||||
print(numbers(4)) // None
|
||||
print(numbers(4)) // None
|
||||
`} />
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Nullable types
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Nullable types
|
||||
|
||||
@ -13,9 +14,11 @@ by the question mark `?` character.
|
||||
For instance, a POST request may have a `username` parameter,
|
||||
or it may not. This can be represented with an `?String`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
?String new_username = POST::get("username")
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
When we have a `?Type` we cannot use it directly. We must first
|
||||
check if the value is null, and then use it.
|
||||
@ -31,7 +34,7 @@ if new_username?
|
||||
// you can also manually check for null
|
||||
if new_username == null
|
||||
{
|
||||
// This is the same as above
|
||||
// This is the same as above
|
||||
}
|
||||
|
||||
`} />
|
||||
@ -46,7 +49,7 @@ To create a nullable type we must explicitly annotate the type.
|
||||
<Code thpcode={`
|
||||
val favorite_color = null // Error, we must define the type
|
||||
|
||||
?String favorite_color = null // Ok
|
||||
?String favorite_color = null // Ok
|
||||
`} />
|
||||
|
||||
Other examples:
|
||||
@ -71,7 +74,6 @@ val name = person?.name
|
||||
- If `person` is null, `person?.name` will return `null`
|
||||
- If `person` is not null, `person?.name` will return `name`
|
||||
|
||||
|
||||
## Null unboxing
|
||||
|
||||
The `!!` operator transforms a `?Type` into `Type`.
|
||||
@ -90,16 +92,17 @@ String s = lastname!!
|
||||
|
||||
You can use it to chain access:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val children_lastname = person!!.child!!.lastname
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
However, if at runtime you use `!!` on a null value,
|
||||
the null value will be returned and your program will
|
||||
blow up later. So make sure to use this operator
|
||||
only when you are sure a value cannot be null.
|
||||
|
||||
|
||||
## Elvis operator
|
||||
|
||||
The Elvis operator `??` is used to give a default value in case a `null` is found.
|
||||
@ -114,13 +117,12 @@ val test_score = get_score() ?? 0
|
||||
For the above code:
|
||||
|
||||
- If `get_score()` is not null, `??` will return `get_score()`
|
||||
- If `get_score()` *is* null, `??` will return `0`
|
||||
- If `get_score()` _is_ null, `??` will return `0`
|
||||
|
||||
You can use the Elvis operator to return early
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val username = get_username() ?? return
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Try/Exceptions
|
||||
---
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro"
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Try/exceptions
|
||||
|
||||
@ -27,6 +28,7 @@ fun invert(Int number) -> DivisionByZero!Int
|
||||
}
|
||||
|
||||
return 1 / number
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -36,7 +38,6 @@ or an `Int`.
|
||||
|
||||
There is no `throw` keyword, errors are just returned.
|
||||
|
||||
|
||||
### Multiple error returns
|
||||
|
||||
TODO: properly define syntax, how this interacts with type unions.
|
||||
@ -44,12 +45,12 @@ TODO: properly define syntax, how this interacts with type unions.
|
||||
Multiple errors are chained with `!`. The last one is always
|
||||
the success value.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun sample() -> Error1!Error2!Error3!Int
|
||||
{ /* ... */}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Error handling
|
||||
|
||||
@ -110,17 +111,17 @@ Use a naked `try` when you want to rethrow an error, if there is any.
|
||||
unset " = = ="
|
||||
set " dangerous()" "Int 50"
|
||||
}
|
||||
`}
|
||||
></InteractiveCode>
|
||||
|
||||
`}
|
||||
|
||||
> </InteractiveCode>
|
||||
|
||||
In the previous example:
|
||||
|
||||
- If `dangerous()` returns an `Exception`, this exception
|
||||
will be returned by `run()`;
|
||||
will be returned by `run()`;
|
||||
- If `dangerous()` succeedes, its value is assigned
|
||||
to `result`, and the function continues executing.
|
||||
|
||||
to `result`, and the function continues executing.
|
||||
|
||||
### Try/return
|
||||
|
||||
@ -136,16 +137,16 @@ fun run() -> Int
|
||||
val result = try dangerous() return 0
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
In the previous example:
|
||||
|
||||
- If `dangerous()` fails, its error will be ignored, and `0` will
|
||||
be returned from `run()`.
|
||||
be returned from `run()`.
|
||||
- If `dangerous()` succeedes, its value will be assigned to `result`,
|
||||
and the function continues executing.
|
||||
|
||||
and the function continues executing.
|
||||
|
||||
### Try/else
|
||||
|
||||
@ -210,7 +211,9 @@ Try/else will assign a new value if an expression fails.
|
||||
}
|
||||
step{line 0}
|
||||
`}
|
||||
|
||||
>
|
||||
|
||||
</InteractiveCode>
|
||||
|
||||
- If `possible_value` is an error, the value `666` is used.
|
||||
@ -218,12 +221,10 @@ Try/else will assign a new value if an expression fails.
|
||||
|
||||
Either way, the function will continue executing.
|
||||
|
||||
|
||||
### Try/catch
|
||||
|
||||
Try/catch allows the error to be manually used & handled.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun run()
|
||||
{
|
||||
@ -239,12 +240,14 @@ fun run()
|
||||
// Return a new value to be assigned to \`result\`
|
||||
0
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
A try/catch may have many `catch` clauses:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
try dangerous()
|
||||
catch Exception1 e
|
||||
{...}
|
||||
@ -252,6 +255,5 @@ catch Exception2 e
|
||||
{...}
|
||||
catch Exception3 e
|
||||
{...}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,8 +2,7 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Abstract
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Abstract
|
||||
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Anonymous classes
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Anonymous classes
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
class Logger {
|
||||
pub fun log(String msg) {
|
||||
@ -19,16 +19,18 @@ setLogger(Logger())
|
||||
|
||||
// Using an anonymous class
|
||||
setLogger(class {
|
||||
pub fun log(String msg) {
|
||||
print(msg)
|
||||
}
|
||||
pub fun log(String msg) {
|
||||
print(msg)
|
||||
}
|
||||
})
|
||||
`} />
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
setLogger(class(Int param1) -> SomeClass(param1), SomeInterface {
|
||||
pub fun method() {
|
||||
// code
|
||||
}
|
||||
})
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Constructor/Destructor
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Constructor/Destructor
|
||||
|
||||
|
||||
## Constructor
|
||||
|
||||
The constructor syntax in THP is inspired by Kotlin.
|
||||
@ -37,7 +37,6 @@ If you want to declare a constructor as protected
|
||||
or private you need to add the `constructor`
|
||||
keyword, after the visibility modifier:
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
// Cow has a protected constructor
|
||||
class Cow
|
||||
@ -48,7 +47,6 @@ class Bat
|
||||
private constructor(Int height)
|
||||
`} />
|
||||
|
||||
|
||||
### Init block
|
||||
|
||||
The `init` block allow us to run code during the
|
||||
@ -62,6 +60,7 @@ class Dog(String name) {
|
||||
init {
|
||||
print("Dog created: {name}")
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -80,15 +79,13 @@ class Dog {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Constructor promotion
|
||||
|
||||
Constructor parameters can serve as class properties.
|
||||
This is done by adding a modifier and `var`/`val`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Parrot(
|
||||
// A public property
|
||||
pub val String name,
|
||||
@ -97,15 +94,14 @@ class Parrot(
|
||||
// A private property
|
||||
var String last_name,
|
||||
)
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
By using this syntax you are declaring properties and assigning them
|
||||
at the same time.
|
||||
|
||||
The contructor parameters can also have default values.
|
||||
|
||||
|
||||
### Derived properties
|
||||
|
||||
You can declare properties whose values depend on values
|
||||
@ -122,43 +118,40 @@ class Animal(
|
||||
}
|
||||
|
||||
val a2 = Animal("Doa")
|
||||
print(a2.name_length) //: 3
|
||||
print(a2.name_length) //: 3
|
||||
`} />
|
||||
|
||||
|
||||
### Constructor that may fail
|
||||
|
||||
A constructor may only fail if there is code
|
||||
that can fail on the `init` block.
|
||||
|
||||
|
||||
|
||||
TBD
|
||||
|
||||
Proposal 1:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class PrayingMantis(String name)
|
||||
throws Error {
|
||||
init -> Error! {
|
||||
// Initialization code that may fail
|
||||
}
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Destructor
|
||||
|
||||
The destructor in THP is the same as PHP.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Owl() {
|
||||
pub fun __destruct() {
|
||||
// Cleanup
|
||||
print("Destroying Owl...")
|
||||
}
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Basics
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Classes
|
||||
|
||||
@ -14,25 +15,27 @@ Classes in THP are significantly different than PHP.
|
||||
|
||||
A class is defined as follows:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Animal() {}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The name of the class **MUST** begin with an uppercase letter.
|
||||
|
||||
Classes have a parameter list even if they have no parameters
|
||||
for consistency sake.
|
||||
|
||||
|
||||
## Instanciation
|
||||
|
||||
To create an instance of a class call it as if it was a function,
|
||||
without `new`.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val animal = Animal()
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Properties
|
||||
|
||||
@ -50,6 +53,7 @@ class Person() {
|
||||
|
||||
// This is also okay
|
||||
String name = "Jane Doe"
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -62,11 +66,12 @@ class Person() {
|
||||
|
||||
// To make a property public use \`pub\`
|
||||
pub var Int age = 30
|
||||
|
||||
}
|
||||
|
||||
val p = Person()
|
||||
print(p.name) // Compile error: \`name\` is private
|
||||
print(p.age) // 30
|
||||
print(p.name) // Compile error: \`name\` is private
|
||||
print(p.age) // 30
|
||||
`} />
|
||||
|
||||
Unlike PHP, to access properties and methods use dot notation `.`
|
||||
@ -79,19 +84,19 @@ Readonly properties are explained in the Readonly page.
|
||||
The interaction between properties and the constructor is explained
|
||||
in the Constructor page.
|
||||
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
Methods are declared with `fun`, as regular functions.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Person() {
|
||||
fun greet() {
|
||||
print("Hello")
|
||||
}
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
**Methods are private by default**, and are made public with `pub`.
|
||||
|
||||
@ -106,11 +111,12 @@ class Person() {
|
||||
pub fun greet() {
|
||||
print("Hello from greet")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val p = Person()
|
||||
p.greet() //: Hello from greet
|
||||
p.private_greet() // Compile time error. Private method.
|
||||
p.greet() //: Hello from greet
|
||||
p.private_greet() // Compile time error. Private method.
|
||||
`} />
|
||||
|
||||
[Unlike PHP](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.properties-methods),
|
||||
@ -125,10 +131,10 @@ class Person() {
|
||||
fun name() -> String {
|
||||
"Rose"
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## This
|
||||
|
||||
THP uses the dollar sign `$` as this inside classes.
|
||||
@ -146,21 +152,23 @@ class Person() {
|
||||
val person_name = $get_name()
|
||||
print("Hello, I'm {person_name}")
|
||||
}
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Mutable methods
|
||||
|
||||
By default methods cannot mutate the state of the object.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Animal(var String name) {
|
||||
pub fun set_name(String new_name) {
|
||||
$name = new_name // Error: Cannot mutate $name
|
||||
}
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
To do so the method must be annotated. The caller must also
|
||||
declare a mutable variable.
|
||||
@ -175,4 +183,3 @@ class Animal(var String name) {
|
||||
var michi = Animal("Michifu")
|
||||
michi.set_name("Garfield")
|
||||
`} />
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Inheritance
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Inheritance
|
||||
|
||||
@ -28,12 +29,14 @@ Cat("Michi", 9).say_name()
|
||||
The call to the parent constructor is done right there, after
|
||||
the parent class name.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Dog(String name)
|
||||
extends Animal(name) {}
|
||||
// |----|
|
||||
// This is the call to the parent constructor
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
You must always call super, even if the parent doesn't
|
||||
define any parameters:
|
||||
@ -44,10 +47,3 @@ class Parent() {}
|
||||
class Child()
|
||||
extends Parent() {}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,12 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Interfaces
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Interfaces
|
||||
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
interface Serializable
|
||||
{
|
||||
@ -15,17 +14,14 @@ interface Serializable
|
||||
fun serialize() -> String
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Cat -> Serializable
|
||||
{
|
||||
pub fun Serializable() -> String
|
||||
{
|
||||
// code
|
||||
}
|
||||
pub fun Serializable() -> String
|
||||
{
|
||||
// code
|
||||
}
|
||||
}
|
||||
|
||||
`} />
|
||||
|
||||
No interface inheritance.
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Magic methods
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Magic methods
|
||||
|
||||
@ -12,15 +13,14 @@ Don't get special treatment.
|
||||
|
||||
class Cat
|
||||
{
|
||||
pub fun __sleep() -> Array[String]
|
||||
{
|
||||
// logic
|
||||
}
|
||||
pub fun \_\_sleep() -> Array[String]
|
||||
{
|
||||
// logic
|
||||
}
|
||||
}
|
||||
|
||||
`} />
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
val option = Some("GAAA")
|
||||
val Some(value) = option
|
||||
|
@ -2,14 +2,15 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Readonly
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Readonly
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
class Caño
|
||||
{
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Static
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Static in classes
|
||||
|
||||
|
||||
## Class constants
|
||||
|
||||
<Code thpcode={`
|
||||
@ -17,18 +17,16 @@ class Cat
|
||||
|
||||
static Cat
|
||||
{
|
||||
const CONSTANT = "constant value"
|
||||
const CONSTANT = "constant value"
|
||||
}
|
||||
|
||||
print(Cat::CONSTANT)
|
||||
`} />
|
||||
|
||||
|
||||
## Static methods
|
||||
|
||||
aka. plain, old functions
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
static Cat
|
||||
{
|
||||
@ -41,23 +39,17 @@ static Cat
|
||||
Cat::static_method()
|
||||
`} />
|
||||
|
||||
|
||||
## Static properties
|
||||
|
||||
aka. global variables
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
static Cat
|
||||
{
|
||||
pub var access_count = 0
|
||||
}
|
||||
|
||||
print(Cat::access_count) // 0
|
||||
print(Cat::access_count) // 0
|
||||
Cat::access_count += 1
|
||||
print(Cat::access_count) // 1
|
||||
print(Cat::access_count) // 1
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Visibility
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Visibility
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Components
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# Components
|
||||
|
||||
@ -14,14 +15,17 @@ Like React, a component is any function that returns `HTML`.
|
||||
will be uppercase or not.
|
||||
|
||||
For now they will be uppercase.
|
||||
|
||||
</Info>
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<p>Hello templates!</p>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Inside the HTML tags you can (mostly) write the HTML you already
|
||||
know.
|
||||
@ -37,6 +41,7 @@ fun MyComponent() -> HTML
|
||||
val name = "John"
|
||||
|
||||
<p>Hello {name}!</p>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -57,6 +62,7 @@ fun MyComponent() -> HTML
|
||||
{if is_vip {"Welcome"} else {"Hi"}}
|
||||
{name}!
|
||||
</p>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -74,8 +80,10 @@ fun MyComponent() -> HTML
|
||||
val user_input = "<b>BOLD</b>"
|
||||
|
||||
<p>answer: {user_input}</p>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
```html
|
||||
<p>answer: <b>BOLD</b></p>
|
||||
```
|
||||
@ -88,12 +96,15 @@ fun MyComponent() -> HTML
|
||||
val user_input = "<b>BOLD</b>"
|
||||
|
||||
<p>answer: <span raw-html={user_input}></span></p>
|
||||
|
||||
}
|
||||
`} />
|
||||
```html
|
||||
<p>answer: <span><b>BOLD</b></span></p>
|
||||
```
|
||||
|
||||
```html
|
||||
<p>
|
||||
answer: <span><b>BOLD</b></span>
|
||||
</p>
|
||||
```
|
||||
|
||||
## Dynamic attributes
|
||||
|
||||
@ -101,12 +112,14 @@ TODO: boolean attributes
|
||||
|
||||
Normal attributes (plain strings) work as you'd expect:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<button class="red">hello</button>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
```html
|
||||
<button class="red">hello</button>
|
||||
```
|
||||
@ -121,29 +134,30 @@ fun MyComponent() -> HTML
|
||||
|
||||
// Note the braces
|
||||
<button class={button_class}>hello</button>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
```html
|
||||
<button class="blue">hello</button>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Fragments
|
||||
|
||||
An HTML expression consist of a single tag that may have children inside.
|
||||
If you need to return multiple tags at once you can use fragments.
|
||||
|
||||
|
||||
The following code doesn't work as you would expect:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<p>hello</p> // This is an error, an ignored expression
|
||||
<p>world</p>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Each `<p>` is a single expression, they are not grouped.
|
||||
And since we cannot have unused expressions, the code will not compile.
|
||||
@ -151,7 +165,8 @@ And since we cannot have unused expressions, the code will not compile.
|
||||
To have these two `<p>` tags as a single expression use a fragment:
|
||||
`<></>`
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
// This is the root "element"
|
||||
@ -160,7 +175,8 @@ fun MyComponent() -> HTML
|
||||
<p>world</p>
|
||||
</>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
```html
|
||||
<p>hello</p>
|
||||
<p>world</p>
|
||||
@ -179,16 +195,14 @@ fun User() -> HTML
|
||||
|
||||
fun MyComponent() -> HTML
|
||||
{
|
||||
<>
|
||||
<p>status</p>
|
||||
<User /> // Here we are using the other component
|
||||
</>
|
||||
}
|
||||
`} />
|
||||
|
||||
<>
|
||||
<p>status</p>
|
||||
<User /> // Here we are using the other component
|
||||
</>
|
||||
} `} />
|
||||
|
||||
```html
|
||||
<p>status</p>
|
||||
<span>world</span>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Control flow
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# Control flow
|
||||
|
||||
@ -12,7 +13,8 @@ import Info from "@/components/docs/Info.astro"
|
||||
Use `@if`, `@else if` and `@else` to branch during the template
|
||||
creation.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun User(User user) -> HTML
|
||||
{
|
||||
<div>
|
||||
@ -26,8 +28,8 @@ fun User(User user) -> HTML
|
||||
}
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Loops
|
||||
|
||||
@ -44,15 +46,16 @@ fun Users() -> HTML
|
||||
<User user={user} />
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Match
|
||||
|
||||
Use `@match` for pattern matching:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun UserDetail(User user) -> HTML
|
||||
{
|
||||
<div>
|
||||
@ -67,8 +70,5 @@ fun UserDetail(User user) -> HTML
|
||||
}
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
|
||||
|
||||
`}
|
||||
/>
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Introduction
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# THP templating
|
||||
|
||||
@ -44,13 +45,13 @@ function render_button(string $name) {
|
||||
This approach has many problems:
|
||||
|
||||
- You have to create a new file for every new component,
|
||||
polluting the file system and the project. This may be significant
|
||||
for some web hosting providers that establish a fixed inode limit.
|
||||
polluting the file system and the project. This may be significant
|
||||
for some web hosting providers that establish a fixed inode limit.
|
||||
- Data is passed dinamically, via strings. If either the render
|
||||
function or the template change, the component will behave
|
||||
incorrectly without any warning.
|
||||
function or the template change, the component will behave
|
||||
incorrectly without any warning.
|
||||
- It's hard to include components inside components. Every new
|
||||
one requires a change in 2 files.
|
||||
one requires a change in 2 files.
|
||||
|
||||
Maybe for these (and other) reasons components are not used with
|
||||
templating libraries. Instead people use sections, layouts, etc.
|
||||
@ -62,18 +63,19 @@ and compose them.
|
||||
|
||||
The following would be the equivalent in THP:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Button(String name) -> HTML {
|
||||
<button class="some tailwind classes">
|
||||
Hello {name}!
|
||||
</button>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
It is very similar to React. The HTML is inside the THP code, not the other
|
||||
way around, so you can have arbitrary logic in the component.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
fun User(String name) -> HTML {
|
||||
// Get info from the database
|
||||
@ -92,15 +94,15 @@ fun User(String name) -> HTML {
|
||||
<TransactionItem t={t} />
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
fun TransactionItem(Transaction t) -> HTML {
|
||||
<li>
|
||||
{t.date} - {t.name} ({t.price})
|
||||
</li>
|
||||
}
|
||||
`} />
|
||||
|
||||
<li>
|
||||
{t.date} - {t.name} ({t.price})
|
||||
</li>
|
||||
} `} />
|
||||
|
||||
## Is this a JavaScript Front-End Framework?
|
||||
|
||||
@ -114,19 +116,14 @@ If you need reactivity on the front-end and want to use this templating
|
||||
take a look at [htmx](https://htmx.org/), [Alpine.js](https://alpinejs.dev/)
|
||||
and [hyperscript](https://hyperscript.org/).
|
||||
|
||||
|
||||
|
||||
## Styling
|
||||
|
||||
We don't provide any syntax or facility for styling.
|
||||
However, this component model is good to use with TailwindCSS.
|
||||
|
||||
|
||||
## Conversion
|
||||
|
||||
<Info>
|
||||
TBD: This is a draft, subject to change.
|
||||
</Info>
|
||||
<Info>TBD: This is a draft, subject to change.</Info>
|
||||
|
||||
HTML expressions will be compiled to plain strings, and
|
||||
those will be then composed.
|
||||
@ -145,6 +142,7 @@ fun Sample(String name) -> HTML {
|
||||
Not logged in
|
||||
}
|
||||
</button>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
@ -163,6 +161,3 @@ function Sample(name) {
|
||||
return "<button>{$__expr_1}</button>"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Props
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
import Info from "@/components/docs/Info.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
import Info from "@/components/docs/Info.astro";
|
||||
|
||||
# Props
|
||||
|
||||
@ -15,23 +16,27 @@ and they are defined as normal parameters.
|
||||
For example, to receive a `String` declare it as a
|
||||
parameter:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Greeter(String name) -> HTML
|
||||
{
|
||||
<p>Hello {name}!</p>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
And to send its value type the parameter name as an attribute:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
<Greeter name="Rose" />
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```html
|
||||
<div><p>Hello Rose</p></div>
|
||||
@ -39,18 +44,21 @@ fun Home() -> HTML
|
||||
|
||||
You can have as many props as you'd like, of any datatype:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Greeter(String name, Int age, Array[String] friends) -> HTML
|
||||
{
|
||||
// ...
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
## Static props
|
||||
|
||||
If the prop has a type `String` you can use a normal attribute.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
@ -58,8 +66,8 @@ fun Home() -> HTML
|
||||
<Greeter name="Rose" />
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Dynamic props
|
||||
|
||||
@ -75,21 +83,22 @@ fun Sample(Cat cat) -> HTML
|
||||
|
||||
fun Home() -> HTML
|
||||
{
|
||||
val my_cat = Cat("Michifu")
|
||||
val my_cat = Cat("Michifu")
|
||||
|
||||
<div>
|
||||
<Sample cat={my_cat} />
|
||||
</div>
|
||||
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
## Components as props
|
||||
|
||||
If for some reason you want to use a component as a prop
|
||||
use the `HTML` datatype:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// The parameter can have any name, not only \`child\`
|
||||
fun Sample(HTML child) -> HTML
|
||||
{
|
||||
@ -98,24 +107,27 @@ fun Sample(HTML child) -> HTML
|
||||
{child}
|
||||
</>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This, however, means that your prop component must be declared
|
||||
as an attribute:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
<Sample child={<span>I am the child</span>} />
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```html
|
||||
<div>
|
||||
<p>Sup</p>
|
||||
<span>I am the child</span>
|
||||
<p>Sup</p>
|
||||
<span>I am the child</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
@ -136,21 +148,16 @@ fun MyButton() -> HTML
|
||||
|
||||
fun Home() -> HTML
|
||||
{
|
||||
<div>
|
||||
<MyButton>
|
||||
buy <b>now!</b>
|
||||
</MyButton>
|
||||
</div>
|
||||
}
|
||||
`} />
|
||||
|
||||
<div>
|
||||
<MyButton>
|
||||
buy <b>now!</b>
|
||||
</MyButton>
|
||||
</div>
|
||||
} `} />
|
||||
|
||||
```html
|
||||
<div>
|
||||
<button>
|
||||
buy <b>now!</b>
|
||||
</button>
|
||||
<button>buy <b>now!</b></button>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,17 +4,21 @@ import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
|
||||
const { frontmatter, headings } = Astro.props;
|
||||
// Get all the posts from this dir
|
||||
|
||||
const posts = await Astro.glob("./**/*.{md,mdx}") as unknown as Array<AstroFile>;
|
||||
const posts = (await Astro.glob(
|
||||
"./**/*.{md,mdx}",
|
||||
)) as unknown as Array<AstroFile>;
|
||||
|
||||
// The base of every URL under this glob
|
||||
const base_url = "/en/latest/learn"
|
||||
const base_url = "/en/latest/learn";
|
||||
const version = "latest";
|
||||
---
|
||||
|
||||
<NewDocsLayout
|
||||
base_url={base_url}
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
base_url={base_url}
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
version={version}
|
||||
>
|
||||
<slot />
|
||||
</NewPagesLayout>
|
||||
<slot />
|
||||
</NewDocsLayout>
|
||||
|
@ -3,9 +3,9 @@ layout: "./_wrapper.astro"
|
||||
title: Welcome
|
||||
order: 1
|
||||
---
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Welcome
|
||||
|
||||
@ -15,11 +15,9 @@ THP is a new programming language that compiles to PHP.
|
||||
|
||||
![Accurate visual description of THP](/img/desc_thp.jpg)
|
||||
|
||||
|
||||
This page details the main design desitions of the language,
|
||||
if you want to install THP go to the [installation guide](install)
|
||||
|
||||
|
||||
## Why?
|
||||
|
||||
PHP is an old language. It has been growing since 1995, adopting a
|
||||
@ -71,10 +69,6 @@ essential today: A good, unified LSP, type definitions,
|
||||
accesible documentation, an opinionated code formatter
|
||||
and plugins for major editors like VSCode and Neovim.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Goals
|
||||
|
||||
- Bring static typing to PHP: Generics, type checks at compile and runtime, etc.
|
||||
@ -93,7 +87,6 @@ and plugins for major editors like VSCode and Neovim.
|
||||
|
||||
![Friendship ended with Rust, now Zig is my best friend.](/img/mudasir.jpg)
|
||||
|
||||
|
||||
## Not goals
|
||||
|
||||
These are **not** things that THP wants to solve or implement
|
||||
@ -104,7 +97,6 @@ These are **not** things that THP wants to solve or implement
|
||||
THP **_intentionally_** uses a different syntax from PHP to signal
|
||||
that it is a different language, and has different semantics.
|
||||
|
||||
|
||||
## Some differences with PHP
|
||||
|
||||
```php
|
||||
@ -113,11 +105,13 @@ $has_key = str_contains($haystack, 'needle');
|
||||
print("has key? " . $has_key);
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
val has_key = haystack.contains("needle")
|
||||
print("has key? " + has_key)
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Explicit variable declaration
|
||||
- No `$` for variable names (and thus no `$$variable`, use a map instead)
|
||||
@ -126,9 +120,8 @@ print("has key? " + has_key)
|
||||
- Strings use only double quotes
|
||||
- String concatenation with `+`
|
||||
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
```php
|
||||
// PHP
|
||||
@ -139,20 +132,22 @@ $obj = [
|
||||
]
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
val obj = .{
|
||||
names: #("Toni", "Stark"), // Tuple
|
||||
age: 33,
|
||||
numbers: [32, 64, 128]
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Tuples, Arrays, Sets, Maps are clearly different
|
||||
- JSON-like object syntax
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
```php
|
||||
// PHP
|
||||
@ -160,18 +155,19 @@ $cat = new Cat("Michifu", 7);
|
||||
$cat->meow();
|
||||
```
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
val cat = Cat("Michifu", 7)
|
||||
cat.meow()
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Instantiate classes without `new`
|
||||
- Use dot `.` instead of arrow `->` syntax
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
```php
|
||||
// PHP
|
||||
@ -179,34 +175,34 @@ use \Some\Deeply\Nested\Class
|
||||
use \Some\Deeply\Nested\Interface
|
||||
```
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// THP
|
||||
use Some::Deeply::Nested::{Class, Interface}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Different module syntax
|
||||
- Explicit module declaration
|
||||
- PSR-4 required
|
||||
- No `include`, `include_once`, `require` or `require_once`
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Other things:
|
||||
|
||||
- Pattern matching
|
||||
- ADTs
|
||||
|
||||
|
||||
## Runtime changes
|
||||
|
||||
Where possible THP will compile to available PHP functions/classes/methods/etc.
|
||||
|
||||
For example:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// This expression
|
||||
val greeting =
|
||||
match get_person()
|
||||
@ -222,7 +218,8 @@ val greeting =
|
||||
{
|
||||
"Nobody is here"
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
```php
|
||||
// Would compile to:
|
||||
@ -243,5 +240,3 @@ else {
|
||||
|
||||
However, more advanced datatypes & helper functions will require a sort of
|
||||
runtime (new classes/functions/etc) or abuse the language's syntax/semantics.
|
||||
|
||||
|
||||
|
24
src/pages/en/v0.0.1/learn/_wrapper.astro
Normal file
24
src/pages/en/v0.0.1/learn/_wrapper.astro
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
|
||||
|
||||
const { frontmatter, headings } = Astro.props;
|
||||
// Get all the posts from this dir
|
||||
|
||||
const posts = (await Astro.glob(
|
||||
"./**/*.{md,mdx}",
|
||||
)) as unknown as Array<AstroFile>;
|
||||
|
||||
// The base of every URL under this glob
|
||||
const base_url = "/en/v0.0.1/learn";
|
||||
const version = "v0.0.1";
|
||||
---
|
||||
|
||||
<NewDocsLayout
|
||||
base_url={base_url}
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
version={version}
|
||||
>
|
||||
<slot />
|
||||
</NewDocsLayout>
|
12
src/pages/en/v0.0.1/learn/index.mdx
Normal file
12
src/pages/en/v0.0.1/learn/index.mdx
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
layout: "./_wrapper.astro"
|
||||
title: Welcome
|
||||
order: 1
|
||||
---
|
||||
|
||||
import InteractiveCode from "@/components/InteractiveCode.astro";
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# THP :D
|
||||
|
||||
Version 0.0.1
|
@ -22,111 +22,107 @@ case Cat(name, lives) {
|
||||
}`;
|
||||
|
||||
const [thp_html] = await native_highlighter(
|
||||
leftTrimDedent(thpcode).join("\n"),
|
||||
HighlightLevel.Lexic,
|
||||
leftTrimDedent(thpcode).join("\n"),
|
||||
HighlightLevel.Lexic,
|
||||
);
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<Navbar showSidebarButton={false} />
|
||||
<Navbar showSidebarButton={false} />
|
||||
|
||||
<div
|
||||
class="container mx-auto lg:py-16 pb-2 pt-20 lg:grid 2xl:grid-cols-[auto_32rem] lg:grid-cols-[auto_36rem] gap-4 lg:px-10 px-2 min-h-[85vh]"
|
||||
>
|
||||
<div class="lg:pl-10 table">
|
||||
<div class="table-cell align-middle">
|
||||
<h1
|
||||
class="font-display font-bold text-c-text-2 lg:text-5xl text-3xl lg:text-left text-center leading-tight"
|
||||
>
|
||||
A modern, type safe,
|
||||
<br class="hidden lg:inline-block" />
|
||||
secure language
|
||||
<br class="hidden lg:inline-block" />
|
||||
compiled to PHP
|
||||
</h1>
|
||||
<p class="text-c-text text-lg pt-6 lg:pr-12">
|
||||
Inspired by Rust, Zig and Kotlin, THP has a modern syntax,
|
||||
semantics, type system and stdlib.
|
||||
</p>
|
||||
<div class="text-center pb-4">
|
||||
<a
|
||||
class="inline-block font-display text-lg rounded-full border-2 border-pink-400 py-2 px-8
|
||||
<div
|
||||
class="container mx-auto lg:py-16 pb-2 pt-20 lg:grid 2xl:grid-cols-[auto_32rem] lg:grid-cols-[auto_36rem] gap-4 lg:px-10 px-2 min-h-[85vh]"
|
||||
>
|
||||
<div class="lg:pl-10 table">
|
||||
<div class="table-cell align-middle">
|
||||
<h1
|
||||
class="font-display font-bold text-c-text-2 lg:text-5xl text-3xl lg:text-left text-center leading-tight"
|
||||
>
|
||||
A modern, type safe,
|
||||
<br class="hidden lg:inline-block" />
|
||||
secure language
|
||||
<br class="hidden lg:inline-block" />
|
||||
compiled to PHP
|
||||
</h1>
|
||||
<p class="text-c-text text-lg pt-6 lg:pr-12">
|
||||
Inspired by Rust, Zig and Kotlin, THP has a modern syntax, semantics,
|
||||
type system and stdlib.
|
||||
</p>
|
||||
<div class="text-center pb-4">
|
||||
<a
|
||||
class="inline-block font-display text-lg rounded-full border-2 border-pink-400 py-2 px-8
|
||||
transition-colors hover:text-black hover:bg-pink-400 mt-2"
|
||||
href="/en/latest/learn/"
|
||||
>
|
||||
Learn
|
||||
</a>
|
||||
href="/en/latest/learn/"
|
||||
>
|
||||
Learn
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="inline-block font-display text-lg border-2 border-sky-400 py-2 px-8 mx-6 rounded-full
|
||||
<a
|
||||
class="inline-block font-display text-lg border-2 border-sky-400 py-2 px-8 mx-6 rounded-full
|
||||
transition-colors hover:text-black hover:bg-sky-400 mt-2 opacity-50 cursor-not-allowed"
|
||||
>
|
||||
Install
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
Install
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex h-full justify-center items-center">
|
||||
<div
|
||||
class="bg-[var(--code-theme-bg-color)] lg:p-6 p-2 rounded-lg w-full max-w-[30rem] relative"
|
||||
>
|
||||
<span
|
||||
class="absolute lg:inline-block hidden h-[35rem] w-[35rem] -z-10 top-1/2 left-1/2 rounded-full
|
||||
<div>
|
||||
<div class="flex h-full justify-center items-center">
|
||||
<div
|
||||
class="bg-[var(--code-theme-bg-color)] lg:p-6 p-2 rounded-lg w-full max-w-[30rem] relative"
|
||||
>
|
||||
<span
|
||||
class="absolute lg:inline-block hidden h-[35rem] w-[35rem] -z-10 top-1/2 left-1/2 rounded-full
|
||||
transform -translate-x-1/2 -translate-y-1/2"
|
||||
style="background-image: conic-gradient(from 180deg at 50% 50%,#5BCEFA 0deg,#a853ba 180deg,#F5A9B8 1turn);
|
||||
style="background-image: conic-gradient(from 180deg at 50% 50%,#5BCEFA 0deg,#a853ba 180deg,#F5A9B8 1turn);
|
||||
filter: blur(75px); opacity: 0.75;"
|
||||
>
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="54"
|
||||
height="14"
|
||||
viewBox="0 0 54 14"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
transform="translate(1 1)"
|
||||
>
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
fill="#FF5F56"
|
||||
stroke="#E0443E"
|
||||
stroke-width=".5"></circle>
|
||||
<circle
|
||||
cx="26"
|
||||
cy="6"
|
||||
r="6"
|
||||
fill="#FFBD2E"
|
||||
stroke="#DEA123"
|
||||
stroke-width=".5"></circle>
|
||||
<circle
|
||||
cx="46"
|
||||
cy="6"
|
||||
r="6"
|
||||
fill="#27C93F"
|
||||
stroke="#1AAB29"
|
||||
stroke-width=".5"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="h-1"></div>
|
||||
<div id="editor" class="font-mono language-thp">
|
||||
<pre set:html={thp_html} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="54"
|
||||
height="14"
|
||||
viewBox="0 0 54 14"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd" transform="translate(1 1)">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
fill="#FF5F56"
|
||||
stroke="#E0443E"
|
||||
stroke-width=".5"></circle>
|
||||
<circle
|
||||
cx="26"
|
||||
cy="6"
|
||||
r="6"
|
||||
fill="#FFBD2E"
|
||||
stroke="#DEA123"
|
||||
stroke-width=".5"></circle>
|
||||
<circle
|
||||
cx="46"
|
||||
cy="6"
|
||||
r="6"
|
||||
fill="#27C93F"
|
||||
stroke="#1AAB29"
|
||||
stroke-width=".5"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="h-1"></div>
|
||||
<div id="editor" class="font-mono language-thp">
|
||||
<pre set:html={thp_html} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-8 md:hidden"></div>
|
||||
<div class="h-8 md:hidden"></div>
|
||||
|
||||
<HeroSection
|
||||
title="Generics & Types"
|
||||
thpcode={`
|
||||
<HeroSection
|
||||
title="Generics & Types"
|
||||
thpcode={`
|
||||
Array[Int] numbers = [0, 1, 2, 3, 4, 5]
|
||||
|
||||
numbers.push("7") // Compile error: expected an Int, found a String
|
||||
@ -143,17 +139,17 @@ const [thp_html] = await native_highlighter(
|
||||
// Ok
|
||||
val result = mock() as String
|
||||
`}
|
||||
>
|
||||
Type safety is enforced at compile time. Everything
|
||||
has a specific type. There is no `mixed`.
|
||||
<br>
|
||||
<br>
|
||||
You can use generics where neccesary.
|
||||
</HeroSection>
|
||||
>
|
||||
Type safety is enforced at compile time. Everything has a specific type.
|
||||
There is no `mixed`.
|
||||
<br />
|
||||
<br />
|
||||
You can use generics where neccesary.
|
||||
</HeroSection>
|
||||
|
||||
<HeroSection
|
||||
title="Tagged unions"
|
||||
thpcode={`
|
||||
<HeroSection
|
||||
title="Tagged unions"
|
||||
thpcode={`
|
||||
union DirEntry {
|
||||
File(String),
|
||||
Dir(String),
|
||||
@ -162,17 +158,17 @@ const [thp_html] = await native_highlighter(
|
||||
val root = DirEntry::Dir("/")
|
||||
val test_file = DirEntry::File("test.txt")
|
||||
`}
|
||||
>
|
||||
Make invalid state irrepresentable.
|
||||
<br />
|
||||
Model data in a type-safe way.
|
||||
<br />
|
||||
Ensure all cases are handled.
|
||||
</HeroSection>
|
||||
>
|
||||
Make invalid state irrepresentable.
|
||||
<br />
|
||||
Model data in a type-safe way.
|
||||
<br />
|
||||
Ensure all cases are handled.
|
||||
</HeroSection>
|
||||
|
||||
<HeroSection
|
||||
title="Pattern matching"
|
||||
thpcode={`
|
||||
<HeroSection
|
||||
title="Pattern matching"
|
||||
thpcode={`
|
||||
match test_file
|
||||
case DirEntry::File(filename)
|
||||
if !filename.starts_with(".") {
|
||||
@ -182,15 +178,15 @@ const [thp_html] = await native_highlighter(
|
||||
print("A valid file was not found")
|
||||
}
|
||||
`}
|
||||
>
|
||||
Match on values, tuples, enums, unions, types etc.
|
||||
<br />
|
||||
Guards available!
|
||||
</HeroSection>
|
||||
>
|
||||
Match on values, tuples, enums, unions, types etc.
|
||||
<br />
|
||||
Guards available!
|
||||
</HeroSection>
|
||||
|
||||
<HeroSection
|
||||
title="Null safety"
|
||||
thpcode={`
|
||||
<HeroSection
|
||||
title="Null safety"
|
||||
thpcode={`
|
||||
String? username = Post::get("username")
|
||||
|
||||
if username? {
|
||||
@ -198,16 +194,16 @@ const [thp_html] = await native_highlighter(
|
||||
print("Hello, {username}")
|
||||
}
|
||||
`}
|
||||
>
|
||||
Nulls are explicit and require handling. Types can be nullable,
|
||||
and they must be checked before usage.
|
||||
<br>
|
||||
The stdlib makes extensive use of them.
|
||||
</HeroSection>
|
||||
>
|
||||
Nulls are explicit and require handling. Types can be nullable, and they
|
||||
must be checked before usage.
|
||||
<br />
|
||||
The stdlib makes extensive use of them.
|
||||
</HeroSection>
|
||||
|
||||
<HeroSection
|
||||
title="Errors as values"
|
||||
thpcode={`
|
||||
<HeroSection
|
||||
title="Errors as values"
|
||||
thpcode={`
|
||||
val user_id = POST::get("id")
|
||||
val user = try User::find(user_id)
|
||||
catch DBException e {
|
||||
@ -215,12 +211,10 @@ const [thp_html] = await native_highlighter(
|
||||
return page(.{}, 404)
|
||||
}
|
||||
`}
|
||||
>
|
||||
Exceptions are values and don't disrupt
|
||||
control flow.
|
||||
<br>
|
||||
<br>
|
||||
Errors cannot be ignored, and we have
|
||||
syntax sugar to ease them.
|
||||
</HeroSection>
|
||||
>
|
||||
Exceptions are values and don't disrupt control flow.
|
||||
<br />
|
||||
<br />
|
||||
Errors cannot be ignored, and we have syntax sugar to ease them.
|
||||
</HeroSection>
|
||||
</BaseLayout>
|
||||
|
@ -4,18 +4,19 @@ import NewDocsLayout, { type AstroFile } from "@/layouts/NewDocsLayout.astro";
|
||||
const { frontmatter, headings } = Astro.props;
|
||||
// Get all the posts from this dir
|
||||
|
||||
const posts = await Astro.glob("./**/*.{md,mdx}") as unknown as Array<AstroFile>;
|
||||
const posts = (await Astro.glob(
|
||||
"./**/*.{md,mdx}",
|
||||
)) as unknown as Array<AstroFile>;
|
||||
|
||||
// The base of every URL under this glob
|
||||
const base_url = "/spec"
|
||||
const base_url = "/spec";
|
||||
---
|
||||
|
||||
<NewDocsLayout
|
||||
base_url={base_url}
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
base_url={base_url}
|
||||
frontmatter={frontmatter}
|
||||
headings={headings}
|
||||
posts={posts}
|
||||
>
|
||||
<slot />
|
||||
</NewPagesLayout>
|
||||
|
||||
<slot />
|
||||
</NewDocsLayout>
|
||||
|
@ -69,7 +69,6 @@ BlockMember = Statement
|
||||
| Expression
|
||||
```
|
||||
|
||||
|
||||
## Assignment
|
||||
|
||||
The target of an assignment can only be an identifier for now.
|
||||
@ -87,5 +86,3 @@ AssignmentOperator = "="
|
||||
| "/="
|
||||
| "%="
|
||||
```
|
||||
|
||||
|
||||
|
@ -7,15 +7,13 @@ title: Expression
|
||||
|
||||
The expression parser effectively implements a precedence table.
|
||||
|
||||
| Operator | Precedence |
|
||||
|------------|------------|
|
||||
| == != | 5 |
|
||||
| > >= < <= | 4 |
|
||||
| - + ++ | 3 |
|
||||
| . ?. !. | 2 |
|
||||
| / * % | 1 |
|
||||
|
||||
|
||||
| Operator | Precedence |
|
||||
| --------- | ---------- |
|
||||
| == != | 5 |
|
||||
| > >= < <= | 4 |
|
||||
| - + ++ | 3 |
|
||||
| . ?. !. | 2 |
|
||||
| / \* % | 1 |
|
||||
|
||||
```ebnf
|
||||
Expression = Equality
|
||||
@ -29,7 +27,6 @@ Unary = ("!" | "-"), Expression
|
||||
| CallExpression
|
||||
```
|
||||
|
||||
|
||||
## CallExpression
|
||||
|
||||
It's so hard to properly name these constructions.
|
||||
@ -40,6 +37,3 @@ CallExpression = primary, "(", (arguments list)?, ")"
|
||||
| primary, "[", (expression, (comma, expression)*, comma?)? "]"
|
||||
| primary
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
layout: "./_wrapper.astro"
|
||||
title: Welcome
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# The THP Language Specification
|
||||
|
||||
@ -42,14 +42,11 @@ The compiler consists of 5 common phases:
|
||||
- **IR**: Transforms the THP AST into a PHP AST
|
||||
- **Codegen**: Generates PHP source code from the PHP AST
|
||||
|
||||
|
||||
|
||||
## Source Code representation
|
||||
|
||||
Source code is encoded in UTF-8, and a single UTF-8 codepoint is
|
||||
a single character.
|
||||
|
||||
|
||||
## Basic characters
|
||||
|
||||
Although the source code must be encoded in UTF-8, most of the actual
|
||||
@ -68,7 +65,6 @@ lowercase_letter = "a".."z"
|
||||
uppercase_letter = "A".."Z"
|
||||
```
|
||||
|
||||
|
||||
## Whitespace & Automatic semicolon insertion
|
||||
|
||||
This section is being reworked on the Zig rewrite of the compiler.
|
||||
@ -85,10 +81,12 @@ parenthesis, square brackets, etc.
|
||||
Other statements require a explicit terminator. For example,
|
||||
the assignment statement:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val computation = 123 + 456 // how to detect if the statement ends here
|
||||
* 789 // or extends up to here?
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
In other languages a semicolon would be used to signal the end of the
|
||||
statement:
|
||||
@ -106,21 +104,25 @@ to the rule:
|
||||
No matter the indentation, whitespace or others, every statement ends
|
||||
with a newline.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val compute = 1 + 2 * 3 / 4
|
||||
// statement ends here ↑
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
As mentioned before, this does not affect statements that have clear delimiters.
|
||||
For example, the following code will work as expected:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val compute = my_function(
|
||||
param1,
|
||||
param2,
|
||||
) / 64
|
||||
// ↑ statement ends here
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
In a way, the parenthesis will "disable" the rule.
|
||||
|
||||
@ -133,11 +135,13 @@ continues.
|
||||
|
||||
For example:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val computation = 123 + 456
|
||||
* 789
|
||||
// ↑ statement ends here, and there is a single statement
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
This is so no matter the indentation:
|
||||
|
||||
@ -145,25 +149,26 @@ This is so no matter the indentation:
|
||||
// weird indentation:
|
||||
|
||||
val computation = 123 + 456
|
||||
* 789
|
||||
// ↑ statement still ends here
|
||||
`} />
|
||||
|
||||
- 789
|
||||
// ↑ statement still ends here
|
||||
`} />
|
||||
|
||||
What is important is that an operator begins the new line.
|
||||
If the operator is left on the previous line, this will not work:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// statement ends here ↓, and now there is a syntax error (dangling operator)
|
||||
val computation = 123 + 456 *
|
||||
789
|
||||
// ↑ this is a different statement
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
For this the parser must do look-ahead of 1 token. This is the only place the parser
|
||||
does so.
|
||||
|
||||
|
||||
|
||||
## Old Whitespace rules
|
||||
|
||||
THP is partially whitespace sensitive. It uses the following tokens: Indent, Dedent & NewLine
|
||||
@ -174,32 +179,33 @@ compares the previous indentation to the new one. If the amount of whitespace is
|
||||
greater than before, it emits a Indent token. If it's lower, emits a Dedent token, and
|
||||
if it's the same it does nothing.
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
1 + 2
|
||||
+ 3
|
||||
+ 4
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
The previous code would emit the following tokens: `1` `+` `2` `NewLine` `Indent` `+` `3` `NewLine`
|
||||
`+` `4` `Dedent`
|
||||
|
||||
|
||||
Additionaly, it is a lexical error to have wrong indentation. The lexer stores all
|
||||
previous indentation levels in a stack, and reports an error if a decrease in indentation
|
||||
doesn't match a previous level.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
if true { // 0 indentation
|
||||
print() // 4 indentation
|
||||
print() // 2 indentation. Error. There is no 2-indentation level
|
||||
}
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
All productions of the grammar ignore whitespace/indentation, except those involved in
|
||||
semicolon inference.
|
||||
|
||||
|
||||
## Statement termination / Semicolon inference
|
||||
|
||||
**Only inside a block of code** whitespace is used to determine where a statement ends
|
||||
@ -207,41 +213,43 @@ and a new one begins. Everywhere else whitespace is ignored.
|
||||
|
||||
Statements in THP end when a new line is encountered:
|
||||
|
||||
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// The statement ends | here, on the newline
|
||||
val value = (123 + 456) * 0.75
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Code thpcode={`
|
||||
// Each line contains a different statement. They all end on their new lines
|
||||
|
||||
var a = 1 + 2 // a = 3
|
||||
+ 3 // this is not part of \`a\`, this is a different statement
|
||||
`} />
|
||||
var a = 1 + 2 // a = 3
|
||||
|
||||
- 3 // this is not part of \`a\`, this is a different statement
|
||||
`} />
|
||||
|
||||
This is true even if the line ends with an operator:
|
||||
|
||||
<Code thpcode={`
|
||||
// These are still different statements
|
||||
|
||||
var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
|
||||
3 // This is still a different statement
|
||||
var a = 1 + 2 + // This is now a compile error, there is a hanging `+`
|
||||
3 // This is still a different statement
|
||||
`} />
|
||||
|
||||
|
||||
### Parenthesis
|
||||
|
||||
Exception 1: When a parenthesis is open, all following whitespace is ignored
|
||||
until the closing parenthesis.
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// open parenthesis found, all whitespace is ignored until the closing
|
||||
name.contains(
|
||||
"weird"
|
||||
)
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
However, for a parenthesis to begin to act, it needs to be open on the same line.
|
||||
|
||||
@ -254,7 +262,7 @@ print
|
||||
|
||||
// Now it's one single statement
|
||||
print(
|
||||
"hello"
|
||||
"hello"
|
||||
)
|
||||
`} />
|
||||
|
||||
@ -264,17 +272,21 @@ Exception 2:
|
||||
|
||||
- When a binary operator is followed by indentation:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val sum = 1 + 2 + // The line ends with a binary operator
|
||||
3 // There is indentation
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
- Or when indentation is followed by a binary operator:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val sum = 1 + 2
|
||||
+ 3 // Indentation and a binary operator
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
In theses cases, all whitespace will be ignored
|
||||
until the indentation returns to the initial level.
|
||||
@ -291,5 +303,3 @@ val person = PersonBuilder()
|
||||
// Here indentation returns, and a new statement begins
|
||||
print(person)
|
||||
`} />
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Comment
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Comment
|
||||
|
||||
@ -10,8 +11,10 @@ import Code from "@/components/Code.astro"
|
||||
Comment = "//", any_except_new_line
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
// This is a comment
|
||||
//
|
||||
// Another // comment
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Identifiers & Datatypes
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Identifiers & Datatypes
|
||||
|
||||
@ -19,14 +20,15 @@ Identifier = (underscore | lowercase_letter), identifier_letter*
|
||||
identifier_letter = underscore | lowercase_letter | uppercase_letter | decimal_digit
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
identifier
|
||||
_identifier
|
||||
_123
|
||||
_many_letters
|
||||
camelCase
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Datatype
|
||||
|
||||
@ -34,22 +36,23 @@ camelCase
|
||||
Datatype = uppercase_letter, indentifier_letter*
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
Datatype
|
||||
PDO
|
||||
WEIRD_DATATYPE
|
||||
`} />
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
## Keywords
|
||||
|
||||
The following are (currently) THP keywords:
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
val var fun
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
Keywords are scanned first as identifiers, then transformed
|
||||
to their respective tokens.
|
||||
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Numbers
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Numbers
|
||||
|
||||
@ -20,7 +21,8 @@ binary_number = "0", ("b" | "B"), binary_digit+
|
||||
decimal_number = "1".."9", decimal_digit*
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
12345
|
||||
01234 // This is an error, a decimal number cant have
|
||||
// leading zeroes
|
||||
@ -28,12 +30,11 @@ decimal_number = "1".."9", decimal_digit*
|
||||
0b0110
|
||||
0xff25
|
||||
0XFfaA
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
`TODO`: Allow underscores `_` between any number: `1_000_000`.
|
||||
|
||||
|
||||
|
||||
## Float
|
||||
|
||||
```ebnf
|
||||
@ -52,12 +53,7 @@ scientific_notation = "e", ("+" | "-"), decimal_digit+
|
||||
123e-3
|
||||
`} />
|
||||
|
||||
|
||||
All floating point numbers must start with at least 1 digit.
|
||||
`.5` is not a valid floating point number.
|
||||
|
||||
`.5` is not a valid floating point number.
|
||||
|
||||
`TODO`: Allow scientific notation to omit the `+`/`-`: `10e4`.
|
||||
|
||||
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: Operator
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# Operator
|
||||
|
||||
|
||||
```ebnf
|
||||
Operator = operator_char+
|
||||
|
||||
@ -15,14 +15,16 @@ operator_char = "+" | "-" | "=" | "*" | "!" | "/" | "|"
|
||||
| "<" | ">" | "^" | "." | ":"
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
+ - / * % < > <= >= -> =>
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
These are all the characters that can make an operator.
|
||||
|
||||
The lexer doesn't know about any operator in particular.
|
||||
In other languages something like `+-1` would be interpreted
|
||||
In other languages something like `+-1` would be interpreted
|
||||
as `+` `-` `1`. In THP, this is always `+-` `1`, and that
|
||||
would throw an error because the operator `+-` doesn't exist.
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
layout: "../_wrapper.astro"
|
||||
title: String
|
||||
---
|
||||
import Code from "@/components/Code.astro"
|
||||
|
||||
import Code from "@/components/Code.astro";
|
||||
|
||||
# String
|
||||
|
||||
@ -20,11 +21,13 @@ escape_seq = "\n"
|
||||
string_char = any_unicode_except_newline_and_double_quote
|
||||
```
|
||||
|
||||
<Code thpcode={`
|
||||
<Code
|
||||
thpcode={`
|
||||
"hello"
|
||||
""
|
||||
"it's me"
|
||||
"\\"Mario\\""
|
||||
`} />
|
||||
`}
|
||||
/>
|
||||
|
||||
`TODO`: String interpolation
|
||||
|
@ -34,5 +34,3 @@ pub enum TokenType {
|
||||
```
|
||||
|
||||
Every keyword has its own token.
|
||||
|
||||
|
||||
|
@ -1,64 +1,64 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"c-thp": "var(--c-thp)",
|
||||
"c-bg": "var(--c-bg)",
|
||||
"c-text": "var(--c-text)",
|
||||
"c-text-2": "var(--c-text-2)",
|
||||
"c-purple": "var(--c-purple)",
|
||||
"c-border-1": "var(--c-border-1)",
|
||||
"c-purple-light": "var(--c-purple-light)",
|
||||
"c-box-shadow": "var(--c-box-shadow)",
|
||||
"c-ping": "var(--c-pink)",
|
||||
"c-background-2": "var(--c-background-2)",
|
||||
"c-primary": "var(--c-primary)",
|
||||
"c-secondary": "var(--c-secondary)",
|
||||
"c-nav-bg": "var(--c-nav-bg)",
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
"mono": "var(--font-code)",
|
||||
"display": "var(--font-display)",
|
||||
"body": "var(--font-body)",
|
||||
},
|
||||
},
|
||||
corePlugins: {
|
||||
container: false
|
||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"c-thp": "var(--c-thp)",
|
||||
"c-bg": "var(--c-bg)",
|
||||
"c-text": "var(--c-text)",
|
||||
"c-text-2": "var(--c-text-2)",
|
||||
"c-purple": "var(--c-purple)",
|
||||
"c-border-1": "var(--c-border-1)",
|
||||
"c-purple-light": "var(--c-purple-light)",
|
||||
"c-box-shadow": "var(--c-box-shadow)",
|
||||
"c-ping": "var(--c-pink)",
|
||||
"c-background-2": "var(--c-background-2)",
|
||||
"c-primary": "var(--c-primary)",
|
||||
"c-secondary": "var(--c-secondary)",
|
||||
"c-nav-bg": "var(--c-nav-bg)",
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
function ({ addComponents }) {
|
||||
addComponents({
|
||||
'.container': {
|
||||
width: '98%',
|
||||
'@screen sm': {
|
||||
maxWidth: '640px',
|
||||
},
|
||||
'@screen md': {
|
||||
maxWidth: '768px',
|
||||
},
|
||||
'@screen lg': {
|
||||
maxWidth: '1024px',
|
||||
},
|
||||
'@screen xl': {
|
||||
maxWidth: '1400px',
|
||||
},
|
||||
},
|
||||
'.small-container': {
|
||||
width: '98%',
|
||||
'@screen sm': {
|
||||
maxWidth: '640px',
|
||||
},
|
||||
'@screen md': {
|
||||
maxWidth: '768px',
|
||||
},
|
||||
'@screen lg': {
|
||||
maxWidth: 'inherit',
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
],
|
||||
}
|
||||
fontFamily: {
|
||||
mono: "var(--font-code)",
|
||||
display: "var(--font-display)",
|
||||
body: "var(--font-body)",
|
||||
},
|
||||
},
|
||||
corePlugins: {
|
||||
container: false,
|
||||
},
|
||||
plugins: [
|
||||
function ({ addComponents }) {
|
||||
addComponents({
|
||||
".container": {
|
||||
width: "98%",
|
||||
"@screen sm": {
|
||||
maxWidth: "640px",
|
||||
},
|
||||
"@screen md": {
|
||||
maxWidth: "768px",
|
||||
},
|
||||
"@screen lg": {
|
||||
maxWidth: "1024px",
|
||||
},
|
||||
"@screen xl": {
|
||||
maxWidth: "1400px",
|
||||
},
|
||||
},
|
||||
".small-container": {
|
||||
width: "98%",
|
||||
"@screen sm": {
|
||||
maxWidth: "640px",
|
||||
},
|
||||
"@screen md": {
|
||||
maxWidth: "768px",
|
||||
},
|
||||
"@screen lg": {
|
||||
maxWidth: "inherit",
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user