Repository: syuilo/aiscript
Pull request #152 (2021-11-04 07:08:13)

Translate docs to English as per issue #151
<!-- ℹ お読みください
PRありがとうございます！ PRを作成する前に、以下をご確認ください:
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
  - fix / refactor / feat / enhance / perf / chore
  - また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
- CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
- テスト、Lintが通っていることを予め確認してください。
  - `npm run test`、`npm run lint`でぞれぞれ実施可能です
- `npm run api`を実行してAPIレポートを更新し、差分がある場合はコミットしてください。
ご協力ありがとうございます🤗
-->
<!-- ℹ README
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
  - fix / refactor / feat / enhance / perf / chore
  - Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
  - You can run it with `npm run test` and `npm run lint`.
- Run `npm run api` to update the API report and commit it if there are any diffs.
Thanks for your cooperation 🤗
-->
See issue #151 
# What
<!-- このPRで何をしたのか？ どう変わるのか？ -->
<!-- What did you do with this PR? How will it change things? -->
This pull request adds an english translation to the docs without modifying the existing ones. The existing docs have a link to the translated version.

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
So more people can contribute.

<!--# Additional info (optional)-->
<!-- テスト観点など -->
<!-- Test perspective, etc -->



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7779ac2..9068825 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,13 @@
+[Read translated version (en)](./translations/en/CHANGELOG.md)
+
 # (unreleased)
 ## Breaking changes
+- 比較の`=` → `==`
+- `&` → `&&`
+- `|` → `||`
 - `? ~ .? ~ .` → `if ~ elif ~ else`
-- `? x { 42 => yes }` → `match x { 42 => yes }`
+- `? x { 42 => yes }` → `match x { 42 => true }`
+- `yes` `no` → `true` `false`
 - `_` → `null`
 - `<<` → `return`
 - `~` → `for`
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 50ccdfd..6fd76c7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,5 @@
+[Read translated version (en)](./translations/en/CONTRIBUTING.md)
+
 # Contribution guide
 プロジェクトに興味を持っていただきありがとうございます！
 このドキュメントでは、プロジェクトに貢献する際に必要な情報をまとめています。
diff --git a/README.md b/README.md
index 577cd72..547b921 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,8 @@
 
 [Play online ▶](https://syuilo.github.io/aiscript/)
 
+[Read translated version (en)](./translations/en/README.md)
+
 AiScriptは、JavaScript上で動作するマルチパラダイムプログラミング言語です。AltJSではありません(1)。
 
 * 配列、オブジェクト、関数等をファーストクラスでサポート
@@ -41,9 +43,9 @@ todo
 ### Fizz Buzz
 ```
 for (#i, 100) {
-  <: if ((i % 15) = 0) "FizzBuzz"
-    elif ((i % 3) = 0) "Fizz"
-    elif ((i % 5) = 0) "Buzz"
+  <: if ((i % 15) == 0) "FizzBuzz"
+    elif ((i % 3) == 0) "Fizz"
+    elif ((i % 5) == 0) "Buzz"
     else i
 }
 ```
diff --git a/docs/get-started.md b/docs/get-started.md
index a505cba..e1c5687 100644
--- a/docs/get-started.md
+++ b/docs/get-started.md
@@ -1,4 +1,7 @@
 # Getting started
+
+[Read translated version (en)](../translations/en/docs/get-started.md)
+
 ## Introduction
 AiScript(あいすくりぷと)は、プログラミング言語です。
 このドキュメントでは、既にある程度のプログラミングの知識があることを前提にしています。
@@ -34,7 +37,7 @@ AiScriptのコメントは`//`で始めます。
 	<tr><th>種類</th><th>型</th><th>リテラル例</th></tr>
 	<tr><td>文字列</td><td><code>str</code></td><td><code>"kawaii"</code></td></tr>
 	<tr><td>数値</td><td><code>num</code></td><td><code>42</code></td></tr>
-	<tr><td>真理値</td><td><code>bool</code></td><td><code>yes</code>/<code>no</code></td></tr>
+	<tr><td>真理値</td><td><code>bool</code></td><td><code>true</code>/<code>false</code></td></tr>
 	<tr><td>配列</td><td><code>arr</code></td><td><code>["ai" "chan" "cute"]</code></td></tr>
 	<tr><td>オブジェクト</td><td><code>obj</code></td><td><code>{ foo: "bar"; a: 42; }</code></td></tr>
 	<tr><td>null</td><td><code>null</code></td><td><code>null</code></td></tr>
@@ -112,9 +115,9 @@ Core:add(1, 1)
 	<tr><td><code>Core:mul</code></td><td><code>*</code></td><td>乗算</td></tr>
 	<tr><td><code>Core:div</code></td><td><code>/</code></td><td>除算</td></tr>
 	<tr><td><code>Core:mod</code></td><td><code>%</code></td><td>剰余</td></tr>
-	<tr><td><code>Core:eq</code></td><td><code>=</code></td><td>等しい</td></tr>
-	<tr><td><code>Core:and</code></td><td><code>&</code></td><td>かつ</td></tr>
-	<tr><td><code>Core:or</code></td><td><code>|</code></td><td>または</td></tr>
+	<tr><td><code>Core:eq</code></td><td><code>==</code></td><td>等しい</td></tr>
+	<tr><td><code>Core:and</code></td><td><code>&&</code></td><td>かつ</td></tr>
+	<tr><td><code>Core:or</code></td><td><code>||</code></td><td>または</td></tr>
 	<tr><td><code>Core:gt</code></td><td><code>></code></td><td>大きい</td></tr>
 	<tr><td><code>Core:lt</code></td><td><code><</code></td><td>小さい</td></tr>
 </table>
@@ -137,7 +140,7 @@ Core:add(1, 1)
 ## 条件分岐
 AiScriptでの条件分岐は、次のように書きます:
 ```
-if (a = b) {
+if (a == b) {
 	<: "a is equal to b"
 }
 ```
@@ -145,7 +148,7 @@ if (a = b) {
 `if`の後にboolを返す式(条件)を書き、その後に条件に一致した場合に評価される式(then節)を書きます。
 then節の後に`else`を書き、さらに式を追加することで条件に一致しなかった場合の処理も行うことが出来ます:
 ```
-if (a = b) {
+if (a == b) {
 	<: "a is equal to b"
 } else {
 	<: "a is not equal to b"
@@ -154,7 +157,7 @@ if (a = b) {
 
 `elif`の後に条件式を書くことで条件判定を複数行うことも出来ます:
 ```
-if (a = b) {
+if (a == b) {
 	<: "a is equal to b"
 } elif (a > b) {
 	<: "a is grater than b"
@@ -165,7 +168,7 @@ if (a = b) {
 
 これらの条件分岐は式なので、ブロック内で値を返せます:
 ```
-<: if (a = b) {
+<: if (a == b) {
 	"a is equal to b"
 } elif (a > b) {
 	"a is grater than b"
@@ -213,10 +216,11 @@ each (#item, items) {
 関数の最後に書かれた式の値が関数の返り値になりますが、関数の途中で値を返したい時は`return`を使います。
 
 ## テンプレート
-文字列の中に変数や式を埋め込めます:
+バッククォートを使うと、文字列の中に変数や式を埋め込めます:
 ```
 #ai = "kawaii"
 <: `Hello, {ai} world!`
+// 結果: Hello, kawaii world!
 ```
 
 ## メタデータ
@@ -228,14 +232,3 @@ AiScriptファイルにメタデータを埋め込める機能です。
 	keywords: ["foo" "bar" "baz"]
 }
 ```
-
-# Examples
-## FizzBuzz
-```
-for (#i, 100) {
-	<: ? ((i % 15) = 0) "FizzBuzz"
-		.? ((i % 3) = 0) "Fizz"
-		.? ((i % 5) = 0) "Buzz"
-		. i
-}
-```
diff --git a/docs/std.md b/docs/std.md
index 2686a40..ee13e12 100644
--- a/docs/std.md
+++ b/docs/std.md
@@ -1,3 +1,5 @@
+[Read translated version](../translations/en/docs/std.md)
+
 ## std
 
 ### @print(message: str): void
diff --git a/docs/syntax.md b/docs/syntax.md
index 335e8c5..d344925 100644
--- a/docs/syntax.md
+++ b/docs/syntax.md
@@ -1,4 +1,7 @@
 # Syntax
+
+[Read translated version (en)](../translations/en/docs/syntax.md)
+
 ## コメント
 `//`で始めた行はコメントになり、プログラムの動作に影響を与えません。
 
diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md
index 130f2ce..434839b 100644
--- a/etc/aiscript.api.md
+++ b/etc/aiscript.api.md
@@ -103,6 +103,9 @@ const FN_NATIVE: (fn: VFn['native']) => {
     }) => Value | Promise<Value> | void) | undefined;
 };
 
+// @public (undocumented)
+function getLangVersion(input: string): string | null;
+
 // @public (undocumented)
 function jsToVal(val: any): Value;
 
@@ -190,7 +193,8 @@ declare namespace utils {
         valToString,
         nodeToString,
         valToJs,
-        jsToVal
+        jsToVal,
+        getLangVersion
     }
 }
 export { utils }
diff --git a/playground/src/App.vue b/playground/src/App.vue
index a25455e..60892f4 100644
--- a/playground/src/App.vue
+++ b/playground/src/App.vue
@@ -77,9 +77,9 @@ watch(script, () => {
 
 const setCode = () => {
 	script.value = `for (#i, 100) {
-  <: if ((i % 15) = 0) "FizzBuzz"
-    elif ((i % 3) = 0) "Fizz"
-    elif ((i % 5) = 0) "Buzz"
+  <: if ((i % 15) == 0) "FizzBuzz"
+    elif ((i % 3) == 0) "Fizz"
+    elif ((i % 5) == 0) "Buzz"
     else i
 }`;
 };
diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts
index 4074ceb..526e1ed 100644
--- a/src/interpreter/index.ts
+++ b/src/interpreter/index.ts
@@ -234,6 +234,8 @@ export class AiScript {
 					const v = await this._run(node.statements, scope.createChildScope());
 					if (v.type === 'break') {
 						break;
+					} else if (v.type === 'return') {
+						return v.value;
 					}
 				}
 				return NULL;
@@ -247,6 +249,8 @@ export class AiScript {
 						const v = await this._eval(node.for, scope);
 						if (v.type === 'break') {
 							break;
+						} else if (v.type === 'return') {
+							return v.value;
 						}
 					}
 				} else {
@@ -260,6 +264,8 @@ export class AiScript {
 						])));
 						if (v.type === 'break') {
 							break;
+						} else if (v.type === 'return') {
+							return v.value;
 						}
 					}
 				}
