diff --git a/site/posts/2021-05-09-variable-declarations.md b/site/posts/2021-05-09-variable-declarations.md
new file mode 100644
index 0000000..a2865db
--- /dev/null
+++ b/site/posts/2021-05-09-variable-declarations.md
@@ -0,0 +1,109 @@
+```
+metadata.title = "Part 10: Variable Declarations"
+metadata.tags = ["build a programming language", "rust"]
+metadata.date = "2021-05-09 19:14:42 -0400"
+metadata.shortDesc = ""
+metadata.slug = "variable-declarations"
+metadata.preamble = `
This post is part of a series about learning Rust and building a small programming language.
`
+```
+
+Now that the parser can handle multiple statements and the usage of variables, let's add the ability to actually declare variables.
+
+
+
+First off, the lexer now lexes a couple new things: the `let` keyword and the equals sign.
+
+When the parser tries to parse a statement and sees a let token, it knows it's looking at a variable declaration. After the let token it expects to find a identifier (the variable name), an equals sign, and then an expression for the initial value of the variable. The variable name and the initial value expression then make up a `Declare` AST node.
+
+```rust
+fn parse_statement<'a, I: Iterator- >(it: &mut Peekable<'a, I>) -> Option {
+ // ...
+ let node = match token {
+ Token::Let => {
+ let name: String;
+ if let Some(Token::Identifier(s)) = it.peek() {
+ name = s.clone();
+ it.next();
+ } else {
+ panic!("expected identifier after let");
+ }
+ expect_token!(it, Equals, "expected equals after identifier after let");
+ let value = parse_expression(it).expect("initial value in let statement");
+ Some(Statement::Declare { name, value })
+ }
+ // ...
+ };
+ // ...
+}
+```
+
+`expect_token!` is a simple macro I wrote to handle expecting to see a specific token in the stream and panicking if it's not there, since that's a pattern that was coming up frequently:
+
+```rust
+macro_rules! expect_token {
+ ($stream:ident, $token:ident, $msg:expr) => {
+ if let Some(Token::$token) = $stream.peek() {
+ $stream.next();
+ } else {
+ panic!($msg);
+ }
+ };
+}
+```
+
+Next, to actually evaluate variable declarations, the evaulator needs to have some concept of a context. Right now, every expression can be evaluated without any external state. But, when a variable is declared, we want it to be accessible later on in the code, so there needs to be somewhere to store that information.
+
+For now, the only information we need is the map of variable names to their values.
+
+```rust
+struct Context {
+ variables: HashMap,
+}
+```
+
+There are also a few methods for `Context`, one to construct a new context and one to declare a variable with an initial value.
+
+```rust
+impl Context {
+ fn new() -> Self {
+ Self {
+ variables: HashMap::new(),
+ }
+ }
+
+ fn declare_variable(&mut self, name: &str, value: Value) {
+ self.variables.insert(name.into(), value);
+ }
+}
+```
+
+Every `eval_` function has also changed to take a reference to the current context[^1] and the main `eval` function creates a context before evaluating each statement.
+
+[^1]: For now a simple mutable reference is fine, because there's only ever one context: the global one. But, in the future, this will need to be something a bit more complicated.
+
+With that, declaration statements can be evaluated just by calling the `declare_variable` method on the context:
+
+```rust
+fn eval_declare_variable(name: &str, value: &Node, context: &mut Context) {
+ let val = eval_expr(value, context);
+ context.declare_variable(name, val);
+}
+```
+
+And we can actually set and read variables now[^2]:
+
+```rust
+fn main() {
+ let code = "let foo = 1; dbg(foo)";
+ let tokens = tokenize(&code);
+ let statements = parse(&tokens);
+ eval(&statements);
+}
+```
+
+```sh
+$ cargo run
+Integer(1)
+```
+
+[^2]: The `dbg` function is a builtin I added that prints out the Rust version of the `Value` it's passed.