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