自分だけのプログラミング言語を作ろう

582 Views

January 30, 26

スライド概要

実際にプログラミング言語を作る過程を体験することでより深くプログラミング言語について理解する機会を提供します。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

自分だけの プログラミング言語を作ろう ashphy @[email protected] 2026/01/30

2.
[beta]
JSONPath実装を書いています
JSONから特定のデータを取得するためのクエリ言語 (RFC 9535)
{

}

"firstName": "John",
"lastName" : "doe",
"age"
: 26,
"address" : {
"streetAddress": "naist street",
"city"
: "Nara",
"postalCode"
: "630-0192"
},
"phoneNumbers": [
{
"type" : "iPhone",
"number": "0123-4567-8888"
},
{
"type" : "home",
"number": "0123-4567-8910"
}
]

JSON

$.phoneNumbers[:1].type
[ "iPhone" ]

JSONPathクエリ
取得結果

2

3.

身の回りのドメイン固有言語 (DSL) Twitterの検索クエリの解析 movie AND (love OR hate) -scary :) since:2015-12-21 3

4.

身の回りのドメイン固有言語 (DSL) Twitterの検索クエリの解析 両方を含む ポジティブな感情表現を含む movie AND (love OR hate) -scary :) since:2015-12-21 キーワード 優先順位の変更 NOT検索 期間指定 4

5.

自分でプログラミング言語を作ることができたら? 5

6.

自分だけの プログラミング言語を作ろう 6

7.

プログラム って? 7

8.

2 + 3 * 4 8

9.

前から処理していくだけではダメ 2 + 3 * 4 先に計算しないといけない もちろん前から処理するだけでいい文法を考えることもできる 9

10.

プログラムは木構造である + 2 + 3 * 4 2 * 3 構文木 4 10

11.

プログラムを実行する 根(ルート)から処理する + 2 + 3 * 4 2 * 3 4 11

12.

プログラムを実行する +の処理をするために子を見に行く 子がないので2を返して終わり 2 + 3 * 4 + 2 * 3 4 12

13.

プログラムを実行する もう一方の子を見に行く 両方の子は葉なので3*4を計算 2 + 3 * 4 + 2 * 3 4 13

14.

プログラムを実行する 子が12の値になる + 2 + 3 * 4 2 12 14

15.

プログラムを実行する 子の値がそろったので+が計算できる + 2 + 3 * 4 2 12 15

16.

プログラムを実行する 実行結果が得られる 14 ← 実行結果 2 + 3 * 4 16

17.

プログラムを実行する + 2 + 3 * 4 2 * 3 4 17

18.

プログラムを実行する + 2 + 3 * 4 2 * 3 深さ優先探索 4 18

20.

オリジナル言語を作る 20

21.

まずは言語仕様を決めよう ● 数値計算を行うプログラミング言語 ● 数値は0以上の整数のみ ● 演算は 加算、減算、乗算のみ ● () も使える 2 * (3 + 4) サンプルコード 21

22.

まずは言語仕様を決めよう ● 数値計算を行うプログラミング言語 ● 数値は0以上の整数のみ ● 演算は 加算、減算、乗算のみ 自然言語で書くと曖昧さが残る ● () も使える 2 * (3 + 4) サンプルコード 22

23.

文法仕様を定義するための文法 PEG Expression: Term (("+" | "-") Term)* Term: Factor ("*" Factor)* Factor: | "(" Expression ")" | Integer Integer: [0-9]+ 実際にはPEGではなく、EBNFやABNFが使われることが多い 23

24.

文法仕様を定義するための文法 PEG Expression: Term (("+" | "-") Term)* Term: *: 0回以上の繰り返し | : OR Factor ("*" Factor)* 正規表現の知識で読める Factor: | "(" Expression ")" | Integer Integer: [0-9]+ [0-9]: 文字クラス +: 1回以上の繰り返し 実際にはPEGではなく、EBNFやABNFが使われることが多い 24

25.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* 2 * (3 + 4) Term: Factor ("*" Factor)* Factor: | "(" Expression ")" | Integer Integer: [0-9]+ 25

26.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* ↓ 現在位置 2 * (3 + 4) Term: Factor ("*" Factor)* Factor: | "(" Expression ")" | Integer Integer: [0-9]+ 26