diff --git a/src/interpreter/infix-to-fncall.ts b/src/interpreter/infix-to-fncall.ts
index 0f820b3..bade491 100644
--- a/src/interpreter/infix-to-fncall.ts
+++ b/src/interpreter/infix-to-fncall.ts
@@ -1,9 +1,8 @@
 import { AiScriptError } from './error';
-import { Node, NInfix, NOperator, NCall, Loc } from '../node';
+import { Node, NInfix, NCall } from '../node';
 
 type OperatorInfo = {
 	func: string; // 対応する関数
-	loc?: Loc;
 	prec: number; // 優先度（高いほど優先して計算される値）
 };
 
@@ -15,34 +14,15 @@ type OperatorInfo = {
  * (1) (3)
  * ```
  */
-type InfixTree = Node | {
+type InfixTree = {
 	type: 'infixtree';
 	opInfo: OperatorInfo;
-	lchild: InfixTree;
-	rchild: InfixTree;
+	left: InfixTree | Node;
+	right: InfixTree | Node;
 };
 
-function makeOperatorInfo(operator: NOperator): OperatorInfo {
-	switch (operator.op) {
-		case '+': return { func: 'Core:add', prec: 6, loc: operator.loc };
-		case '-': return { func: 'Core:sub', prec: 6, loc: operator.loc };
-		case '*': return { func: 'Core:mul', prec: 7, loc: operator.loc };
-		case '/': return { func: 'Core:div', prec: 7, loc: operator.loc };
-		case '%': return { func: 'Core:mod', prec: 7, loc: operator.loc };
-		case '=': return { func: 'Core:eq', prec: 4, loc: operator.loc };
-		case '!=': return { func: 'Core:neq', prec: 4, loc: operator.loc };
-		case '&': return { func: 'Core:and', prec: 3, loc: operator.loc };
-		case '|': return { func: 'Core:or', prec: 2, loc: operator.loc };
-		case '<': return { func: 'Core:lt', prec: 4, loc: operator.loc };
-		case '>': return { func: 'Core:gt', prec: 4, loc: operator.loc };
-		case '<=': return { func: 'Core:lteq', prec: 4, loc: operator.loc };
-		case '>=': return { func: 'Core:gteq', prec: 4, loc: operator.loc };
-		default: throw new AiScriptError(`No such operator: ${operator.op}.`);
-	}
-}
-
-function makeInfixTree(opInfo: OperatorInfo, lchild: InfixTree, rchild: InfixTree): InfixTree {
-	return { opInfo, lchild, rchild, type: 'infixtree' };
+function INFIX_TREE(opInfo: OperatorInfo, left: InfixTree | Node, right: InfixTree | Node): InfixTree {
+	return { type: 'infixtree', opInfo, left, right };
 }
 
 /**
@@ -68,45 +48,63 @@ function makeInfixTree(opInfo: OperatorInfo, lchild: InfixTree, rchild: InfixTre
  * - NOTE: 右結合性の演算子としては代入演算子などが挙げられる
  * - NOTE: 比較の演算子などは非結合性とされる
  */
-function insert(tree: InfixTree, nextOpInfo: OperatorInfo, nextTree: InfixTree): InfixTree {
+function insert(tree: InfixTree | Node, nextOpInfo: OperatorInfo, nextTree: InfixTree | Node): InfixTree {
 	if (tree.type !== 'infixtree') {
-		return makeInfixTree(nextOpInfo, tree, nextTree);
-	} else if (tree.opInfo.prec >= nextOpInfo.prec) {
-		return makeInfixTree(nextOpInfo, tree, nextTree);
-	} else {
-		const { opInfo, lchild, rchild } = tree;
-		return makeInfixTree(opInfo, lchild, insert(rchild, nextOpInfo, nextTree));
+		return INFIX_TREE(nextOpInfo, tree, nextTree);
+	}
+	if (tree.opInfo.prec >= nextOpInfo.prec) {
+		return INFIX_TREE(nextOpInfo, tree, nextTree);
+	}
+	const { opInfo, left, right } = tree;
+	return INFIX_TREE(opInfo, left, insert(right, nextOpInfo, nextTree));
+}
+
+const opInfoTable = {
+	'+': { func: 'Core:add', prec: 6 },
+	'-': { func: 'Core:sub', prec: 6 },
+	'*': { func: 'Core:mul', prec: 7 },
+	'/': { func: 'Core:div', prec: 7 },
+	'%': { func: 'Core:mod', prec: 7 },
+	'==': { func: 'Core:eq', prec: 4 },
+	'!=': { func: 'Core:neq', prec: 4 },
+	'&&': { func: 'Core:and', prec: 3 },
+	'||': { func: 'Core:or', prec: 2 },
+	'<': { func: 'Core:lt', prec: 4 },
+	'>': { func: 'Core:gt', prec: 4 },
+	'<=': { func: 'Core:lteq', prec: 4 },
+	'>=': { func: 'Core:gteq', prec: 4 },
+};
+
+function getOpInfo(operator: string): OperatorInfo {
+	const op = opInfoTable[operator];
+	if (op == null) {
+		throw new AiScriptError(`No such operator: ${operator}.`);
 	}
+	return op;
 }
 
 /**
  * 中置演算子式を表す木を対応する関数呼び出しの構造体に変換する
  */
-function ascall(tree: InfixTree): Node {
+function asCall(tree: InfixTree | Node): Node {
 	if (tree.type !== 'infixtree') {
 		return tree;
-	} else {
-		return {
-			type: 'call',
-			loc: tree.opInfo.loc,
-			name: tree.opInfo.func,
-			args: [ ascall(tree.lchild), ascall(tree.rchild) ],
-		} as NCall;
 	}
+	return {
+		type: 'call',
+		name: tree.opInfo.func,
+		args: [ asCall(tree.left), asCall(tree.right) ],
+	} as NCall;
 }
 
 /**
  * NInfix を関数呼び出し形式に変換する
  */
 export function infixToFnCall(node: NInfix): Node {
-	const infos = node.operators.map(makeOperatorInfo);
-	const nodes = node.operands;
-
-	let tree = makeInfixTree(infos[0], nodes[0], nodes[1]);
-
-	for (let i = 1; i < infos.length; i++) {
-		tree = insert(tree, infos[i], nodes[i + 1]);
+	const opInfos = node.operators.map(op => getOpInfo(op));
+	let tree = INFIX_TREE(opInfos[0], node.operands[0], node.operands[1]);
+	for (let i = 1; i < opInfos.length; i++) {
+		tree = insert(tree, opInfos[i], node.operands[i + 1]);
 	}
-
-	return ascall(tree);
+	return asCall(tree);
 }
diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts
index 5d3d7de..2f0c5d0 100644
--- a/src/interpreter/util.ts
+++ b/src/interpreter/util.ts
@@ -48,7 +48,7 @@ export function eq(a: Value, b: Value) {
 export function valToString(val: Value, simple = false) {
 	if (simple) {
 		if (val.type === 'num') return val.value;
-		if (val.type === 'bool') return val.value ? 'yes' : 'no';
+		if (val.type === 'bool') return val.value ? 'true' : 'false';
 		if (val.type === 'str') return `"${val.value}"`;
 		if (val.type === 'arr') return `[${val.value.map(item => valToString(item, true)).join(', ')}]`;
 		if (val.type === 'null') return '(null)';
@@ -112,3 +112,8 @@ export function jsToVal(val: any): Value {
 	}
 	return NULL;
 }
+
+export function getLangVersion(input: string): string | null {
+	const match = /^\s*\/\/\/\s*@\s*([A-Z0-9_.-]+)(?:[\r\n][\s\S]*)?$/i.exec(input);
+	return (match != null) ? match[1] : null;
+}
diff --git a/src/node.ts b/src/node.ts
index e63ed69..ba5a45e 100644
--- a/src/node.ts
+++ b/src/node.ts
@@ -233,15 +233,9 @@ export type NInfix = {
 	type: 'infix'; // 中置演算子式
 	loc?: Loc; // コード位置
 	operands: Node[]; // 項
-	operators: NOperator[]; // 演算子
+	operators: string[]; // 演算子
 };
 
-export type NOperator = {
-	type: 'operator'; // 中置演算子
-	loc?: Loc; // コード位置
-	op: string; // 実際の記号列
-}
-
 // TODO: analyze前のNodeとanalyze後のNodeが区別されておらず気持ち悪いのをなんとかしたい
 export type Node =
 	NDef |
@@ -275,5 +269,4 @@ export type Node =
 	NNs |
 	NMeta |
 	NAttr |
-	NInfix |
-	NOperator;
+	NInfix;
diff --git a/src/parser/parser.peggy b/src/parser/parser.peggy
index e42cbf1..13d5d63 100644
--- a/src/parser/parser.peggy
+++ b/src/parser/parser.peggy
@@ -103,7 +103,6 @@ Statement
 	= VarDef      // "#" NAME | "$" NAME
 	/ FnDef       // "@"
 	/ Out         // "<:"
-	/ Debug       // "<<<"
 	/ Return      // "return"
 	/ Attr        // "+"
 	/ ForOf       // "~~" | "each"
@@ -145,7 +144,7 @@ Literal
 	= Tmpl        // "`"
 	/ Str         // "\""
 	/ Num         // "+" | "-" | "1"~"9"
-	/ Bool        // "yes" | "no" | "+" | "-"
+	/ Bool        // "true" | "false"
 	/ Null        // "null"
 	/ Obj         // "{"
 	/ Arr         // "["
@@ -155,7 +154,7 @@ Literal
 StaticLiteral
 	= Num         // "+" "1"~"9" | "-" "1"~"9" | "1"~"9"
 	/ Str         // "\""
-	/ Bool        // "yes" | "no" | "+" | "-"
+	/ Bool        // "true" | "false"
 	/ StaticArr   // "["
 	/ StaticObj   // "{"
 	/ Null        // "null"
@@ -201,12 +200,6 @@ Out
 	= "<:" _* expr:Expr
 { return createNode('call', { name: 'print', args: [expr] }); }
 
-// debug statement
-
-Debug
-	= "<<<" _* expr:Expr
-{ return createNode('debug', { expr }); }
-
 // attribute statement
 
 // Note: Attribute will be combined with def node when parsing is complete.
@@ -351,7 +344,7 @@ ReservedOps
 	= "<<<" / "=>" / "<-" / "+<-" / "-<-"
 
 Op
-	= !(ReservedOps ![-+*/%&|=~<>!?]) [-+*/%&|=~<>!?]+ { return createNode('operator', { op: text() }); }
+	= !(ReservedOps ![-+*/%&|=~<>!?]) [-+*/%&|=~<>!?]+ { return text(); }
 
 // if statement
 
@@ -478,11 +471,11 @@ Bool
 	/ False
 
 True
-	= "yes" ![A-Z0-9_:]i
+	= "true" ![A-Z0-9_:]i
 { return createNode('bool', { value: true }); }
 
 False
-	= "no" ![A-Z0-9_:]i
+	= "false" ![A-Z0-9_:]i
 { return createNode('bool', { value: false }); }
 
 // null literal
diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts
index fc0bebd..04c9459 100644
--- a/src/parser/plugins/validate-keyword.ts
+++ b/src/parser/plugins/validate-keyword.ts
@@ -3,8 +3,6 @@ import { Node } from '../../node';
 
 const reservedWord = [
 	'null',
-	'yes',
-	'no',
 	'true',
 	'false',
 	'each',
@@ -24,7 +22,6 @@ const reservedWord = [
 	// 'attribute',
 	// 'static',
 	// 'const',
-	// 'null',
 	// 'var',
 	// 'def',
 	// 'fn',
diff --git a/src/serializer.ts b/src/serializer.ts
index 5972203..40bbda3 100644
--- a/src/serializer.ts
+++ b/src/serializer.ts
@@ -1,4 +1,4 @@
-import { Node, NDef, NAssign, NCall, NReturn, NIf, NFor, NForOf, NVar, NNull, NBool, NNum, NStr, NArr, NFn, NObj, NProp, NPropCall, NIndex, NBlock, NTmpl, NNs, NMatch, NMeta, NPropAssign, NIndexAssign, NInc, NDec, NAttr, NBreak, NLoop, NContinue, NInfix, NOperator } from './node';
+import { Node, NDef, NAssign, NCall, NReturn, NIf, NFor, NForOf, NVar, NNull, NBool, NNum, NStr, NArr, NFn, NObj, NProp, NPropCall, NIndex, NBlock, NTmpl, NNs, NMatch, NMeta, NPropAssign, NIndexAssign, NInc, NDec, NAttr, NBreak, NLoop, NContinue, NInfix } from './node';
 
 const types = {
 	def: 0,
@@ -33,7 +33,6 @@ const types = {
 	loop: 29,
 	continue: 30,
 	infix: 31,
-	operator: 32,
 };
 
 type Bin = any[];
@@ -72,8 +71,7 @@ export function serializeOne(node: Node | null | undefined): Bin | null {
 		case 'break': return [types[node.type], null];
 		case 'loop': return [types[node.type], serialize(node.statements)];
 		case 'continue': return [types[node.type], null];
-		case 'infix': return [types[node.type], serialize(node.operands), serialize(node.operators)];
-		case 'operator': return [types[node.type], node.op];
+		case 'infix': return [types[node.type], serialize(node.operands), node.operators];
 	}
 }
 
@@ -116,8 +114,7 @@ export function deserializeOne(bin: Bin | null): Node | undefined {
 		case types.break: return { type, name: bin[1], value: deserializeOne(null) } as NBreak;
 		case types.loop: return { type, statements: deserialize(bin[1]), } as NLoop;
 		case types.continue: return { type, name: bin[1], value: deserializeOne(null) } as NContinue;
-		case types.infix: return { type, operands: deserialize(bin[1]), operators: deserialize(bin[2])} as NInfix;
-		case types.operator: return { type, op: bin[1] } as NOperator;
+		case types.infix: return { type, operands: deserialize(bin[1]), operators: bin[2]} as NInfix;
 	}
 	throw new Error('deserialization failed');
 }
diff --git a/translations/en/CHANGELOG.md b/translations/en/CHANGELOG.md
new file mode 100644
index 0000000..d7a2cdf
--- /dev/null
+++ b/translations/en/CHANGELOG.md
@@ -0,0 +1,22 @@
+# (unreleased)
+## Breaking changes
+- `? ~ .? ~ .` → `if ~ elif ~ else`
+- `? x { 42 => yes }` → `match x { 42 => yes }`
+- `_` → `null`
+- `<<` → `return`
+- `~` → `for`
+- `~~` → `each`
+- `+ attributeName attributeValue` → `#[attributeName attributeValue]`
+- Removed `+`/`-` notation for truth values.
+- for and each no longer return an array
+
+## Features
+- `continue`
+- `break`
+- `loop`
+
+## Fixes
+- Fixed an issue where empty functions could not be defined.
+- Fixed an issue where empty scripts were not allowed.
+- Fixed increment and decrement of namespaced variables.
+- Fixed an issue that prevented assignment to variables with namespaces.
diff --git a/translations/en/CONTRIBUTING.md b/translations/en/CONTRIBUTING.md
new file mode 100644
index 0000000..82a0e01
--- /dev/null
+++ b/translations/en/CONTRIBUTING.md
@@ -0,0 +1,62 @@
+# Contribution guide
+Thank you for your interest in our project!
+This document contains all the information you need to contribute to the project.
+
+## Before implementing
+When you want to add a feature or fix a bug, first have the design and policy reviewed in an Issue or something similar (if it is not there, please create one). Without this step, there is a high possibility that the PR will not be merged even if you have implemented it.
+
+Also, when you start implementation, please assign yourself to the issue in question (if you cannot do it yourself, please ask another member to assign you).
+By indicating that you are going to implement it, you can avoid conflicts.
+
+## Tools
+### Jest
+In this project, we have introduced [Jest](https://jestjs.io/) as a testing framework.
+Tests are placed in the [`/test` directory](./test).
+
+Testing is done automatically by CI for each commit/each PR.
+To run the test in your local environment, run `npm run test`.
+
+### tsd
+In this project, we have introduced [tsd](https://github.com/SamVerschueren/tsd) to test the types.
+By using tsd, we can ensure that the type is what we intended it to be.
+Type tests with tsd are placed in the [`/test-d` directory](./test-d).
+
+Testing is done automatically by CI for each commit/each PR.
+To run the test in your local environment, run `npm run test`.
+
+### API Extractor
+In this project, we have introduced [API Extractor](https://api-extractor.com/), which is responsible for generating API reports.
+The API report is a snapshot of the API, so to speak, and contains the definitions of the various functions and types that the library exposes (exports) to the outside world. When you run the `npm run api` command, the current report will be generated in the [`/etc` directory](./etc).
+
+When there is a change in the API being exported, the content of the report generated will naturally change, so for example, by comparing the report generated in the develop branch with the report generated in the PR branch, you can use it to detect unintended destructive changes or to check the impact of destructive changes.
+Also, inside CI, which is executed for each commit and each PR, API reports are generated each time to check if there are any differences with the existing reports. If there are any differences, an error will occur.
+
+When you create a PR, please run the `npm run api` command to generate API reports and commit any differences.
+Committing the report will show that the disruptive change was intended, and as mentioned above, the difference between the reports will make it easier to review the scope of the impact.
+
+### Codecov
+In this project, we have introduced [Codecov](https://about.codecov.io/) to measure coverage. Coverage is a measure of how much of the code is covered by the tests.
+
+The coverage measurement is done automatically by CI and no special operation is required. The coverage can be viewed from [here](https://codecov.io/gh/syuilo/aiscript).
+
+Also, for each PR, the coverage of that branch is automatically calculated, and the Codecov bot comments a report containing the difference between the coverage of the branch and the coverage of the merged branch. This allows you to see how much coverage is increased/decreased by merging the PRs.
+
+## Tips for Reviewers
+You can read a lot about it in the template for creating a PR. (Maybe you can move it to this document?).
+Also, be aware of the "review perspective" described below.
+
+## Tips for Reviewers
+- Be willing to comment on the good points as well as the things you want fixed.
+    - It will motivate you to contribute.
+
+### Review perspective
+- security
+    - Doesn't merging this PR create a vulnerability?
+- Performance
+    - Will merging these PRs cause unexpected performance degradation?
+    - Is there a more efficient way?
+- Test
+    - Is the expected behavior ensured by the test?
+    - Are there any loose ends or leaks?
+    - Are you able to check for abnormal systems?
+    
\ No newline at end of file
diff --git a/translations/en/README.md b/translations/en/README.md
new file mode 100644
index 0000000..ab4d387
--- /dev/null
+++ b/translations/en/README.md
@@ -0,0 +1,52 @@
+<h1><img src="../../aiscript.svg" alt="AiScript" width="300"></h1>
+
+[![](https://img.shields.io/npm/v/@syuilo/aiscript.svg?style=flat-square)](https://www.npmjs.com/package/@syuilo/aiscript)
+![](https://github.com/syuilo/aiscript/workflows/ci/badge.svg)
+[![codecov](https://codecov.io/gh/syuilo/aiscript/branch/master/graph/badge.svg?token=R6IQZ3QJOL)](https://codecov.io/gh/syuilo/aiscript)
+[![](https://img.shields.io/badge/license-MIT-444444.svg?style=flat-square)](http://opensource.org/licenses/MIT)
+[![](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square&logo=github)](http://makeapullrequest.com)
+
+> **AiScript** is a scripting language runing on JavaScript. Not altJS.
+
+[Play online ▶](https://syuilo.github.io/aiscript/)
+
+AiScript is a multi-paradigm programming language that runs on JavaScript, not AltJS (1).
+
+* First-class support for arrays, objects, functions, etc.
+* Flexibility, including conditional branches and blocks can be treated as expressions
+* Easy to write, no need for semicolons or commas
+* Runs in a secure(2) sandbox environment
+* Variables and functions can be provided from the host
+
+> (1) ... It "runs on" JavaScript, not is "converted" to JavaScript. Therefore, it is not AltJS.
+
+> (2) ... Not being able to access the host's information.
+
+This repository contains parsers and processors implemented in JavaScript.
+
+Note: AiScript and [Misskey](https://github.com/syuilo/misskey) are completely independent projects. AiScript does not prescribe any specific host, but Misskey is the largest user of AiScript (today!)
+
+## Getting started (language)
+[See here](./docs/get-started.md)
+
+## Getting started (host implementation)
+todo
+
+## Example programs
+### Hello world
+```
+<: "Hello, world!"
+```
+
+### Fizz Buzz
+```
+for (#i, 100) {
+  <: if ((i % 15) = 0) "FizzBuzz"
+    elif ((i % 3) = 0) "Fizz"
+    elif ((i % 5) = 0) "Buzz"
+    else i
+}
+```
+
+## License
+[MIT](LICENSE)
diff --git a/translations/en/docs/get-started.md b/translations/en/docs/get-started.md
new file mode 100644
index 0000000..28ce414
--- /dev/null
+++ b/translations/en/docs/get-started.md
@@ -0,0 +1,240 @@
+# Getting started
+## Introduction
+AiScript is a programming language.
+This document assumes that you already have some programming knowledge.
+Therefore, I will only write about the syntax and specifications of AiScript, and leave out the description of programming itself.
+
+## Hello, world!
+In AiScript, it is written as follows:
+```
+print("Hello, world!")
+```
+
+`print( ~ )` is a function call. The name of the function to be called is written before the parentheses, and the arguments are written inside the parentheses.
+If there are multiple arguments, they are listed separated by `,`.
+The details of the function are described later.
+
+`"~"` is a string literal. Anything enclosed in `"` is a string.
+
+Incidentally, `print( ~ )` has a sugar-coated syntax and can be written as follows:
+```
+<: "Hello, world!"
+```
+
+## Comments
+AiScript comments start with `//`.
+Comments do not affect the behavior of the program.
+
+```
+// this is a comment
+```
+
+## Primary object
+<table>
+	<tr><th>Type</th><th>Form</th><th>Example of literal</th></tr>
+	<tr><td>string</td><td><code>str</code></td><td><code>"kawaii"</code></td></tr>
+	<tr><td>numeric</td><td><code>num</code></td><td><code>42</code></td></tr>
+	<tr><td>truth-value</td><td><code>bool</code></td><td><code>yes</code>/<code>no</code></td></tr>
+	<tr><td>array</td><td><code>arr</code></td><td><code>["ai" "chan" "cute"]</code></td></tr>
+	<tr><td>object</td><td><code>obj</code></td><td><code>{ foo: "bar"; a: 42; }</code></td></tr>
+	<tr><td>null</td><td><code>null</code></td><td><code>null</code></td></tr>
+	<tr><td>function</td><td><code>fn</code></td><td><code>@(x) { x }</code></td></tr>
+</table>
+
+## Variables
+### Declaration
+The variable declaration is written as follows:
+```
+#message = "Hello"
+```
+
+Write the name of the variable after `#` and the value after `=`.
+
+In AiScript, variables declared in this way are immutable. In other words, you cannot change the value of a variable later.
+To create a variable that can be reassigned, declare the variable with `$` instead of `#`. Also, use `<-` for assignments. Example:
+```
+// Declare a mutable variable.
+$message <- "Hello"
+
+// reassignment
+message <- "Hi"
+
+// Reassigning again
+message <- "Yo"
+```
+
+### Reference
+When referring to the value of a variable, simply write the name of the variable:
+```
+print(message)
+```
+
+## Array
+Enumerate expressions in an array `[]`, separated by spaces.
+```
+["ai" "chan" "kawaii"]
+```
+
+To access an element of an array, write `[<index>]`. The index starts at 1.
+```
+#arr = ["ai" "chan" "kawaii"]
+<: arr[1] // "ai"
+```
+
+## Object
+```
+{
+	foo: "bar"
+	answer: 42
+	nested: {
+		some: "thing"
+	}
+}
+```
+
+## Arithmetic operation
+The operation is
+```
+(1 + 1)
+```
+This is a sugar-coated syntax for function calls. This is the sugar-coated syntax for function calls, and is actually interpreted as such:
+```
+Core:add(1, 1)
+```
+
+All of the functions listed below can be used as sugar-coated syntax such as `(1 + 1)`.
+Note that it cannot be written as `(1 + 1 + 1)`, but must be nested as `(1 + (1 + 1))`.
+
+<table>
+	<tr><th>Function</th><th>Operator</th><th>Meaning</th></tr>
+	<tr><td><code>Core:add</code></td><td><code>+</code></td><td>addition</td></tr>
+	<tr><td><code>Core:sub</code></td><td><code>-</code></td><td>subtraction</td></tr>
+	<tr><td><code>Core:mul</code></td><td><code>*</code></td><td>multiplication</td></tr>
+	<tr><td><code>Core:div</code></td><td><code>/</code></td><td>division</td></tr>
+	<tr><td><code>Core:mod</code></td><td><code>%</code></td><td>remainder</td></tr>
+	<tr><td><code>Core:eq</code></td><td><code>=</code></td><td>equal</td></tr>
+	<tr><td><code>Core:and</code></td><td><code>&</code></td><td>and</td></tr>
+	<tr><td><code>Core:or</code></td><td><code>|</code></td><td>or</td></tr>
+	<tr><td><code>Core:gt</code></td><td><code>></code></td><td>greater than</td></tr>
+	<tr><td><code>Core:lt</code></td><td><code><</code></td><td>less than</td></tr>
+</table>
+
+## Block
+A block is a collection of processes, written as `{ ~ }`.
+The expression written at the end of the block will be returned as the value of the block.
+```
+#foo = {
+	#a = 1
+	#b = 2
+	(a + b)
+}
+
+<: foo // 3
+```
+
+The block itself is also a formula.
+
+## Conditional branching
+The conditional branch in AiScript is written as follows:
+```
+if (a = b) {
+	<: "a is equal to b"
+}
+```
+
+After `if`, write an expression (condition) that returns a bool, followed by an expression (then clause) that will be evaluated if the condition is met.
+You can add `else` after the `then' clause and add an expression to handle the case when the condition is not met:
+```
+if (a = b) {
+	<: "a is equal to b"
+} else {
+	<: "a is not equal to b"
+}
+```
+
+You can also write conditional expressions after `elif` to make multiple conditional decisions:
+```
+if (a = b) {
+	<: "a is equal to b"
+} elif (a > b) {
+	<: "a is grater than b"
+} else {
+	<: "a is less than b"
+}
+```
+
+Since these conditional branches are expressions, they can return a value in the block:
+```
+<: if (a = b) {
+	"a is equal to b"
+} elif (a > b) {
+	"a is grater than b"
+} else {
+	"a is less than b"
+}
+```
+
+## Loop
+To iterate in AiScript, write the following:
+```
+for (#i, 100) {
+	<: i
+}
+```
+After `for`, write the name of the iterator variable, and after `,` write an expression that returns the number of iterations. Write the process to be repeated in the block that follows.
+The iterator variable can also be omitted:
+```
+for (100) {
+	<: "yo"
+}
+```
+
+## Loop (array)
+You can use `each` to repeat items in an array:
+```
+#items = ["a" "b" "c"]
+each (#item, items) {
+	<: item
+}
+```
+
+## Function
+### Function definition
+Write the following:
+```
+@fn(x) {
+	(x * 2)
+}
+```
+
+Write the function name after `@` and the argument definition in parentheses. After that, the block becomes the process of the function.
+
+### return
+The value of the expression written at the end of the function will be the return value of the function, but if you want to return a value in the middle of the function, use `return`.
+
+## Template
+You can embed variables and expressions in a string:
+```
+#ai = "kawaii"
+<: `Hello, {ai} world!`
+```
+
+## Metadata
+This function allows you to embed metadata in AiScript files.
+```
+### {
+	name: "example"
+	version: 42
+	keywords: ["foo" "bar" "baz"]
+}
+```
+# Examples
+## FizzBuzz
+```
+for (#i, 100) {
+	<: ? ((i % 15) = 0) "FizzBuzz"
+		.? ((i % 3) = 0) "Fizz"
+		.? ((i % 5) = 0) "Buzz"
+		. i
+}
+```
diff --git a/translations/en/docs/std.md b/translations/en/docs/std.md
new file mode 100644
index 0000000..33b8b33
--- /dev/null
+++ b/translations/en/docs/std.md
@@ -0,0 +1,194 @@
+## std
+
+### @print(message: str): void
+Display a string on the screen.  
+
+### @readline(message: str): str
+Accept input of character strings.  
+
+## :: Core
+
+### #Core:v
+Type: `str`.  
+AiScript version.
+
+### @Core:type(v: value): str
+Get the type name of the value.  
+
+### @Core:to_str(v: value): str
+Get a string representing a value.  
+
+## :: Util
+
+### @Util:uuid(): str
+Generate a new UUID.
+
+## :: Json
+
+### @Json:stringify(v: value): str
+Generate JSON.
+
+### @Json:parse(json: str): value
+Parse JSON.  
+
+## :: Date
+
+### @Date:now(): num
+Get the current time.
+
+### @Date:year(): num
+Get the year of the current time.  
+
+### @Date:month(): num
+Get the month of the current time.  
+
+### @Date:day(): num
+Get the day of the current time.  
+
+### @Date:hour(): num
+Get the current time in hours.  
+
+### @Date:minute(): num
+Get the minutes of the current time.  
+
+### @Date:second(): num
+Get the current time in seconds.  
+
+### @Date:parse(date: str): num
+
+## :: Math
+
+### #Math:PI
+Type: `num`.  
+It's pi.
+
+### @Math:sin(x: num): num
+Calculate the sine.  
+
+### @Math:cos(x: num): num
+Calculate the cosine.  
+
+### @Math:abs(x: num): num
+Calculate the absolute value.  
+
+### @Math:sqrt(x: num): num
+Calculate the square root.  
+
+### @Math:round(x: num): num
+
+### @Math:floor(x: num): num
+
+### @Math:min(a: num, b: num): num
+Get the smaller value.  
+
+### @Math:max(a: num, b: num): num
+Get the larger value.  
+
+### @Math:rnd(min?: num, max?: num): num
+Generate a random number.  
+
+### @Math:gen_rng(seed: num | str): fn
+
+## :: Num
+
+### @Num:to_hex(x: num): str
+Generates a hexadecimal string from a number.  
+
+### @Num:from_hex(hex: str): num
+Generates a numeric value from a hexadecimal string.  
+
+## :: Str
+
+### #Str:lf
+Type: `str`.  
+Newline code (LF).
+
+### @Str:to_num(v: num | str): num | null
+
+### @Str:len(v: str): num
+Get the length of a string.
+
+### @Str:pick(v: str, i: num): str | null
+
+### @Str:incl(v: str, keyword: str): bool
+
+### @Str:slice(v: str, begin: num, end: num): str
+Get the specified portion of a string.  
+
+### @Str:split(v: str, splitter?: str): arr<str>
+
+### @Str:replace(v: str, old: str, new: str): str
+
+### @Str:index_of(v: str, search: str): num
+
+### @Str:trim(v: str): str
+
+### @Str:upper(v: str): str
+
+### @Str:lower(v: str): str
+
+## :: Arr
+
+### @Arr:len(v: arr): num
+Get the number of array elements.  
+
+### @Arr:push(v: arr, i: value): null
+Adds an element to the end of an array.  
+
+### @Arr:unshift(v: arr, i: value): null
+
+### @Arr:pop(v: arr): value
+Extracts the last element of an array.  
+
+### @Arr:shift(v: arr): value
+
+### @Arr:concat(a: arr, b: arr): arr
+Concatenate the arrays.
+
+### @Arr:join(v: arr<str>, joiner?: str): str
+Concatenates an array of strings and returns them as a single string.  
+
+### @Arr:slice(v: arr, begin: num, end: num): arr
+
+### @Arr:incl(v: arr, i: str | num | bool | null): bool
+Returns whether the array contains the specified value or not.  
+
+### @Arr:map(v: arr, f: fn): arr
+
+### @Arr:filter(v: arr, f: fn): arr
+
+### @Arr:reduce(v: arr, f: @(acm: value, item: value, index: num) { value }, initial: value): value
+
+### @Arr:find(v: arr, f: @(item: value, index: num) { bool }): value
+Searches for an element in an array.  
+
+### @Arr:reverse(v: arr): null
+Invert the array.  
+
+### @Arr:copy(v: arr): arr
+Generate a copy of the array.
+
+## :: Obj
+
+### @Obj:keys(v: obj): arr
+
+### @Obj:kvs(v: obj): arr
+
+### @Obj:get(v: obj, key: str): value
+
+### @Obj:set(v: obj, key: str, val: value): null
+
+### @Obj:has(v: obj, key: str): bool
+
+### @Obj:copy(v: obj): obj
+Generate a copy of the object.
+
+## :: Async
+
+### @Async:interval(interval: num, callback: fn, immediate?: bool): fn
+Calls the callback function at the specified period.  
+Returns the stop function as a return value.  
+
+### @Async:timeout(delay: num, callback: fn):
+Calls the callback function after the specified time has elapsed.  
+Returns the stop function as a return value.  
diff --git a/translations/en/docs/syntax.md b/translations/en/docs/syntax.md
new file mode 100644
index 0000000..2877136
--- /dev/null
+++ b/translations/en/docs/syntax.md
@@ -0,0 +1,76 @@
+# Syntax
+## Comments
+Lines that begin with `//` are comments and do not affect the behavior of the program.
+
+```
+// this is a comment
+```
+
+## Variable declaration
+```
+#answer = 42
+```
+
+## if
+```
+? eq(answer, 42) {
+	print("correct answer")
+}
+```
+
+else:
+```
+? eq(answer, 42) {
+	print("correct answer")
+} . {
+	print("wrong answer")
+}
+```
+
+else if:
+```
+? eq(answer, "bebeyo") {
+	print("correct answer")
+} .? eq(answer, "ai") {
+	print("kawaii")
+} . {
+	print("wrong answer")
+}
+```
+
+as expression:
+```
+#result =
+	? eq(answer, "bebeyo") { "correct answer" }
+	.? eq(answer, "ai") { "kawaii" }
+	. { "wrong answer" }
+
+print(result)
+```
+
+## for
+```
+for #i, 10 {
+	print(i)
+}
+```
+
+## Block
+```
+#foo = {
+	#x = 1
+	#y = 2
+	add(x, y)
+}
+
+print(foo) // 3
+```
+
+## Function
+```
+@inc(x) {
+	add(x, 1)
+}
+
+print(inc(42)) // 43
+```


Test patch:
diff --git a/test/index.ts b/test/index.ts
index 817840d..2e879cb 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -4,7 +4,7 @@
  */
 
 import * as assert from 'assert';
-import { parse, Parser, serialize, deserialize } from '../src';
+import { parse, Parser, serialize, deserialize, utils } from '../src';
 import { AiScript } from '../src/interpreter';
 import { NUM, STR, NULL, ARR, OBJ, BOOL } from '../src/interpreter/value';
 import { NAttr, Node } from '../src/node';
@@ -69,9 +69,9 @@ it('legacy parser api', async () => {
 });
 
 describe('ops', () => {
-	it('=', async () => {
-		eq(await exe('<: (1 = 1)'), BOOL(true));
-		eq(await exe('<: (1 = 2)'), BOOL(false));
+	it('==', async () => {
+		eq(await exe('<: (1 == 1)'), BOOL(true));
+		eq(await exe('<: (1 == 2)'), BOOL(false));
 	});
 
 	it('!=', async () => {
@@ -79,18 +79,18 @@ describe('ops', () => {
 		eq(await exe('<: (1 != 1)'), BOOL(false));
 	});
 
-	it('&', async () => {
-		eq(await exe('<: (yes & yes)'), BOOL(true));
-		eq(await exe('<: (yes & no)'), BOOL(false));
-		eq(await exe('<: (no & yes)'), BOOL(false));
-		eq(await exe('<: (no & no)'), BOOL(false));
+	it('&&', async () => {
+		eq(await exe('<: (true && true)'), BOOL(true));
+		eq(await exe('<: (true && false)'), BOOL(false));
+		eq(await exe('<: (false && true)'), BOOL(false));
+		eq(await exe('<: (false && false)'), BOOL(false));
 	});
 
-	it('|', async () => {
-		eq(await exe('<: (yes | yes)'), BOOL(true));
-		eq(await exe('<: (yes | no)'), BOOL(true));
-		eq(await exe('<: (no | yes)'), BOOL(true));
-		eq(await exe('<: (no | no)'), BOOL(false));
+	it('||', async () => {
+		eq(await exe('<: (true || true)'), BOOL(true));
+		eq(await exe('<: (true || false)'), BOOL(true));
+		eq(await exe('<: (false || true)'), BOOL(true));
+		eq(await exe('<: (false || false)'), BOOL(false));
 	});
 
 	it('+', async () => {
@@ -140,7 +140,7 @@ describe('ops', () => {
 	it('precedence', async () => {
 		eq(await exe('<: 1 + 2 * 3 + 4'), NUM(11));
 		eq(await exe('<: 1 + 4 / 4 + 1'), NUM(3));
-		eq(await exe('<: 1 + 1 = 2 & 2 * 2 = 4'), BOOL(true));
+		eq(await exe('<: 1 + 1 == 2 && 2 * 2 == 4'), BOOL(true));
 		eq(await exe('<: (1 + 1) * 2'), NUM(4));
 	});
 
@@ -163,23 +163,23 @@ describe('Infix expression', () => {
 
 	it('syntax symbols vs infix operators', async () => {
 		const res = await exe(`
-		<: match yes {
-			1 = 1 => "yes"
-			1 < 1 => "no"
+		<: match true {
+			1 == 1 => "true"
+			1 < 1 => "false"
 		}
 		`);
-		eq(res, STR('yes'));
+		eq(res, STR('true'));
 	});
 
 	it('number + if expression', async () => {
-		eq(await exe('<: 1 + if yes 1 else 2'), NUM(2));
+		eq(await exe('<: 1 + if true 1 else 2'), NUM(2));
 	});
 
 	it('number + match expression', async () => {
 		const res = await exe(`
-			<: 1 + match 2 = 2 {
-				yes => 3
-				no  => 4
+			<: 1 + match 2 == 2 {
+				true => 3
+				false  => 4
 			}
 		`);
 		eq(res, NUM(4));
@@ -342,7 +342,7 @@ it('Closure (counter)', async () => {
 it('Recursion', async () => {
 	const res = await exe(`
 	@fact(n) {
-		if (n = 0) { 1 } else { (fact((n - 1)) * n) }
+		if (n == 0) { 1 } else { (fact((n - 1)) * n) }
 	}
 
 	<: fact(5)
@@ -350,11 +350,11 @@ it('Recursion', async () => {
 	eq(res, NUM(120));
 });
 
-it('Var name starts with \'no\'', async () => {
+it('Var name starts with reserved word', async () => {
 	const res = await exe(`
-	#note = "ai"
+	#falsecat = "ai"
 
-	<: note
+	<: falsecat
 	`);
 	eq(res, STR('ai'));
 });
@@ -541,9 +541,9 @@ it('Fizz Buzz', async () => {
 	#res = []
 	for (#i, 15) {
 		#msg =
-			if ((i % 15) = 0) "FizzBuzz"
-			elif ((i % 3) = 0) "Fizz"
-			elif ((i % 5) = 0) "Buzz"
+			if ((i % 15) == 0) "FizzBuzz"
+			elif ((i % 3) == 0) "Fizz"
+			elif ((i % 5) == 0) "Buzz"
 			else i
 		Arr:push(res msg)
 	}
@@ -584,7 +584,7 @@ it('SKI', async () => {
 
 		// extract
 		@x(v) {
-			if (Core:type(v) = "arr") { c(v) } else { v }
+			if (Core:type(v) == "arr") { c(v) } else { v }
 		}
 
 		// rec
@@ -649,7 +649,7 @@ describe('Return', () => {
 	it('Early return', async () => {
 		const res = await exe(`
 		@f() {
-			if yes {
+			if true {
 				return "ai"
 			}
 
@@ -663,8 +663,8 @@ describe('Return', () => {
 	it('Early return (nested)', async () => {
 		const res = await exe(`
 		@f() {
-			if yes {
-				if yes {
+			if true {
+				if true {
 					return "ai"
 				}
 			}
@@ -679,7 +679,7 @@ describe('Return', () => {
 	it('Early return (nested) 2', async () => {
 		const res = await exe(`
 		@f() {
-			if yes {
+			if true {
 				return "ai"
 			}
 
@@ -687,7 +687,7 @@ describe('Return', () => {
 		}
 
 		@g() {
-			if (f() = "ai") {
+			if (f() == "ai") {
 				return "kawaii"
 			}
 
@@ -702,7 +702,7 @@ describe('Return', () => {
 	it('Early return without block', async () => {
 		const res = await exe(`
 		@f() {
-			if yes return "ai"
+			if true return "ai"
 
 			"pope"
 		}
@@ -710,6 +710,38 @@ describe('Return', () => {
 		`);
 		eq(res, STR('ai'));
 	});
+
+	it('return inside for', async () => {
+		const res = await exe(`
+		@f() {
+			$count <- 0
+			for (#i, 100) {
+				count +<- 1
+				if (i == 42) {
+					return count
+				} 
+			}
+		}
+		<: f()
+		`);
+		eq(res, NUM(42));
+	});
+
+	it('return inside loop', async () => {
+		const res = await exe(`
+		@f() {
+			$count <- 0
+			loop {
+				count +<- 1
+				if (count == 42) {
+					return count
+				} 
+			}
+		}
+		<: f()
+		`);
+		eq(res, NUM(42));
+	});
 });
 
 describe('Block', () => {
@@ -731,7 +763,7 @@ describe('if', () => {
 	it('if', async () => {
 		const res1 = await exe(`
 		$msg <- "ai"
-		if yes {
+		if true {
 			msg <- "kawaii"
 		}
 		<: msg
@@ -740,7 +772,7 @@ describe('if', () => {
 
 		const res2 = await exe(`
 		$msg <- "ai"
-		if no {
+		if false {
 			msg <- "kawaii"
 		}
 		<: msg
@@ -751,7 +783,7 @@ describe('if', () => {
 	it('else', async () => {
 		const res1 = await exe(`
 		$msg <- null
-		if yes {
+		if true {
 			msg <- "ai"
 		} else {
 			msg <- "kawaii"
@@ -762,7 +794,7 @@ describe('if', () => {
 
 		const res2 = await exe(`
 		$msg <- null
-		if no {
+		if false {
 			msg <- "ai"
 		} else {
 			msg <- "kawaii"
@@ -775,9 +807,9 @@ describe('if', () => {
 	it('elif', async () => {
 		const res1 = await exe(`
 		$msg <- "bebeyo"
-		if no {
+		if false {
 			msg <- "ai"
-		} elif yes {
+		} elif true {
 			msg <- "kawaii"
 		}
 		<: msg
@@ -786,9 +818,9 @@ describe('if', () => {
 
 		const res2 = await exe(`
 		$msg <- "bebeyo"
-		if no {
+		if false {
 			msg <- "ai"
-		} elif no {
+		} elif false {
 			msg <- "kawaii"
 		}
 		<: msg
@@ -799,9 +831,9 @@ describe('if', () => {
 	it('if ~ elif ~ else', async () => {
 		const res1 = await exe(`
 		$msg <- null
-		if no {
+		if false {
 			msg <- "ai"
-		} elif yes {
+		} elif true {
 			msg <- "chan"
 		} else {
 			msg <- "kawaii"
@@ -812,9 +844,9 @@ describe('if', () => {
 
 		const res2 = await exe(`
 		$msg <- null
-		if no {
+		if false {
 			msg <- "ai"
-		} elif no {
+		} elif false {
 			msg <- "chan"
 		} else {
 			msg <- "kawaii"
@@ -826,12 +858,12 @@ describe('if', () => {
 
 	it('expr', async () => {
 		const res1 = await exe(`
-		<: if yes "ai" else "kawaii"
+		<: if true "ai" else "kawaii"
 		`);
 		eq(res1, STR('ai'));
 
 		const res2 = await exe(`
-		<: if no "ai" else "kawaii"
+		<: if false "ai" else "kawaii"
 		`);
 		eq(res2, STR('kawaii'));
 	});
@@ -908,7 +940,7 @@ describe('loop', () => {
 		const res = await exe(`
 		$count <- 0
 		loop {
-			if (count = 10) break
+			if (count == 10) break
 			count <- (count + 1)
 		}
 		<: count
@@ -922,8 +954,8 @@ describe('loop', () => {
 		$b <- []
 		loop {
 			$x <- Arr:shift(a)
-			if (x = "chan") continue
-			if (x = "!") break
+			if (x == "chan") continue
+			if (x == "!") break
 			Arr:push(b, x)
 		}
 		<: b
@@ -970,7 +1002,7 @@ describe('for', () => {
 		const res = await exe(`
 		$count <- 0
 		for (#i, 20) {
-			if (i = 11) break
+			if (i == 11) break
 			count <- (count + i)
 		}
 		<: count
@@ -982,7 +1014,7 @@ describe('for', () => {
 		const res = await exe(`
 		$count <- 0
 		for (#i, 10) {
-			if (i = 5) continue
+			if (i == 5) continue
 			count <- (count + 1)
 		}
 		<: count
@@ -1016,7 +1048,7 @@ describe('for of', () => {
 		const res = await exe(`
 		#msgs = []
 		each #item, ["ai", "chan", "kawaii"] {
-			if (item = "kawaii") break
+			if (item == "kawaii") break
 			Arr:push(msgs, Arr:join([item, "!"]))
 		}
 		<: msgs
@@ -1089,14 +1121,14 @@ describe('namespace', () => {
 describe('literal', () => {
 	it('bool (true)', async () => {
 		const res = await exe(`
-		<: yes
+		<: true
 		`);
 		eq(res, BOOL(true));
 	});
 
 	it('bool (false)', async () => {
 		const res = await exe(`
-		<: no
+		<: false
 		`);
 		eq(res, BOOL(false));
 	});
@@ -1249,7 +1281,7 @@ describe('type declaration', () => {
 			x
 		}
 		
-		<: f([1, 2, 3], "a", @(n) { n = 1 })
+		<: f([1, 2, 3], "a", @(n) { n == 1 })
 		`);
 		eq(res, ARR([NUM(1), NUM(2), NUM(3), NUM(0), NUM(5)]));
 	});
@@ -1299,7 +1331,7 @@ describe('meta', () => {
 	describe('Boolean', () => {
 		it('valid', async () => {
 			const res = getMeta(`
-			### x yes
+			### x true
 			`);
 			eq(res, new Map([
 				['x', true]
@@ -1397,6 +1429,49 @@ describe('meta', () => {
 	});
 });
 
+describe('lang version', () => {
+	it('number', async () => {
+		const res = utils.getLangVersion(`
+		/// @2021
+		@f(x) {
+			x
+		}
+		`);
+		assert.strictEqual(res, '2021');
+	});
+
+	it('chars', async () => {
+		const res = utils.getLangVersion(`
+		/// @ canary
+		const a = 1
+		@f(x) {
+			x
+		}
+		f(a)
+		`);
+		assert.strictEqual(res, 'canary');
+	});
+
+	it('complex', async () => {
+		const res = utils.getLangVersion(`
+		/// @ 2.0-Alpha
+		@f(x) {
+			x
+		}
+		`);
+		assert.strictEqual(res, '2.0-Alpha');
+	});
+
+	it('no specified', async () => {
+		const res = utils.getLangVersion(`
+		@f(x) {
+			x
+		}
+		`);
+		assert.strictEqual(res, null);
+	});
+});
+
 describe('Attribute', () => {
 	it('single attribute with function (str)', async () => {
 		let node: Node;
@@ -1428,7 +1503,7 @@ describe('Attribute', () => {
 		const nodes = parser.parse(`
 		#[Endpoint { path: "/notes/create"; }]
 		#[Desc "Create a note."]
-		#[Cat yes]
+		#[Cat true]
 		@createNote(text) {
 			<: text
 		}