-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
315 lines (313 loc) · 34.7 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title></title>
<link href="https://jmintb.github.io/blog/atom.xml" rel="self" type="application/atom+xml"/>
<link href="https://jmintb.github.io/blog/"/>
<generator uri="https://www.getzola.org/">Zola</generator>
<updated>2023-07-17T00:00:00+00:00</updated>
<id>https://jmintb.github.io/blog/atom.xml</id>
<entry xml:lang="en">
<title>Building a programming language with Rust and MLIR part 1: pest the parser</title>
<published>2023-07-17T00:00:00+00:00</published>
<updated>2023-07-17T00:00:00+00:00</updated>
<author>
<name>Unknown</name>
</author>
<link rel="alternate" href="https://jmintb.github.io/blog/programming-language-1/" type="text/html"/>
<id>https://jmintb.github.io/blog/programming-language-1/</id>
<content type="html"><p>Before diving in, I want to make it clear that I am not(yet) an expert in compiler and language development. I have a bit of experience
from my bachelor project which involved implementing a JIT compiler for a language developed by my university. This experience made me hungry
for more and served as inspiration for all sorts of ideas I want to try out. I will make plenty of mistakes and have to learn along the way.
By documentating the process you can learn from my mistakes as well :)</p>
<h2 id="overview">Overview</h2>
<p>This blog post is the first in (hopefully) a series on building and designing a programming language. For now the primary
motivation is to satisfy an urge I have To dig my teeth into language + compiler design and implementation.
I have some existing ideas for the language I plan to build, those will get a separate post. for I will focus on getting
the practical setup out of the way.</p>
<p>The first couple of posts will serve as a sort of tutorial/introduction to the practical and theoretical aspects using the tools I have chosen (Rust, MLIR and pest).
Once the foundations have been set, I will start designing and implementing more involved aspects specific to the my language. I really need to find a name. </p>
<p>I hope you stay for the journey :)</p>
<p>The first milestone is very simple and mostly serves as a way to get our project up and running. We will start by
building a minimal compiler that can compile: </p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#96b5b4;">println</span><span>(&quot;</span><span style="color:#a3be8c;">Hello World!</span><span>&quot;);
</span></code></pre>
<p>and have &quot;Hello World!&quot; output to standard out.</p>
<p>This should be conceptually simple enough to wrap our heads around but involved enough to kick start the project and get our compiler stack setup.</p>
<p>This first post will focus on the defining a grammar powerful enough for <code>prinln(&quot;Hello World!&quot;)</code> and generating a parser. A small introduction to compiler development
will precede the parser development, to help provide general compiler intuition for the uninitiated.</p>
<p>Lets go!</p>
<h2 id="an-introduction-to-compiling-programming-languages">An introduction to compiling programming languages</h2>
<p>Before we start it is important to have some intuition around the problem space. I assume that you have experience with using a programming language.
If not then you have my respect for starting your journey with how to build a programming language!</p>
<p>Both compiled and interpreted languages are quite common today. For example Python is a popular interpreted language and Rust is a compiled language.
We won't go deep into interpreted languages but the short version is, instead of preparing machine instructions a head of time, they are generated on
the fly as new code is fed to the interpreter. A longer explanation is found [here](https://en.wikipedia.org/wiki/Interpreter_(computing). One advantage
here is that code can be run immediately without the need to invoke a compiler. Two major disadvantages are poor performance and no compile time checks, for
example like checking types. </p>
<p>We will be focusing on the design and implementation of a compiled language.
I am confident that a <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">just in time(JIT)</a> option will be
sufficient to support dynamic/scripting purposes. This will also force us to keep compile times low, win win.</p>
<p>What is a compiler?</p>
<p>The goal is to take input, often in the form of high level program, think Rust, C, Java etc and lower it to a different
and hopefully well optimized representation that can be executed in some target environment. For general purpose programming languages this
will often be some type of machine code that the target environment can execute like an X86 processor or virtual machine like the <a href="https://en.wikipedia.org/wiki/Java_virtual_machine">JVM</a>.</p>
<p>This will usually involve parsing the input to build an abstract syntax tree (AST), lowering to some intermediate representation or multiple intermediate representations
until we end up with a representation the target environment can execute. </p>
<p>We will start with at the beginning, parsing our input.</p>
<h2 id="the-parser">The parser</h2>
<p>The first step in our compiler pipeline is the parser. This step will build an AST and verify that the syntax is valid.
We could implement our own lexer, and for educational purposes I think that is a good exercise. However I will be using
the PEG parsing crate <a href="https://pest.rs/">pest</a>, it is an incredible tool for generating a parser based on a PEG grammar in Rust.
This also forces us to provide a specification for our language in the form of a grammar, win win. The grammar syntax
can be a bit daunting at first. Lets develop at simple grammar that will be sufficient.</p>
<p>To parse <code>println(&quot;Hello World!&quot;)</code> we need a few pieces of syntax in our grammar. Function calls, identifiers and values in the form of strings.
For now print will be a built in provided by the compiler, so we do not need syntax for declaring functions, only calling them.</p>
<p>Pest provides helpful builtins for common string related cases.</p>
<p>Lets define strings first, we will start with the string defined for pest's JSON <a href="https://pest.rs/book/examples/json.html">example</a> and strip it down a bit.</p>
<p>Our grammar now looks like this, we will initialize a Rust project in a moment:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>// grammar.pest
</span><span>
</span><span>char = {
</span><span> !(&quot;\&quot;&quot; | &quot;\\&quot;) ~ ANY
</span><span>}
</span><span>
</span><span>inner = @{ char* }
</span><span>
</span><span>string = ${ &quot;\&quot;&quot; ~ inner ~ &quot;\&quot;&quot; }
</span><span>
</span></code></pre>
<p>Pest has a rich syntax for specifying
grammar. Unless you are in the habit of writing grammars often you will want to check out the the pest book
<a href="https://pest.rs/book/grammars/grammars.html">here</a> to familiarize your self with the options. </p>
<p>We now have three rules, one for single characters <code>char</code>, one for the string contents <code>inner</code> and one for the whole string surrounded by double quotes <code>string</code>.
We won't cover everything pest can do but breaking down our grammar hopefully provides a good starting point. </p>
<p>The <code>char</code> rule uses the <a href="https://pest.rs/book/grammars/syntax.html#predicates"><code>any character but</code></a> idiom.
In our case this means if the following characters is not <code>&quot;</code> or <code>\</code> consume one character.
Looking at the char rule from left to write the <code>!</code> acts as a negation, so <code>!( &quot;\&quot;&quot; | &quot;\\&quot; )</code> says reject the patterns in the parentheses, where <code>|</code> is the
<a href="https://pest.rs/book/grammars/syntax.html?highlight=choice#ordered-choice">choice operator</a>. This operator will try to match on the provided options from left to right. Finally if the pattern is rejected
a single character is consumed using the builtin consumes one character denoted by the builtin <a href="https://pest.rs/book/grammars/built-ins.html"><code>ANY</code> rule</a>.</p>
<p>If you are wondering why these characters are not allowed, <code>&quot;</code> is used to define our strings and thus can not be included without being escaped
and <code>\</code> will be used to escape characters.
Escaping these characters is not strictly necessary yet. However it does set a nice foundation and shows of more of pest. So why not? </p>
<p>A string contains an arbitrary number of characters, therefore the <code>inner</code> rule denotes exactly that with <code>char*</code>, where <code>*</code> indicates zero or more. The <code>@</code> marks this rule
as <a href="https://pest.rs/book/grammars/syntax.html#atomic">atomic</a>. Atomic rules have the following special properties:</p>
<ol>
<li><code>~</code> means immediately follow by, so no white space. </li>
<li>Repetition operators (* and +) have no implicit separation.</li>
<li>Rules inside an atomic rule are treated as atomic.</li>
</ol>
<p>This is practical for parsing strings as we do not want to produce a token for each individual character. This also removes the implicit
allowance of white space that normal rules have with <code>~</code>. If you are wondering why have the <code>inner</code> rule at all and just put <code>char*</code> directly
inside the string rule, that is a fair question. The <code>inner</code> rule contains the contents of a string without the surrounding<code>&quot;</code>, if we removed it we would have to manually
remove the surrounding quotes.</p>
<p>We now finally get to the <code>string</code> rule. A string is delimited by <code>&quot;</code>, therefore our rule does exactly that
<code>string = ${ &quot;\&quot;&quot; ~ inner ~ &quot;\&quot;&quot; }</code>. We look for a pair of <code>&quot;</code> with an <code>inner</code> between then. Note the <code>$</code>, it denotes <code>string</code> as
a <a href="https://pest.rs/book/grammars/syntax.html#atomic">compound atomic</a>. This ensures that no implicit white space is permitted while
still collecting the inner rules for parsing. The example below will show this in action :)</p>
<p>Lets write some Rust and test our grammar.</p>
<p>Init a cargo project, if you have not already and add the <a href="https://pest.rs"><code>pest</code></a> dependencies:</p>
<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">cargo</span><span> init</span><span style="color:#bf616a;"> --bin</span><span> somelang
</span><span style="color:#bf616a;">cargo</span><span> add pest pest_derive
</span></code></pre>
<p>I believe that <code>cargo add</code> is a builtin option these days. If you are on an older version of cargo it can be installed with <a href="https://github.com/killercup/cargo-edit"><code>cargo-edit</code></a>. </p>
<p>Otherwise just add the dependencies manually to <code>Cargo.toml</code> as shown below :)</p>
<p>Your <code>Cargo.toml</code> should look like this:</p>
<pre data-lang="toml" style="background-color:#2b303b;color:#c0c5ce;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[dependencies]
</span><span style="color:#bf616a;">pest </span><span>= &quot;</span><span style="color:#a3be8c;">2.6</span><span>&quot;
</span><span style="color:#bf616a;">pest_derive </span><span>= &quot;</span><span style="color:#a3be8c;">2.6</span><span>&quot;
</span></code></pre>
<p>We can now jump into <code>src/main.rs</code> and use our parser. Lets start by printing what a parsed string looks like.</p>
<p>Remember to put the grammar defined earlier into <code>grammar.pest</code> in the same directory as <code>Cargo.toml</code>. Your directory should have the following structure:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>Cargo.toml
</span><span>Cargo.lock
</span><span>grammar.pest
</span><span>src/main.rs
</span><span>...
</span></code></pre>
<p>In <code>main.rs</code> we:</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span>
</span><span style="color:#65737e;">// Add the pest depdendencies
</span><span style="color:#b48ead;">use </span><span>pest::Parser;
</span><span style="color:#b48ead;">use </span><span>pest_derive::Parser;
</span><span>
</span><span>
</span><span style="color:#65737e;">// Declare our Parser struct and derive the pest Parser trait.
</span><span style="color:#65737e;">// As well as reference the file containing our grammar.
</span><span>#[</span><span style="color:#bf616a;">derive</span><span>(Parser)]
</span><span>#[</span><span style="color:#bf616a;">grammar </span><span>= &quot;</span><span style="color:#a3be8c;">grammar.pest</span><span>&quot;]
</span><span style="color:#b48ead;">pub struct </span><span>LangParser;
</span><span>
</span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">main</span><span>() {
</span><span> </span><span style="color:#b48ead;">let</span><span> input = &quot;</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">hello world</span><span style="color:#96b5b4;">\&quot;</span><span>&quot;.</span><span style="color:#96b5b4;">to_string</span><span>();
</span><span>
</span><span> </span><span style="color:#65737e;">// This parses the &quot;hello world&quot; string using the string rule.
</span><span> </span><span style="color:#b48ead;">let</span><span> parsed_input = LangParser::parse(Rule::string, &amp;input).</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span>
</span><span> </span><span style="color:#65737e;">// We use #? to pretty the output for easy reading.
</span><span> println!(&quot;</span><span style="color:#d08770;">{parsed_input:#?}</span><span>&quot;);
</span><span>}
</span><span>
</span></code></pre>
<p>Running <code>cargo run</code> should produce the following output.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>[
</span><span> Pair {
</span><span> rule: string,
</span><span> span: Span {
</span><span> str: &quot;\&quot;hello world\&quot;&quot;,
</span><span> start: 0,
</span><span> end: 13,
</span><span> },
</span><span> inner: [
</span><span> Pair {
</span><span> rule: inner,
</span><span> span: Span {
</span><span> str: &quot;hello world&quot;,
</span><span> start: 1,
</span><span> end: 12,
</span><span> },
</span><span> inner: [],
</span><span> },
</span><span> ],
</span><span> },
</span><span>]
</span></code></pre>
<p>Here we can see the <a href="https://docs.rs/pest/latest/pest/iterators/struct.Pair.html">Pair</a> which
represents a matching pair of tokens and everything in between. In our case <code>&quot;</code> is the token
matched by the first Pair: <code>span: Span { str: &quot;\&quot;hello world\&quot;&quot;, start: 0, end: 13 }</code>.
The inner field contains what ever the tokens spanned. In this case another <code>Pair</code> spanning the actual
string contents. The matching token pair is <code>h</code> and <code>d</code>. This might seem a bit counter intuitive
but our string contains any number of <code>CHAR</code> due to <code>inner = { CHAR* }</code> and char matches
on almost any character. An important observation here is that matching tokens for a <code>Pair</code> do not need to be the same,
only match the same rule. </p>
<p>Note how the <code>inner</code> does not contain any <code>CHAR</code> rules but just the characters it spans.
This is due the fact that <code>inner</code> is <a href="https://pest.rs/book/grammars/syntax.html#atomic">atomic</a> and does not permit
white space between rules. <code>string</code> on the other hand is a <a href="https://pest.rs/book/grammars/syntax.html#atomic">compound atomic</a> rule.
It will therefore have inner rules but still does not permit any white space between matches.</p>
<h3 id="function-calls">Function calls</h3>
<p>Next up let us expand our grammar to support function calls with a single input value. This will be the last addition required to print <code>Hello World!</code>.
We will start by extending our input in <code>src/main.rs</code>.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span> ...
</span><span> </span><span style="color:#b48ead;">let</span><span> input = &quot;</span><span style="color:#a3be8c;">print(</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">hello world</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">)</span><span>&quot;.</span><span style="color:#96b5b4;">to_string</span><span>();
</span><span> ...
</span></code></pre>
<p>Now if we run <code>cargo run</code> we will get an error as our grammar does not support function calls yet. Lets fix that!</p>
<p>We will add 3 new rules, first <code>val = _{ string }</code>. <code>val</code> represents a value. Right now we only support string values
so <code>val</code> only contains the <code>string</code> rule. <code>val</code> is silent denoted by <code>_</code>. This means that it will note be visible in our
parser. Instead we will see instances it's inner rules. This is nice as it saves us having to &quot;unwrap&quot; val instances, since we
are always interested in it's contents.</p>
<p><code>id = { ASCII_ALPHA+ }</code> will represent an identifier of variables, functions, modules etc. For example <code>println</code> is an identifier.
<code>ASCII_ALPHA+</code> indicates matches on one or more
letters from a-z and A-Z. In future iterations we want to be support more characters but this will do for now. </p>
<p>Finally <code>call = ${ id ~ &quot;(&quot; ~ val ~ &quot;)&quot; }</code>, which matches on a function call containing a single argument.
<code>call</code> consists of an identifier followed by a single argument surrounded by parentheses.
Exactly what we need! We make this rule compound atomic as we do not want white space between the
identifier and parentheses.</p>
<p><code>grammar.pest</code> should now look like this.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>// grammar.pest
</span><span>
</span><span>char = {
</span><span> !(&quot;\&quot;&quot; | &quot;\\&quot; ) ~ ANY
</span><span>}
</span><span>
</span><span>inner = @{ char* }
</span><span>
</span><span>string = ${ &quot;\&quot;&quot; ~ inner ~ &quot;\&quot;&quot; }
</span><span>
</span><span>val = _{ string }
</span><span>
</span><span>id = { ASCII_ALPHA* }
</span><span>
</span><span>call = ${ id ~ &quot;(&quot; ~ val ~ &quot;)&quot; }
</span><span>
</span></code></pre>
<p>Lets take it for a spin!. Running <code>cargo run</code> should still produce an error, as we have not updated which rule we use to parse this.
In <code>src/main.rs</code> update the parsing line to use <code>Rule::call</code>. </p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#65737e;">// src/main.rs
</span><span> ...
</span><span> </span><span style="color:#b48ead;">let</span><span> parsed_input = LangParser::parse(Rule::call, &amp;input).</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span> ...
</span></code></pre>
<p>Now you should be able to run <code>cargo run</code> and get the parsed function call like this:</p>
<pre data-lang="shell" style="background-color:#2b303b;color:#c0c5ce;" class="language-shell "><code class="language-shell" data-lang="shell"><span>[
</span><span> Pair {
</span><span> rule: call,
</span><span> span: Span {
</span><span> str: &quot;print(\&quot;hello world\&quot;)&quot;,
</span><span> start: 0,
</span><span> end: 20,
</span><span> },
</span><span> inner: [
</span><span> Pair {
</span><span> rule: id,
</span><span> span: Span {
</span><span> str: &quot;print&quot;,
</span><span> start: 0,
</span><span> end: 5,
</span><span> },
</span><span> inner: [],
</span><span> },
</span><span> Pair {
</span><span> rule: string,
</span><span> span: Span {
</span><span> str: &quot;\&quot;hello world\&quot;&quot;,
</span><span> start: 6,
</span><span> end: 19,
</span><span> },
</span><span> inner: [
</span><span> Pair {
</span><span> rule: inner,
</span><span> span: Span {
</span><span> str: &quot;hello world&quot;,
</span><span> start: 7,
</span><span> end: 18,
</span><span> },
</span><span> inner: [],
</span><span> },
</span><span> ],
</span><span> },
</span><span> ],
</span><span> },
</span><span>]
</span></code></pre>
<p>And with that we are finished with the grammar, for now ;)</p>
<p>This post has grown quite long so we will continue in the next one.
Where we will actually use our parser and hopefully finish <code>prinln(&quot;Hello World!&quot;)</code>.</p>
<p>As a final step we will extract the identifier and argument, by traversing the parsed input as nested iterators.
This will help show how to work with pest.</p>
<pre data-lang="rust" style="background-color:#2b303b;color:#c0c5ce;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#65737e;">// src/main.rs
</span><span>...
</span><span>
</span><span> </span><span style="color:#65737e;">// Get an iterator over the pairs matched by Rule::call
</span><span> </span><span style="color:#b48ead;">let mut</span><span> call_pairs = parsed_input.</span><span style="color:#96b5b4;">into_iter</span><span>().</span><span style="color:#96b5b4;">next</span><span>().</span><span style="color:#96b5b4;">unwrap</span><span>().</span><span style="color:#96b5b4;">into_inner</span><span>();
</span><span>
</span><span> </span><span style="color:#65737e;">// We know the id comes first.
</span><span> </span><span style="color:#b48ead;">let</span><span> id = call_pairs.</span><span style="color:#96b5b4;">next</span><span>().</span><span style="color:#96b5b4;">unwrap</span><span>();
</span><span>
</span><span> </span><span style="color:#65737e;">// And the function argument second.
</span><span> </span><span style="color:#65737e;">// We extract the `inner` rule inside the matched `string`.
</span><span> </span><span style="color:#b48ead;">let</span><span> argument = call_pairs.</span><span style="color:#96b5b4;">next</span><span>().</span><span style="color:#96b5b4;">unwrap</span><span>().</span><span style="color:#96b5b4;">into_inner</span><span>();
</span><span>
</span><span> println!(&quot;</span><span style="color:#a3be8c;">id: </span><span style="color:#d08770;">{}</span><span style="color:#a3be8c;">, argument: </span><span style="color:#d08770;">{}</span><span>&quot;, id.</span><span style="color:#96b5b4;">as_str</span><span>(), argument.</span><span style="color:#96b5b4;">as_str</span><span>());
</span><span>
</span><span>}
</span><span>
</span></code></pre>
<p><code>cargo run</code> should now produce:</p>
<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">id:</span><span> print, argument: hello world
</span></code></pre>
<p>Note how <code>hello world</code> does not have surrounding quotes. This is because we went one level deeper. If we stopped at <code>argument = call_pairs.next().unwrap</code> we would have the <code>string</code> rule which
contains <code>&quot;</code>. By calling <code>into_inner</code> we go one rule deeper and extract the matched <code>inner</code> which only holds the contents of a
string. </p>
<p>Traversing our parsed input in this manner will allow us to build a proper AST one we are ready. I hope this example showed how
powerful and simple <a href="https://jmintb.github.io/blog/programming-language-1/todo">pest</a> allows us to get started parsing.</p>
<h3 id="closing-notes">Closing notes</h3>
<p>Thanks for reading! I hope this post was informative and fun. The code for this post is available <a href="https://jmintb.github.io/blog/programming-language-1/TODO">here</a>.</p>
<p>If you are interested in this sort of thing subscribe to the <a href="https://jmintb.github.io/blog/programming-language-1/TODO">rss</a>. I do not have a proper about section
yet, so I will put my socials here:</p>
<p>mastodon: https://hachyderm.io/@jmintb</p>
<p>github: https://github.com/jmintb</p>
<p>youtube: https://www.youtube.com/channel/UCiktIroKtzNNLqyRgPxvnfQ</p>
<p>twitch: https://www.twitch.tv/teainspace</p>
</content>
</entry>
</feed>