27.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: ↓ 現在位置 2 * (3 + 4) Expression (式) Factor ("*" Factor)* Factor: | "(" Expression ")" | Integer Integer: [0-9]+ 27

28.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: ↓ 現在位置 2 * (3 + 4) Expression (式) Factor ("*" Factor)* Term (項) Factor: | "(" Expression ")" | Integer Integer: [0-9]+ 28

29.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: ↓ 現在位置 2 * (3 + 4) Expression (式) Factor ("*" Factor)* Term (項) Factor: | "(" Expression ")" Factor (因子) | Integer Integer: [0-9]+ 29

30.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: ↓ 現在位置 2 * (3 + 4) Expression (式) Factor ("*" Factor)* Term (項) Factor: | "(" Expression ")" | Integer Integer: [0-9]+ Factor (因子) ? "(" Expression ")" Integer (整数) 30

31.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: ↓ 現在位置 Term (("+" | "-") Term)* Term: 2 * (3 + 4) 1文字先読みする Expression (式) Factor ("*" Factor)* Term (項) Factor: | "(" Expression ")" | Integer Integer: [0-9]+ Factor (因子) ? "(" Expression ")" Integer (整数) 31

32.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: ↓ 現在位置 Term (("+" | "-") Term)* Term: 2 * (3 + 4) 1文字先読みする Expression (式) Factor ("*" Factor)* Term (項) Factor: | "(" Expression ")" | Integer Integer: [0-9]+ Factor (因子) ? "(" Expression ")" Integer (整数) 32

33.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: ↓ 現在位置 Term (("+" | "-") Term)* Term: 2 * (3 + 4) 1文字先読みする Factor (因子) Factor ("*" Factor)* Factor: | "(" Expression ")" | Integer Integer: [0-9]+ 33

34.

LL(1) の再帰下降構文解析の説明をしています コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: Factor ("*" Factor)* ↓ 現在位置 2 * (3 + 4) 1文字先読みする Factor (因子) ? "(" Expression ")" Integer (整数) Factor: | "(" Expression ")" | Integer 以下繰り返し Integer: [0-9]+ 34

35.

コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: Factor ("*" Factor)* 2 * (3 + 4) Expression └── Term ├── Factor │ └── Integer │ └── "2" ├── "*" └── Factor ├── "(" ├── Expression Factor: | "(" Expression ")" | Integer │ ├── Term │ │ │ │ └── Integer │ │ └── "3" │ ├── "+" │ └── Term │ Integer: [0-9]+ └── Factor └── Factor │ └── Integer │ └── "4" └── ")" 35

36.

コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: Factor ("*" Factor)* 2 * (3 + 4) Expression └── Term ├── Factor │ └── Integer │ └── "2" ├── "*" └── Factor ├── "(" ├── Expression Factor: | "(" Expression ")" | Integer │ ├── Term │ │ │ │ └── Integer │ │ └── "3" │ ├── "+" │ └── Term │ Integer: [0-9]+ └── Factor └── Factor │ └── Integer │ └── "4" └── ")" 36

37.

コードから構文木を構築する Term: Factor ("*" Factor)* Factor: | "(" Expression ")" | Integer Integer: [0-9]+ └── Term 下に書いてる方が先に処理される Term (("+" | "-") Term)* 2 * (3 + 4) Expression Expression: ├── Factor │ └── Integer │ └── "2" ├── "*" └── Factor ├── "(" ├── Expression │ ├── Term │ │ │ │ └── Integer │ │ └── "3" │ ├── "+" │ └── Term │ └── Factor └── Factor │ └── Integer │ └── "4" └── ")" () が子にあるので 先に計算される 37

38.

コードから構文木を構築する Expression: Term (("+" | "-") Term)* Term: Factor ("*" Factor)* 2 * (3 + 4) Expression └── Term ├── Factor │ └── Integer │ └── "2" ├── "*" └── Factor ├── "(" ├── Expression Factor: | "(" Expression ")" | Integer │ ├── Term │ │ │ │ └── Integer │ │ └── "3" │ ├── "+" │ └── Term │ Integer: [0-9]+ └── Factor └── Factor │ └── Integer │ └── "4" └── ")" 全部は要らない 38

