Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Let’s create a programming language! [SoundClou...
Search
Denis Defreyne
June 07, 2016
Programming
0
240
Let’s create a programming language! [SoundCloud HQ edition]
Denis Defreyne
June 07, 2016
Tweet
Share
More Decks by Denis Defreyne
See All by Denis Defreyne
The importance of naming
denisdefreyne
0
110
An introduction to fibers
denisdefreyne
0
220
Code as data (RubyConfBY 2019 edition)
denisdefreyne
0
130
Code as data
denisdefreyne
0
200
How to memoize
denisdefreyne
0
210
Clean & fast code with enumerators
denisdefreyne
0
150
Fibers
denisdefreyne
0
490
Let’s create a programming language! [RUG::B edition]
denisdefreyne
1
230
Let’s write a parser! [SoundCloud HQ edition]
denisdefreyne
0
260
Other Decks in Programming
See All in Programming
ktr0731/go-mcpでMCPサーバー作ってみた
takak2166
0
170
レガシーシステムの機能調査・開発におけるAI利活用
takuya_ohtonari
0
600
DroidKnights 2025 - 다양한 스크롤 뷰에서의 영상 재생
gaeun5744
3
300
Bytecode Manipulation 으로 생산성 높이기
bigstark
2
360
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
240
F#で自在につくる静的ブログサイト - 関数型まつり2025
pizzacat83
0
310
生成AIコーディングとの向き合い方、AIと共創するという考え方 / How to deal with generative AI coding and the concept of co-creating with AI
seike460
PRO
1
300
Go Modules: From Basics to Beyond / Go Modulesの基本とその先へ
kuro_kurorrr
0
120
関数型まつり2025登壇資料「関数プログラミングと再帰」
taisontsukada
2
830
Go1.25からのGOMAXPROCS
kuro_kurorrr
1
760
赤裸々に公開。 TSKaigiのオフシーズン
takezoux2
0
130
Team topologies and the microservice architecture: a synergistic relationship
cer
PRO
0
850
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1031
460k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
31
1.2k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
How to Think Like a Performance Engineer
csswizardry
24
1.7k
Bash Introduction
62gerente
614
210k
Unsuck your backbone
ammeep
671
58k
A Tale of Four Properties
chriscoyier
159
23k
Java REST API Framework Comparison - PWX 2021
mraible
31
8.6k
Producing Creativity
orderedlist
PRO
346
40k
Writing Fast Ruby
sferik
628
61k
Transcript
Let’s create a programming language! DENIS DEFREYNE SOUNDCLOUD, BERLIN JUNE
7, 2016
2 dynamic vs. static
3 dynamic vs. static
4 compiler interpreter vs.
5 compiler interpreter vs.
but why? 6
incremental 7
Language 1 8 Numbers
% 9 cat stuff.cke 4173 5 27 2016
% 10 cat stuff.cke | ./clarke 4173 5 27 2016
11 module Grammar extend DParse::DSL DIGIT = char_in('0'..'9') PROGRAM =
DIGIT end
12 res = Grammar::PROGRAM.apply($stdin.read) case res when DParse::Success handle_success(res)
when DParse::Failure handle_failure(res) end
13 def handle_success(success) p success.data end
14 DIGIT = char_in('0'..'9')
15 DIGIT = … NUMBER = repeat1(DIGIT)
16 DIGIT = … NUMBER = repeat1(DIGIT) .capture
17 DIGIT = … NUMBER = repeat1(DIGIT) .capture .map {
|d| d.to_i }
18 DIGIT = … NUMBER = … EXPRESSION = NUMBER
19 DIGIT = … NUMBER = … EXPRESSION = …
EXPRESSIONS = intersperse( EXPRESSION, char("\n").ignore, ).compact
20 DIGIT = … NUMBER = … EXPRESSION = …
EXPRESSIONS = … PROGRAM = seq(EXPRESSIONS, eof) .first
% 21 cat stuff.cke 4173 5 27 2016
% 22 cat stuff.cke | ./clarke [4173, 5, 27, 2016]
23 def handle_success(success) p success.data end
24 def handle_success(success) success.data.each do |expr| p eval_expr(expr) end end
25 def eval_expr(expr) expr end
% 26 cat stuff.cke | ./clarke 4173 5 27 2016
% 27 echo "aa" | ./clarke expected digit at line
1, column 1 aa ↑
Language 2 28 Function calls
% 29 cat stuff.cke 4173 add(2, 5)
% 30 cat stuff.cke | ./clarke 4173 7
31 LETTER = char_in('a'..'z') FUNCTION_NAME = repeat1(LETTER).capture
32 FUNCTION_CALL = seq( FUNCTION_NAME, char('('), intersperse( lazy { EXPRESSION
}, char(','), ), char(')'), )
33 FUNCTION_CALL = seq( FUNCTION_NAME, char('('), intersperse( lazy { EXPRESSION
}, char(',').ignore, ).compact, char(')'), )
34 FUNCTION_CALL = seq( FUNCTION_NAME, char('(').ignore, intersperse( lazy { EXPRESSION
}, char(',').ignore, ).compact, char(')').ignore, ).compact
35 EXPRESSION = NUMBER
36 EXPRESSION = alt(NUMBER, FUNCTION_CALL)
% 37 cat stuff.cke 4173 add(2, 5)
% 38 cat stuff.cke | ./clarke 4173 ["add", [2, 5]]
39 FUNCTION_CALL = seq( FUNCTION_NAME, char('(').ignore, intersperse( lazy { EXPRESSION
}, char(',').ignore, ).compact, char(')').ignore, ).compact
40 def eval_expr(expr) expr end
41 def eval_expr(expr) case expr when Integer expr when Array
eval_function_call(expr) end end
42 def eval_function_call(expr) case expr[0] when 'add' expr[1] .reduce(:+) when
'mul' … end end
43 def eval_function_call(expr) case expr[0] when 'add' expr[1] .map {
|e| eval_expr(e) } .reduce(:+) when 'mul' … end end
% 44 cat stuff.cke 4173 add(2, add(1, 4))
% 45 cat stuff.cke | ./clarke 4173 7
Ugh, we forgot about whitespace. 46
Cleanup 1 47 Abstract syntax
48 class IntNode < Struct.new(:value) end class FunCallNode < Struct.new(:name,
:arguments) end
49 DIGIT = … NUMBER = repeat1(DIGIT) .capture .map {
|d| d.to_i }
50 DIGIT = … NUMBER = repeat1(DIGIT) .capture .map {
|d| IntNode.new(d.to_i) }
51 FUNCTION_CALL = seq( FUNCTION_NAME, char('(').ignore, intersperse(…), char(')').ignore, ).compact
52 FUNCTION_CALL = seq( FUNCTION_NAME, char('(').ignore, intersperse(…), char(')').ignore, ).compact.map do
|data| FunCallNode.new(data[0], data[1]) end
53 def eval_expr(expr) case expr when Integer expr when Array
eval_function_call(expr) end end
54 def eval_expr(expr) case expr when IntNode expr.value when FunCallNode
eval_function_call(expr) end end
55 def eval_function_call(expr) case expr[0] when 'add' expr[1] .map {
|e| eval_expr(e) } .reduce(:+) when 'mul' … end end
56 def eval_function_call(expr) case expr.name when 'add' expr.args .map {
|e| eval_expr(e) } .reduce(:+) when 'mul' … end end
Cleanup 2 57 Environment
58 $env = {} $env['add'] = FunDef.new( ['a', 'b'], ->(a,
b) { a + b }, )
59 class FunDef < Struct.new(:args, :body) end
60 def eval_function_call(expr) case expr.name when 'add' expr.args .map {
|e| eval_expr(e) } .reduce(:+) when 'mul' … end end
61 def eval_function_call(expr) fun = $env[expr.name] values = expr.args.map {
|e| eval_expr(e) } fun.body.call(*values) end
… with error handling, of course. 62
Language 3 63 Variables
% 64 cat stuff.cke 4173 add(2,amount)
% 65 cat stuff.cke | ./clarke 4173 7
66 class VarNode < Struct.new(:name) end
67 VARIABLE_NAME = repeat1(LETTER).capture
68 VARIABLE_REFERENCE = VARIABLE_NAME .map { |d| VarNode.new(d) }
69 EXPRESSION = alt( NUMBER, FUNCTION_CALL, )
70 EXPRESSION = alt( NUMBER, FUNCTION_CALL, VARIABLE_REFERENCE, )
71 def eval_expr(expr) case expr when IntNode expr.value when FunCallNode
eval_function_call(expr) end end
72 def eval_expr(expr) case expr when IntNode expr.value when FunCallNode
eval_function_call(expr) when VarNode eval_var(expr) end end
73 def eval_var(expr) $env[expr.name] end
74 $env['amount'] = 5
Language 3.1 75 Variable assignment
% 76 cat stuff.cke 4173 amount=5 add(2,amount)
% 77 cat stuff.cke | ./clarke 4173 7
78 class AssignNode < Struct.new(:name, :rhs) end
79 ASSIGNMENT = seq( VARIABLE_NAME, char('='), lazy { EXPRESSION },
)
80 ASSIGNMENT = seq( VARIABLE_NAME, char('=').ignore, lazy { EXPRESSION },
).compact
81 ASSIGNMENT = seq( VARIABLE_NAME, char('=').ignore, lazy { EXPRESSION },
).compact.map do |data| AssignNode.new(data[0], data[1]) end
82 EXPRESSION = alt( NUMBER, FUNCTION_CALL, VARIABLE_REFERENCE, )
83 EXPRESSION = alt( NUMBER, FUNCTION_CALL, VARIABLE_REFERENCE, ASSIGNMENT, )
84 def eval_expr(expr) case expr when … … when AssignNode
eval_assign(expr) end end
85 def eval_assign(expr) $env[expr.name] = eval_expr(expr.rhs) end
Cleanup 3 86 Print function
87 def handle_success(success) success.data.each do |expr| p eval_expr(expr) end end
88 def handle_success(success) success.data.each do |expr| eval_expr(expr) end end
89 $env['print'] = FunDef.new( ['a'], ->(a) { p a },
)
% 90 cat stuff.cke 4173 amount=5 print(add(2,amount))
% 91 cat stuff.cke | ./clarke 7
Cleanup 4 92 Immutable env
93 def eval_expr(expr) case expr when … … when AssignNode
eval_assign(expr) end end
94 def eval_expr(expr, env) case expr when … … when
AssignNode eval_assign(expr, env) end end
95 def eval_assign(expr) $env[expr.name] = eval_expr(expr.rhs) end
96 def eval_assign(expr, env) $env[expr.name] = eval_expr(expr.rhs) end
97 def eval_assign(expr, env) val = eval_expr(expr.rhs) new_env = env.merge(expr.name
=> val) ValAndEnv.new(val, new_env) end
98 class ValAndEnv < Struct.new(:val, :env) end
99 def handle_success(success) success.data.each do |expr| eval_expr(expr) end end
100 def handle_success(success) init = ValAndEv.new(0, INITIAL_ENV)
101 def handle_success(success) init = ValAndEv.new(0, INITIAL_ENV) success.data.reduce(init) do |result,
e| eval_expr(e, result.env) end end
102 INITIAL_ENV = { 'add' => FunDef.new( ['a', 'b'], ->(a,
b) { a + b }, ) }
Language 4 103 Scopes
% 104 cat stuff.cke 4173 amount = { a =
2 b = 3 add(a, b) } print(add(2, amount))
% 105 cat stuff.cke | ./clarke 7
106 class ScopeNode < Struct.new(:exprs) end
107 SCOPE = seq( char('{'), repeat1(lazy { EXPRESSION }), char('}'),
)
108 SCOPE = seq( char('{').ignore, repeat1(lazy { EXPRESSION }), char('}').ignore,
).compact
109 SCOPE = seq( char('{').ignore, repeat1(lazy { EXPRESSION }), char('}').ignore,
).compact.map do |data| ScopeNode.new(data[0]) end
110 EXPRESSION = alt( NUMBER, FUNCTION_CALL, VARIABLE_REFERENCE, ASSIGNMENT, )
111 EXPRESSION = alt( NUMBER, FUNCTION_CALL, VARIABLE_REFERENCE, ASSIGNMENT, SCOPE, )
112 def eval_expr(expr, env) case expr when … … when
ScopeNode eval_scope(expr, env) end end
113 def eval_scope(expr, env) init = ValAndEnv.new(0, env) result =
expr.exprs.reduce(init) do |result, e| eval_expr(e, result.env) end ValAndEnv.new(result.val, env) end
114 def eval_scope(expr, env) init = ValAndEnv.new(0, env) result =
expr.exprs.reduce(init) do |result, e| eval_expr(e, result.env) end ValAndEnv.new(result.val, env) end
% 115 cat stuff.cke 4173 amount = { a =
2 b = 3 add(a, b) } print(add(2, amount))
Language 5 116 Conditionals
% 117 cat stuff.cke if (0) { print(100) } else
{ print(200) }
% 118 cat stuff.cke | ./clarke 200
SKIP 119
Language 6 120 Function definitions
% 121 cat stuff.cke fun multiply(a, b) { if (b)
{ add(a, multiply(a, sub(b, 1))) } else { 0 } } print(multiply(3, 5))
122 class FunDefNode < Struct.new( :name, :argument_names, :body) end
123 FUNCTION_DEF = seq( string('fun'), FUNCTION_NAME, char('('), intersperse( VARIABLE_NAME, char(','),
), char(')'), SCOPE, )
124 FUNCTION_DEF = seq( string('fun').ignore, FUNCTION_NAME, char('(').ignore, intersperse( VARIABLE_NAME, char(',').ignore,
).compact, char(')').ignore, SCOPE, ).compact
125 FUNCTION_DEF = seq( string('fun').ignore, FUNCTION_NAME, char('(').ignore, intersperse( VARIABLE_NAME, char(',').ignore,
).compact, char(')').ignore, SCOPE, ).compact.map do |data| FunDefNode.new(data[0], data[1], data[2]) end
126 class FunDef < Struct.new(:argument_names, :body) end
127
127 def eval_function_def(expr, env)
127 def eval_function_def(expr, env) fun_def = FunDef.new( expr.argument_names, expr.body)
127 def eval_function_def(expr, env) fun_def = FunDef.new( expr.argument_names, expr.body) ValAndEnv.new(
fun_def, env.merge(expr.name => fun_def), ) end
128 def eval_function_call(expr, env) fun = env[expr.name] values = expr.args.map
{ |e| eval_expr(e) } fun.body.call(*values)
129 def eval_function_call(expr, env) fun = env[expr.name] values = expr.args.map
{ |e| eval_expr(e) } case fun.body when Proc when Scope … fun.body.call(*values)
129 def eval_function_call(expr, env) fun = env[expr.name] values = expr.args.map
{ |e| eval_expr(e) } case fun.body when Proc when Scope … fun.body.call(*values)
130
130 when Scope new_env = env.merge( Hash[fun.argument_names.zip(values)])
130 when Scope new_env = env.merge( Hash[fun.argument_names.zip(values)]) eval_scope(fun.body, new_env) end
% 131 cat stuff.cke fun multiply(a, b) { if (b)
{ add(a, multiply(a, sub(b, 1))) } else { 0 } } print(multiply(3, 5))
Language 7 132 Operators
% 133 cat stuff.cke fun multiply(a, b) { if (b)
{ a + multiply(a, b - 1) } else { 0 } } print(multiply(3, 5))
134 10 - 2 - 5 * 2 + 2
^ 3 ^ 4
135 1. Precedence
136 4 + 5 * 2_
137 4 + (5 * 2)
138 (4 + 5) * 2 _
138 (4 + 5) * 2 _ WRONG!
139 2. Associativity
140 2 ^ 3 ^ 4_
141 2 ^ (3 ^ 4)
142 (2 ^ 3) ^ 4__
142 (2 ^ 3) ^ 4__ WRONG!
143 Shunting-yard algorithm (en.wikipedia.org/wiki/Shunting-yard_algorithm)
144
144 input: infix notation e.g. a + b * c
144 input: infix notation e.g. a + b * c
precedences e.g. a + b * c
144 input: infix notation e.g. a + b * c
precedences e.g. a + b * c associativities e.g. a + b * c
144 input: infix notation e.g. a + b * c
precedences e.g. a + b * c associativities e.g. a + b * c output: postfix notation e.g. a b c * +
145 tokens = "10 - 2 - 5 * 2
+ 2 ^ 3 ^ 4" .split(" ")
146 tokens = "10 - 2 - 5 * 2
+ 2 ^ 3 ^ 4" .split(" ") res = shunting_yard( tokens, PRECEDENCES, ASSOCIATIVITIES, )
147 tokens = "10 - 2 - 5 * 2
+ 2 ^ 3 ^ 4" .split(" ") res = shunting_yard( tokens, PRECEDENCES, ASSOCIATIVITIES, ) puts res.join(' ')
148 PRECEDENCES = { '^' => 3, '*' => 2,
'/' => 2, '+' => 1, '-' => 1, }
149 ASSOCIATIVITIES = { '^' => :right, '*' => :left,
'/' => :left, '+' => :left, '-' => :left, }
150 10 2 - 5 2 * - 2 3
4 ^ ^ +
151 10 2 - 5 2 * - 2 3
4 ^ ^ + 10
152 10 2 - 5 2 * - 2 3
4 ^ ^ + 2 10
153 10 2 - 5 2 * - 2 3
4 ^ ^ + 8
154 10 2 - 5 2 * - 2 3
4 ^ ^ + 5 8
155 10 2 - 5 2 * - 2 3
4 ^ ^ + 2 5 8
156 10 2 - 5 2 * - 2 3
4 ^ ^ + 10 8
157 10 2 - 5 2 * - 2 3
4 ^ ^ + -2
158 10 2 - 5 2 * - 2 3
4 ^ ^ + 2 -2
159 10 2 - 5 2 * - 2 3
4 ^ ^ + 3 2 -2
160 10 2 - 5 2 * - 2 3
4 ^ ^ + 4 3 2 -2
161 10 2 - 5 2 * - 2 3
4 ^ ^ + 81 2 -2
162 10 2 - 5 2 * - 2 3
4 ^ ^ + 241785163 -2
163 10 2 - 5 2 * - 2 3
4 ^ ^ + 241785163
164 OPERATOR = alt( char('^'), char('*'), char('/'), char('+'), char('-'), )
165 BIN_OP_EXPRESSION = intersperse( OPERAND, OPERATOR, )
166 postfix_tokens = shunting_yard( expr.tokens, PRECEDENCES, ASSOCIATIVITIES) stack = []
postfix_tokens.each do |e| if e.is_a?(Clarke::AST::Op) values = [stack.pop, stack.pop] stack.push(eval_op(e, values)) else stack.push(e) end end
167 def eval_op(e, values) case e.name when '^' values.reduce(:**) when
'+' values.reduce(:+) when … end end
% 168 cat stuff.cke fun multiply(a, b) { if (b)
{ a + multiply(a, b - 1) } else { 0 } } print(multiply(3, 5))
etc.
Types! Loops! Closures! Objects! Classes! Inheritance! Tuples! Records! Typechecking! Multimethods!
Enumerations! Proper lexical scoping! Pattern matching! Currying! Modules! 170
171
171 L1 Numbers
171 L1 Numbers L2 Function calls
171 L1 Numbers L2 Function calls C1 Abstract syntax
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables C3 Print function
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables C3 Print function C4 Immutable environment
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables C3 Print function C4 Immutable environment L4 Scopes
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables C3 Print function C4 Immutable environment L4 Scopes L5 Conditionals
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables C3 Print function C4 Immutable environment L4 Scopes L5 Conditionals L6 Function definitions
171 L1 Numbers L2 Function calls C1 Abstract syntax C2
Environment L3 Variables C3 Print function C4 Immutable environment L4 Scopes L5 Conditionals L6 Function definitions L7 Operators
172 github.com/ddfreyne/clarke
172 github.com/ddfreyne/clarke SOON
173 Ready to interpret your questions.
[email protected]
@DENIS