Fix highlighter. Add docs on templating
This commit is contained in:
parent
733921a2ff
commit
5208496124
@ -1,11 +1,13 @@
|
|||||||
---
|
---
|
||||||
import { trimAndDedent } from "./utils";
|
import { trimAndDedent } from "./utils";
|
||||||
|
|
||||||
const {thpcode} = Astro.props;
|
const { thpcode } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex h-full py-8 items-center">
|
<div class="flex h-full py-8 items-center">
|
||||||
<div class="p-4 w-full">
|
<div class="p-4 w-full">
|
||||||
<pre class="language-thp" data-language="thp"><code class="language-thp">{trimAndDedent(thpcode).join("\n")}</code></pre>
|
<pre
|
||||||
|
class="language-thp"
|
||||||
|
data-language="thp"><code class="language-thp">{trimAndDedent(thpcode).join("\n")}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,8 +3,7 @@ import { lex } from "../lexer/lexer";
|
|||||||
import type { Instruction } from "../thp_machine/machine_parser";
|
import type { Instruction } from "../thp_machine/machine_parser";
|
||||||
import { parse_str } from "../thp_machine/machine_parser";
|
import { parse_str } from "../thp_machine/machine_parser";
|
||||||
import { trimAndDedent } from "./utils";
|
import { trimAndDedent } from "./utils";
|
||||||
const {code, steps} = Astro.props;
|
const { code, steps } = Astro.props;
|
||||||
|
|
||||||
|
|
||||||
function highlightCode(lines: Array<string>): string {
|
function highlightCode(lines: Array<string>): string {
|
||||||
let outLines: Array<string> = [];
|
let outLines: Array<string> = [];
|
||||||
@ -12,12 +11,14 @@ function highlightCode(lines: Array<string>): string {
|
|||||||
for (const [idx, line] of lines.entries()) {
|
for (const [idx, line] of lines.entries()) {
|
||||||
const tokens = lex(line);
|
const tokens = lex(line);
|
||||||
const lineArray = [
|
const lineArray = [
|
||||||
`<div class=\"inline-block w-full\" :class=\"line === ${idx + 1}? 'bg-green-200 dark:bg-green-900': ''\">`
|
`<div class=\"inline-block w-full\" :class=\"line === ${idx + 1}? 'bg-green-200 dark:bg-green-900': ''\">`,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (token.token_type !== "") {
|
if (token.token_type !== "") {
|
||||||
lineArray.push(`<span class="token ${token.token_type}">${token.v}</span>`);
|
lineArray.push(
|
||||||
|
`<span class="token ${token.token_type}">${token.v}</span>`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
lineArray.push(token.v);
|
lineArray.push(token.v);
|
||||||
}
|
}
|
||||||
@ -42,7 +43,8 @@ try {
|
|||||||
const serialized_inst = JSON.stringify(instructionSet);
|
const serialized_inst = JSON.stringify(instructionSet);
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="bg-black text-white rounded px-1"
|
<div
|
||||||
|
class="bg-black text-white rounded px-1"
|
||||||
x-data={`{
|
x-data={`{
|
||||||
line: 0,
|
line: 0,
|
||||||
stdout: "",
|
stdout: "",
|
||||||
@ -52,45 +54,62 @@ const serialized_inst = JSON.stringify(instructionSet);
|
|||||||
state: {},
|
state: {},
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span class="inline-block bg-[var(--code-theme-bg-acolor)] px-2 rounded-tl rounded-tr font-mono text-sm">thp code</span>
|
<span
|
||||||
<pre class="language-thp" style="margin: 0;" data-disabled><code set:html={codeHtml}></code></pre>
|
class="inline-block bg-[var(--code-theme-bg-acolor)] px-2 rounded-tl rounded-tr font-mono text-sm"
|
||||||
|
>thp code</span
|
||||||
|
>
|
||||||
|
<pre
|
||||||
|
class="language-thp"
|
||||||
|
style="margin: 0;"
|
||||||
|
data-disabled><code set:html={codeHtml} /></pre>
|
||||||
<div class="grid grid-cols-2 font-mono text-sm">
|
<div class="grid grid-cols-2 font-mono text-sm">
|
||||||
<div>
|
<div>
|
||||||
<div class="p-1 border-b border-r border-white">stdout</div>
|
<div class="p-1 border-b border-r border-white">stdout</div>
|
||||||
<div class="h-24 p-1 border-r border-white">
|
<div class="h-24 p-1 border-r border-white">
|
||||||
<pre><code class="bg-black" x-text="stdout"></code></pre>
|
<pre><code class="bg-black" x-text="stdout" /></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="p-1 border-b border-white">state</div>
|
<div class="p-1 border-b border-white">state</div>
|
||||||
<div class="h-24 p-1 overflow-y-scroll">
|
<div class="h-24 p-1 overflow-y-scroll">
|
||||||
<template x-for="(value, key) in state">
|
<template x-for="(value, key) in state">
|
||||||
<div x-text="key.replaceAll(' ', ' ') + ' = ' + value"></div>
|
<div x-text="key.replaceAll(' ', ' ') + ' = ' + value">
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-white p-1">
|
<div class="border-t border-white p-1">
|
||||||
<button class="font-mono px-1 rounded bg-pink-200 dark:bg-pink-950 text-black dark:text-white disabled:opacity-50 disabled:cursor-not-allowed" @click="alpineNext($data)" :disabled="done && 'true'">
|
<button
|
||||||
|
class="font-mono px-1 rounded bg-pink-200 dark:bg-pink-950 text-black dark:text-white disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
@click="alpineNext($data)"
|
||||||
|
:disabled="done && 'true'"
|
||||||
|
>
|
||||||
Step: <span x-text="ip"></span>
|
Step: <span x-text="ip"></span>
|
||||||
</button>
|
</button>
|
||||||
<button class="font-mono px-1 rounded bg-pink-200 dark:bg-pink-950 text-black dark:text-white" @click="alpineReset($data)">
|
<button
|
||||||
|
class="font-mono px-1 rounded bg-pink-200 dark:bg-pink-950 text-black dark:text-white"
|
||||||
|
@click="alpineReset($data)"
|
||||||
|
>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { InstructionType, type Instruction } from "../thp_machine/machine_parser";
|
import {
|
||||||
|
InstructionType,
|
||||||
|
type Instruction,
|
||||||
|
} from "../thp_machine/machine_parser";
|
||||||
|
|
||||||
type AlpineState = {
|
type AlpineState = {
|
||||||
line: number,
|
line: number;
|
||||||
stdout: string,
|
stdout: string;
|
||||||
ip: number,
|
ip: number;
|
||||||
inst: Array<Array<Instruction>>
|
inst: Array<Array<Instruction>>;
|
||||||
done: boolean,
|
done: boolean;
|
||||||
state: {[key: string]: string},
|
state: { [key: string]: string };
|
||||||
}
|
};
|
||||||
|
|
||||||
/// Executes the instruction following the state of the machine.
|
/// Executes the instruction following the state of the machine.
|
||||||
function alpineNext(data: AlpineState) {
|
function alpineNext(data: AlpineState) {
|
||||||
@ -108,7 +127,7 @@ const serialized_inst = JSON.stringify(instructionSet);
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case InstructionType.Out: {
|
case InstructionType.Out: {
|
||||||
data.stdout += i.v0.slice(1, -1) + "\n"
|
data.stdout += i.v0.slice(1, -1) + "\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case InstructionType.Set: {
|
case InstructionType.Set: {
|
||||||
|
@ -17,7 +17,10 @@ export function highlightOnDom() {
|
|||||||
|
|
||||||
const pre_parent = el.parentElement!;
|
const pre_parent = el.parentElement!;
|
||||||
const new_div = document.createElement("div");
|
const new_div = document.createElement("div");
|
||||||
const code = el.innerText;
|
let code = el.innerHTML;
|
||||||
|
|
||||||
|
// Replace all < with < and all > with >
|
||||||
|
code = code.replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|
||||||
el.parentElement!.className = "language-thp";
|
el.parentElement!.className = "language-thp";
|
||||||
pre_parent.removeChild(el);
|
pre_parent.removeChild(el);
|
||||||
|
@ -26,11 +26,11 @@ export type Token = {
|
|||||||
* @param code Code to lex
|
* @param code Code to lex
|
||||||
* @returns An array of all the tokens found
|
* @returns An array of all the tokens found
|
||||||
*/
|
*/
|
||||||
export function lex(code: string): Array<Token> {
|
export function lex(code: string, start = 0): Array<Token> {
|
||||||
const code_len = code.length;
|
const code_len = code.length;
|
||||||
const tokens: Array<Token> = [];
|
const tokens: Array<Token> = [];
|
||||||
|
|
||||||
let current_pos = 0;
|
let current_pos = start;
|
||||||
let current_default_token = "";
|
let current_default_token = "";
|
||||||
|
|
||||||
while (current_pos < code_len) {
|
while (current_pos < code_len) {
|
||||||
@ -115,6 +115,34 @@ export function lex(code: string): Array<Token> {
|
|||||||
current_pos = pos;
|
current_pos = pos;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// try to scan a multiline comment
|
||||||
|
else if (c === "/" && code[current_pos + 1] === "*") {
|
||||||
|
// if the current default token is not empty, push it to the tokens array
|
||||||
|
if (current_default_token !== "") {
|
||||||
|
tokens.push({ v: current_default_token, token_type: "" });
|
||||||
|
current_default_token = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let comment = "";
|
||||||
|
let pos = current_pos;
|
||||||
|
|
||||||
|
while (pos < code_len) {
|
||||||
|
const char = code[pos];
|
||||||
|
|
||||||
|
if (char === "*" && code[pos + 1] === "/") {
|
||||||
|
pos += 2;
|
||||||
|
comment += "*/";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
comment += char;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push({ v: comment, token_type: "comment" });
|
||||||
|
current_pos = pos;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// replace < with <
|
// replace < with <
|
||||||
else if (c === "<") {
|
else if (c === "<") {
|
||||||
current_default_token += "<";
|
current_default_token += "<";
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
import Navbar from "../components/Navbar.astro";
|
import Navbar from "../components/Navbar.astro";
|
||||||
import CodeEditor from "../components/CodeEditor.astro";
|
|
||||||
import HeroSection from "../components/HeroSection.astro";
|
import HeroSection from "../components/HeroSection.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -145,6 +144,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
}
|
}
|
||||||
|
|
||||||
val root = DirEntry::Dir("/")
|
val root = DirEntry::Dir("/")
|
||||||
|
val test_file = DirEntry::File("test.txt")
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Make invalid state irrepresentable.
|
Make invalid state irrepresentable.
|
||||||
@ -164,7 +164,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
{
|
{
|
||||||
print(filename)
|
print(filename)
|
||||||
}
|
}
|
||||||
else
|
case _
|
||||||
{
|
{
|
||||||
print("A valid file was not found")
|
print("A valid file was not found")
|
||||||
}
|
}
|
||||||
@ -181,13 +181,18 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
thpcode={`
|
thpcode={`
|
||||||
String? username = Globals::Post::get("username")
|
String? username = Globals::Post::get("username")
|
||||||
|
|
||||||
if username? {
|
if username?
|
||||||
|
{
|
||||||
// username is a \`String\` here
|
// username is a \`String\` here
|
||||||
print("Hello, {username}")
|
print("Hello, {username}")
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Nulls are explicit and require handling.
|
Nulls are explicit and require handling. Types are nullable, and they
|
||||||
|
are used everywhere they are needed.
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Nullable types must be explicitly checked before using them.
|
||||||
</HeroSection>
|
</HeroSection>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -213,7 +218,8 @@ case Dog(name)
|
|||||||
{
|
{
|
||||||
print("{name} has 1 live ")
|
print("{name} has 1 live ")
|
||||||
}
|
}
|
||||||
case Cat(name, lives) {
|
case Cat(name, lives)
|
||||||
|
{
|
||||||
print("{name} has {lives}")
|
print("{name} has {lives}")
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
|
@ -5,17 +5,27 @@ title: Comments
|
|||||||
|
|
||||||
# Comments
|
# Comments
|
||||||
|
|
||||||
Only these two:
|
THP supports single and multi line comments:
|
||||||
|
|
||||||
|
|
||||||
## Single line
|
## Single line
|
||||||
|
|
||||||
|
Begin with double slash `//` and continue until the end of the line.
|
||||||
|
|
||||||
```thp
|
```thp
|
||||||
// This is a single line comment
|
// This is a single line comment
|
||||||
|
print("hello!")
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Multi line comments can be nested in THP.
|
||||||
|
|
||||||
```thp
|
```thp
|
||||||
/*
|
/*
|
||||||
This is a
|
This is a
|
||||||
@ -30,5 +40,7 @@ Only these two:
|
|||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
TBD: Doc comments use triple slash `///`? or `/** */`?
|
## Documentation comments
|
||||||
|
|
||||||
|
TBD: Should doc comments use triple slash `///`? or `/** */`?
|
||||||
|
|
||||||
|
@ -6,11 +6,39 @@ import InteractiveCode from "../../../components/InteractiveCode.astro";
|
|||||||
|
|
||||||
# Hello, world!
|
# Hello, world!
|
||||||
|
|
||||||
Create a file named `hello.thp` with the contents:
|
## THP source code
|
||||||
|
|
||||||
|
Unlike PHP, THP code is written directly. There is no need to use any `<?php` tags,
|
||||||
|
just write the code like any other programming language.
|
||||||
|
|
||||||
|
As a consequence of this, HTML templates are defined in other ways, which will be
|
||||||
|
detailed later on.
|
||||||
|
|
||||||
|
To write a hello world program write the following code in a file:
|
||||||
|
|
||||||
```thp
|
```thp
|
||||||
print("Hello, world!")
|
print("Hello, world!")
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run `thp hello.thp`
|
Then run `thp hello.thp` from your terminal.
|
||||||
|
|
||||||
|
|
||||||
|
## Instruction separation
|
||||||
|
|
||||||
|
THP uses whitespace to determine when a statement is over. In short,
|
||||||
|
where PHP uses a semicolon `;`, THP uses a newline.
|
||||||
|
|
||||||
|
```php
|
||||||
|
echo("A");
|
||||||
|
echo("B");
|
||||||
|
echo("C");
|
||||||
|
```
|
||||||
|
|
||||||
|
```thp
|
||||||
|
print("A")
|
||||||
|
print("B")
|
||||||
|
print("C")
|
||||||
|
```
|
||||||
|
|
||||||
|
As a consequence of this, there can only be 1 statement per line.
|
||||||
|
|
||||||
|
@ -6,12 +6,18 @@ title: Variables
|
|||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
|
|
||||||
thp distinguishes between mutable and immutable variables.
|
THP distinguishes between mutable and immutable variables.
|
||||||
|
|
||||||
Variables have to be declared in THP, to avoid issues with scopes.
|
Variables must be declared in THP to avoid issues with scoping and
|
||||||
It is a compile error to use undeclared variables.
|
to know if they are mutable/immutable.
|
||||||
|
It's a compile error to use undeclared variables.
|
||||||
|
|
||||||
Variable names **must** begin with a lowercase letter.
|
Variable names **don't** start with a dollar sign `$`.
|
||||||
|
|
||||||
|
Variable names **must** begin with a lowercase letter or an underscore.
|
||||||
|
Then they may contain lowercase/uppercase letters, numbers and underscores.
|
||||||
|
|
||||||
|
As a regex: `[a-z_][a-zA-Z0-9_]*`
|
||||||
|
|
||||||
## Immutable variables
|
## Immutable variables
|
||||||
|
|
||||||
@ -39,7 +45,7 @@ String surname = "Doe"
|
|||||||
Int year_of_birth = 1984
|
Int year_of_birth = 1984
|
||||||
```
|
```
|
||||||
|
|
||||||
This means that if a variable has only a datatype, it is immutable.
|
This means that if a variable only has a datatype, it is immutable.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ pagesLayout:
|
|||||||
title: Basics
|
title: Basics
|
||||||
children:
|
children:
|
||||||
- path: hello-world
|
- path: hello-world
|
||||||
|
- path: comments
|
||||||
- path: variables
|
- path: variables
|
||||||
- path: datatypes
|
- path: datatypes
|
||||||
- path: comments
|
|
||||||
- path: operators
|
- path: operators
|
||||||
- path: flow-control
|
- path: flow-control
|
||||||
title: Flow control
|
title: Flow control
|
||||||
@ -48,6 +48,10 @@ pagesLayout:
|
|||||||
- path: interfaces
|
- path: interfaces
|
||||||
- path: anonymous
|
- path: anonymous
|
||||||
- path: magic
|
- path: magic
|
||||||
|
- path: templating
|
||||||
|
title: Templating
|
||||||
|
children:
|
||||||
|
- path: intro
|
||||||
---
|
---
|
||||||
import InteractiveCode from "../../components/InteractiveCode.astro";
|
import InteractiveCode from "../../components/InteractiveCode.astro";
|
||||||
|
|
||||||
@ -106,19 +110,17 @@ THP program!
|
|||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Bring static typing to PHP: Not just type hints, not just `mixed` for everything
|
- Bring static typing to PHP: Generics, type checks at compile and runtime.
|
||||||
that isn't a primitive type.
|
- Reduce implicit type conversion to a minimum.
|
||||||
- Generics & ADTs
|
|
||||||
- Avoid implicit type conversion.
|
|
||||||
- Remove the inconsistencies in the language.
|
- Remove the inconsistencies in the language.
|
||||||
- Organize the stdlib into modules.
|
- Organize the PHP stdlib.
|
||||||
- Differentiate between Arrays, Tuples, Maps and Sets.
|
- Have clear distinctions between Arrays, Tuples, Maps and Sets.
|
||||||
|
- Implement Union types
|
||||||
- Create a **consistent** language.
|
- Create a **consistent** language.
|
||||||
- Have typings for popular libraries (like TS's `.d.ts`).
|
- Have typings for popular libraries (like TS's `.d.ts`).
|
||||||
- Have a simple instalation and configuration (requiring just Composer).
|
- Have a simple instalation and configuration (requiring just Composer).
|
||||||
- Ship a fast, native binary (written in Rust) (why use PHP when we can go **_blazingly fast_**?).
|
- Ship a fast, native binary (written in Rust) (why use PHP when we can go **_blazingly fast_**?).
|
||||||
- Support in-place compilation.
|
- Support in-place compilation.
|
||||||
- (Try to) emit readable PHP.
|
|
||||||
- Implement an LSP server.
|
- Implement an LSP server.
|
||||||
- Implement a formatter.
|
- Implement a formatter.
|
||||||
|
|
||||||
@ -128,8 +130,7 @@ THP program!
|
|||||||
These are **not** aspects that THP looks to solve or implement.
|
These are **not** aspects that THP looks to solve or implement.
|
||||||
|
|
||||||
- Be what TypeScript is for JavaScript (PHP with types).
|
- Be what TypeScript is for JavaScript (PHP with types).
|
||||||
- Use PHP syntax/conventions.
|
- Strictly adhere to PHP syntax/conventions.
|
||||||
- Be familiar for PHP developers.
|
|
||||||
|
|
||||||
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.
|
||||||
|
@ -5,6 +5,8 @@ title: Install
|
|||||||
|
|
||||||
# Install
|
# Install
|
||||||
|
|
||||||
|
**THP is not available for public use. These are goals.**
|
||||||
|
|
||||||
## From scratch
|
## From scratch
|
||||||
|
|
||||||
Also install php (through XAMPP in windows/mac, php in linux) and Composer.
|
Also install php (through XAMPP in windows/mac, php in linux) and Composer.
|
||||||
|
104
src/pages/learn/templating/intro.md
Normal file
104
src/pages/learn/templating/intro.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
layout: ../../../layouts/DocsLayout.astro
|
||||||
|
title: Introduction
|
||||||
|
---
|
||||||
|
|
||||||
|
# THP templating
|
||||||
|
|
||||||
|
React changed the way HTML is rendered with the introduction of JSX.
|
||||||
|
Having access and being able to create and manipulate HTML elements
|
||||||
|
from code led to the pattern of components.
|
||||||
|
|
||||||
|
On the other hand, PHP is characterized for its ability to easily render
|
||||||
|
HTML, to the point that PHP code is conceptually written inside an
|
||||||
|
HTML tag. However, this model doesn't play well with the components
|
||||||
|
model pioneered by React and that is so common nowadays on front-end
|
||||||
|
development.
|
||||||
|
|
||||||
|
Creating small, reusable templates is hard with pure PHP. Instead you
|
||||||
|
need to rely on external templating libraries, and even still, all your
|
||||||
|
HTML is written outside the PHP file, and communicates in one way
|
||||||
|
with weak, fragile data passing.
|
||||||
|
|
||||||
|
For example, using the Plates PHP library you may create a Button component
|
||||||
|
like so:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// button.php
|
||||||
|
function render_button(string $name) {
|
||||||
|
$templates = new League\Plates\Engine("./");
|
||||||
|
$button_html = $templates->render("button.template", ["name" => $name]);
|
||||||
|
return $button_html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
|
// button.template.php
|
||||||
|
<button class="some tailwind classes">
|
||||||
|
Hello <?= $this->e($name) ?>!
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
This approach has many problems:
|
||||||
|
|
||||||
|
- You have to create a new file for every new component,
|
||||||
|
polluting the file system and the project. This may be significant
|
||||||
|
for some web hosting providers that establish a fixed inode limit.
|
||||||
|
- Data is passed dinamically, via strings. If either the render
|
||||||
|
function or the template change, the component will behave
|
||||||
|
incorrectly without any warning.
|
||||||
|
- It's hard to include components inside components. Every new
|
||||||
|
one requires a change in 2 files.
|
||||||
|
|
||||||
|
Maybe for these (and other) reasons components are not used with
|
||||||
|
templating libraries. Instead people use sections, layouts, etc.
|
||||||
|
|
||||||
|
We believe that by inverting the PHP model we can improve
|
||||||
|
the experience of writing HTML. By having HTML inside of code,
|
||||||
|
not code inside of HTML, we can easily create many small components
|
||||||
|
and compose them.
|
||||||
|
|
||||||
|
The following would be the equivalent in THP:
|
||||||
|
|
||||||
|
```thp
|
||||||
|
fun Button(String name) -> Html {
|
||||||
|
<button class="some tailwind classes">
|
||||||
|
Hello {name}!
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is very similar to React. The HTML is inside the THP code, not the other
|
||||||
|
way around, so you can have arbitrary logic in the component.
|
||||||
|
|
||||||
|
```thp
|
||||||
|
fun User(String name) {
|
||||||
|
// Get info from the database
|
||||||
|
val user = try Model::get_user(name)
|
||||||
|
else {
|
||||||
|
return <div>User not found!</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run other logic
|
||||||
|
|
||||||
|
<div class="some tailwind classes">
|
||||||
|
Hello {user.name}!
|
||||||
|
<br>
|
||||||
|
Here are your transactions:
|
||||||
|
#for t in user.transactions {
|
||||||
|
<TransactionItem t={t} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TransactionItem(Transaction t) {
|
||||||
|
<li>
|
||||||
|
{t.date} - {t.name} ({t.price})
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user