39.

構文木の仕様を決めよう これだけあれば実行できる BinaryExpression (*) 2 * (3 + 4) 構文解析 ├── Integer (2) └── BinaryExpression (+) ├── Integer (3) └── Integer (4) BinaryExpression: 二項演算式 Integer: コード 整数 構文木 39

40.

パーサージェネレーターってのがある 構文解析器(パーサー)を作成するためのプログラム ● yacc / bison ○ Ruby (3.2以前) ● ANTLR ○ Hibernate HQL, Twitter検索クエリ ● PEG ○ Python (3.9以降) 40

41.

ライブコーディング 文法を定義して構文解析器を作成する 41

42.
[beta]
構文木を実行する評価機を作成する
{

}

"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Integer",
"value": 2
},
"right": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Integer",
"value": 3
},
"right": {
"type": "Integer",
"value": 4
}
}

function evaluate(node) {
switch (node.type) {
子要素を再帰で取得する
case "Integer":
= 深さ優先探索
return node.value;
case "BinaryExpression": {
const left = evaluate(node.left);
const right = evaluate(node.right);
switch (node.operator) {
case "+":
return left + right;
case "-":
計算した値を返す
return left - right;
case "*":
return left * right;
}
}
}
42
}

43.

ライブコーディング プログラムを実行してみよう 43

44.

このままではプログラムっぽくない 44

45.

変数の概念を導入する 新しいキーワード var を導入して変数を宣言できるようにする 変数を計算に使用できる var x = 3; var y = x + 4; x * 2; サンプルコード 45

46.

変数の文法定義 Program: Statement* Statement: | VariableDeclaration | ExpressionStatement VariableDeclaration: "var" Identifier "=" Expression ";" Factor: | "(" Expression ")" | Integer | Identifier Identifier: [a-zA-Z_][a-zA-Z0-9_]* 46

47.

変数の文法定義 Program: Statement* プログラムは Statement (文) の集合である Statement: | VariableDeclaration | ExpressionStatement VariableDeclaration: "var" Identifier "=" Expression ";" Factor: | "(" Expression ")" | Integer | Identifier Identifier: [a-zA-Z_][a-zA-Z0-9_]* 47

48.

変数の文法定義 Program: Statement* プログラムは Statement (文) の集合である Statement: | VariableDeclaration | ExpressionStatement VariableDeclaration: "var" Identifier "=" Expression ";" 変数宣言 変数に格納する値には Expression が使える Factor: | "(" Expression ")" | Integer | Identifier Identifier: [a-zA-Z_][a-zA-Z0-9_]* 48

49.

変数の文法定義 Program: Statement* プログラムは Statement (文) の集合である Statement: | VariableDeclaration | ExpressionStatement VariableDeclaration: "var" Identifier "=" Expression ";" 変数宣言 変数に格納する値には Expression が使える Factor: | "(" Expression ")" | Integer | Identifier 値として変数が使用できる Identifier: [a-zA-Z_][a-zA-Z0-9_]* 変数名 49

50.

ライブコーディング 文法に変数定義を追加する 50

51.
[beta]
変数宣言を実装する
function executeStatement(statement, env) {
switch (statement.type) {
case "VariableDeclaration": {
const name = statement.id.name;
const value = evaluateExpression(statement.init, env);
env[name] = value;
return undefined;
}
case "ExpressionStatement":
return evaluateExpression(statement.expression, env);
}
}
51

52.
[beta]
変数宣言を実装する
変数を格納するオブジェクト {}
function executeStatement(statement, env) {
switch (statement.type) {
case "VariableDeclaration": {
const name = statement.id.name;
const value = evaluateExpression(statement.init, env);
env[name] = value;
return undefined;
}
case "ExpressionStatement":
return evaluateExpression(statement.expression, env);
}
}
52

53.
[beta]
変数宣言を実装する
変数を格納するオブジェクト {}
function executeStatement(statement, env) {
switch (statement.type) {
初期値を評価する
case "VariableDeclaration": {
const name = statement.id.name;
const value = evaluateExpression(statement.init, env);
env[name] = value;
return undefined;
}
case "ExpressionStatement":
return evaluateExpression(statement.expression, env);
}
}
53

54.
[beta]
変数宣言を実装する
変数を格納するオブジェクト {}
function executeStatement(statement, env) {
switch (statement.type) {
初期値を評価する
case "VariableDeclaration": {
const name = statement.id.name;
const value = evaluateExpression(statement.init, env);
env[name] = value;
return undefined;
変数を格納する
}
case "ExpressionStatement":
return evaluateExpression(statement.expression, env);
}
}
54

55.
[beta]
変数の読み取りを実装する
変数を格納するオブジェクト
function evaluateExpression(expression, env) {
switch (expression.type) {
case "Integer":
return expression.value;
case "Identifier": {
const name = expression.name;
if (!(name in env)) {
throw new ReferenceError(`Undefined variable: ${name}`);
}
return env[name];
}

55

56.
[beta]
変数の読み取りを実装する
変数を格納するオブジェクト
function evaluateExpression(expression, env) {
switch (expression.type) {
case "Integer":
return expression.value;
case "Identifier": {
const name = expression.name;
変数が定義されているか調べる
if (!(name in env)) {
throw new ReferenceError(`Undefined variable: ${name}`);
}
return env[name];
}

56

57.
[beta]
変数の読み取りを実装する
変数を格納するオブジェクト
function evaluateExpression(expression, env) {
switch (expression.type) {
case "Integer":
return expression.value;
case "Identifier": {
const name = expression.name;
変数が定義されているか調べる
if (!(name in env)) {
throw new ReferenceError(`Undefined variable: ${name}`);
}
return env[name];
変数を取り出す
}

57

58.

ライブコーディング 変数の評価機能を追加する 58

59.

関数も欲しいですよね? 59

60.

関数を導入する 新しいキーワード def を導入して任意の関数を定義できるようにする def double(x) { return x * 2; } double(2); サンプルコード 60

61.

関数の文法を定義する Statement: | FunctionDeclaration 関数宣言の追加 | VariableDeclaration | ExpressionStatement FunctionDeclaration: "def" Identifier "(" ParameterList? ")" BlockStatement def 関数名 (引数) ブロック ParameterList: Identifier ( "," Identifier)* 複数個の引数が宣言できる BlockStatement: "{" BlockBodyStatement* "}" BlockBodyStatement: | ReturnStatement 関数の本体には変数宣言と式が書ける 関数の中で関数は宣言できない | VariableDeclaration | ExpressionStatement ReturnStatement: "return" Expression ";" CallExpression: Identifier "(" ArgumentList? ")" ArgumentList: Expression ("," Expression)* 関数の呼び出し 61

62.
[beta]
関数宣言の実装
function executeStatement(statement, env) {
switch (statement.type) {
case "FunctionDeclaration": {
const name = statement.id.name;
const fnValue = {
name,
params: statement.params ?? [],
body: statement.body,
env,
};
env[name] = fnValue;
関数本体を変数と同じ環境に保存する
return undefined;
}
62

63.
[beta]
関数呼び出しの実装
function callFunction(fnValue, args) {
const localEnv = Object.create(fnValue.env);
fnValue.params.forEach((param, i) => {
const name = param.name;
localEnv[name] = args[i];
});
const result = executeBlock(fnValue.body, localEnv);
return result[RETURN] ? result.value : undefined;
}

63

64.
[beta]
関数呼び出しの実装
レキシカルスコープを作成する
function callFunction(fnValue, args) {
const localEnv = Object.create(fnValue.env);
fnValue.params.forEach((param, i) => {
const name = param.name;
localEnv[name] = args[i];
});
const result = executeBlock(fnValue.body, localEnv);
return result[RETURN] ? result.value : undefined;
}

64

65.
[beta]
関数呼び出しの実装
レキシカルスコープを作成する
function callFunction(fnValue, args) {
const localEnv = Object.create(fnValue.env);
fnValue.params.forEach((param, i) => {
const name = param.name;
localEnv[name] = args[i];
});

引数を積む

const result = executeBlock(fnValue.body, localEnv);
return result[RETURN] ? result.value : undefined;
}

65

66.
[beta]
関数呼び出しの実装
レキシカルスコープを作成する
function callFunction(fnValue, args) {
const localEnv = Object.create(fnValue.env);
fnValue.params.forEach((param, i) => {
const name = param.name;
localEnv[name] = args[i];
});

引数を積む

const result = executeBlock(fnValue.body, localEnv);
return result[RETURN] ? result.value : undefined;

関数を実行

}

66

67.

ライブコーディング 関数を追加する 67

68.

せっかくならコンパイラにしたい 68

69.

実行ファイルを作る 構文木から対応する機械語を生成すればよい いきなり機械語をしゃべるのは面倒なのでアセンブラを間に挟む 2 + 3 コード コンパイル mov eax, 2 mov ebx, 3 add eax, ebx アセンブラ (x86) アセンブル .exe 実行可能のファイル 69

70.

直接アセンブラを書くのも結構めんどい mov w0, #2 mov w1, #3 add w0, w0, w1 .app (Mach-O) macOS AARch64 2 + 3 コード mov eax, 2 mov ebx, 3 add eax, ebx .exe (Portable Executable) Windows x86 li t0, 2 li t1, 3 add t0, t0, t1 RICS-V .o (ELF) Linux 70

71.

LLVM プログラムを最適化するよう設計された、任意のプログラミング言語に対応可 能なコンパイラ基盤。最適化もやってくれる。 2 + 3 コード コンパイル %a = add i32 2, 3 LLVM IR (中間表現) アセンブル .exe 実行可能のファイル 71

72.
[beta]
LLVM IRの例
declare i32 @printf(ptr, ...)

C標準のprintfを使う宣言

@.fmt = private unnamed_addr constant [6 x i8] c"%lld\0A\00",
align 1

def double(x) {
return x * 2;
}

double(5);

コード

define i64 @double(i64 %x) {
entry:
%t1 = alloca i64, align 8
store i64 %x, ptr %t1, align 8
%t2 = load i64, ptr %t1, align 8
%t3 = mul i64 %t2, 2
ret i64 %t3
}
define i32 @main() {
entry:
%t1 = call i64 @double(i64 5)
%t2 = getelementptr inbounds [6 x i8], ptr @.fmt, i64 0, i64 0
call i32 (ptr, ...) @printf(ptr %t2, i64 %t1)
ret i32 0
}

LLVM IR

関数宣言

かけ算
仮想レジスタ

関数呼び出し
printf

72

73.

ライブコーディング LLVM IRを通して 実行可能ファイルを作成する 73

74.
[beta]
完成したオリジナル言語
Program: Statement*
Statement: | FunctionDeclaration
| VariableDeclaration
| ExpressionStatement
FunctionDeclaration: "def" Identifier "(" ParameterList? ")" BlockStatement
ParameterList: Identifier ("," Identifier)*
BlockStatement: "{" BlockBodyStatement* "}"
BlockBodyStatement: | ReturnStatement
| VariableDeclaration

文
法
定
義

| ExpressionStatement
ReturnStatement: "return" Expression ";"
VariableDeclaration: "var" Identifier "=" Expression ";"
ExpressionStatement: Expression ";"
Expression: Term (("+" | "-") Term)*
Term: Factor ("*" Factor)*
Factor: | CallExpression

def rectArea(w, h) {
return w * h;
}
var width = 12;
var height = 4;
rectArea(width, height);

| "(" Expression ")"
| Integer
| Identifier
CallExpression: Identifier "(" ArgumentList? ")"
ArgumentList: Expression ("," Expression)*

サンプルコード

Identifier: [a-zA-Z_][a-zA-Z0-9_]*
Integer: [0-9]+

74

75.

付録 75

76.

PEG: Parsing expression grammars ● Ford, Bryan. "Parsing expression grammars: a recognition-based syntactic foundation". SIGPLAN Not. 39. 1(2004): 111–122. ● Peggy ○ JavaScriptによるPEG実装 76

77.

文法定義 ● Python ● JavaScript (ECMAScript) ● JSONPath: Query Expressions for JSON (RFC9535) 77

78.

解説 ● 低レイヤを知りたい人のためのCコンパイラ作成入門 ● 筑波大学 プログラミング言語処理 講義資料 78