<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: galzmarc</title>
    <description>The latest articles on DEV Community by galzmarc (@galzmarc).</description>
    <link>https://dev.to/galzmarc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1109446%2F3ab85652-5746-4c92-b2d3-0142145811e1.png</url>
      <title>DEV Community: galzmarc</title>
      <link>https://dev.to/galzmarc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/galzmarc"/>
    <language>en</language>
    <item>
      <title>Building a Lisp Interpreter in Rust</title>
      <dc:creator>galzmarc</dc:creator>
      <pubDate>Sun, 30 Jun 2024 16:52:35 +0000</pubDate>
      <link>https://dev.to/galzmarc/building-a-lisp-interpreter-in-rust-2njj</link>
      <guid>https://dev.to/galzmarc/building-a-lisp-interpreter-in-rust-2njj</guid>
      <description>&lt;p&gt;I’ve been playing around with Rust for some time now; it's a pretty cool systems language with a very nice set of features: it's statically typed, and it enforces memory safety without a garbage collector (it uses a borrow checker instead) by statically determining when a memory object is no longer in use. These characteristics make it a “different” language that I was interested in exploring, plus the community is absolutely amazing.&lt;/p&gt;

&lt;p&gt;As for the question "why building a Lisp interpreter?", it just happens to be one of John Crickett's &lt;a href="https://codingchallenges.fyi/"&gt;Coding Challenges&lt;/a&gt; and I decided to give it it a try. Also, Scheme's minimal syntax offers a clear pathway for building core language features, thus making it an ideal language for my purpose.&lt;/p&gt;

&lt;h4&gt;
  
  
  Inspiration and acknowledgements
&lt;/h4&gt;

&lt;p&gt;I have two sources to cite here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Peter Norvig's &lt;a href="http://norvig.com/lispy.html"&gt;Lispy&lt;/a&gt;: Many years ago, Peter Norving wrote this beautiful article about creating a Lisp interpreter in Python. I used his article as the main guide for my Rust implementation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stepan Parunashvili's &lt;a href="https://stopa.io/post/222"&gt;Risp&lt;/a&gt;: Norvig's article inspired Stepan to write Risp, a Lisp interpreter in Rust. While I took some different decisions in my own code, this was a great source.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As Norvig does, we are going to actually create a Scheme interpreter (so not exactly Lisp). I will also likely depart from both articles at a certain point, but I'll do my best to note when I do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: A Basic Calculator
&lt;/h2&gt;

&lt;p&gt;I will not even try to match Norvig's ability to offer definitions and explanations, so please refer to his article above for any of that. In here, I'll only try to document my thought process and provide some guidance to anyone willing to try this feat.&lt;/p&gt;

&lt;p&gt;The only thing we need to remember at this point is the flow of an interpreter:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;our program ⟶ &lt;strong&gt;parse&lt;/strong&gt; ⟶ abstract syntax tree ⟶ &lt;strong&gt;eval&lt;/strong&gt; ⟶ result&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In short, our goal is to get the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; parse(program)
['define', 'r', 10], ['*', 'pi', ['*', 'r', 'r']]
&amp;gt; eval(parse(program))
314.1592653589793
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Type Definitions
&lt;/h3&gt;

&lt;p&gt;For now, our interpreter is going to have three kinds of values: a Scheme expression is either an Atom or List, with the former being a string or a float.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[derive(Debug, Clone, PartialEq)]
pub enum Atom {
    Symbol(String),
    Number(f64),
}

#[derive(Debug, Clone, PartialEq)]
pub enum Exp {
    Atom(Atom),
    List(Vec&amp;lt;Exp&amp;gt;),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are also going to need a Scheme environment, which is essentially a key-value mapping of {variable: value}; we are going to use a HashMap for this, although we wrap our own struct around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[derive(Debug, Clone, PartialEq)]
pub struct Env {
    data: HashMap&amp;lt;String, Exp&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parsing
&lt;/h3&gt;

&lt;p&gt;The first step in our parsing process is lexical analysis, which means we take the input character string and break it up into a sequence of tokens (at this point, parentheses, symbols, and numbers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenize("(+ 1 2)") //=&amp;gt; ["(", "+", "1", "2", ")"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we do that as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn tokenize(exp: String) -&amp;gt; Vec&amp;lt;String&amp;gt; {
    exp.replace("(", " ( ")
        .replace(")", " ) ")
        .split_whitespace()
        .map(|x| x.to_string())
        .collect()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we proceed with syntactic analysis, in which the tokens are assembled into an abstract syntax tree.&lt;/p&gt;

&lt;p&gt;Essentially, we take the first token; if it’s the beginning of a list “(“, we start reading and parsing the tokens that follow, until we hit a closing parenthesis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn read_from_tokens(tokens: &amp;amp;mut Vec&amp;lt;String&amp;gt;) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    if tokens.is_empty() {
        return Err("No input provided. Please provide a Scheme expression.".to_string());
    }
    let token = tokens.remove(0);
    if token == "(" {
        let mut list: Vec&amp;lt;Exp&amp;gt; = Vec::new();
        while tokens[0] != ")" {
            list.push(read_from_tokens(tokens)?);
        }
        tokens.remove(0); // pop off ')'
        Ok(Exp::List(list))
    } else if token == ")" {
        return Err(format!("Unexpected ')'."));
    } else {
        Ok(atom(token))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, it can only be an atom, so we parse that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn atom(token: String) -&amp;gt; Exp {
    match token.parse::&amp;lt;f64&amp;gt;() {
        Ok(num) =&amp;gt; Exp::Atom(Atom::Number(num)),
        Err(_) =&amp;gt; Exp::Atom(Atom::Symbol(token)),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we put everything together in our parse() function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn parse(input: String) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    // Read a Scheme expression from a string
    read_from_tokens(&amp;amp;mut tokenize(input))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment
&lt;/h3&gt;

&lt;p&gt;Environments are where we store variable definitions and built-in functions, so we need to create a default one for our interpreter.&lt;/p&gt;

&lt;p&gt;To start implementing our basic built-in operations (+, -), we need a way to save Rust functions references. To do that, we need to update our Exp enum to allow an additional type, Func(fn(&amp;amp;[Exp]):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[derive(Debug, Clone, PartialEq)]
pub enum Exp {
    Bool(bool),
    Atom(Atom),
    List(Vec&amp;lt;Exp&amp;gt;),
    Func(fn(&amp;amp;[Exp]) -&amp;gt; Exp),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can implement some methods and functions for our Env struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl Env {
    fn new() -&amp;gt; Self {
        Env {
            data: HashMap::new(),
        }
    }
    pub fn get(&amp;amp;self, k: &amp;amp;str) -&amp;gt; Option&amp;lt;&amp;amp;Exp&amp;gt; {
        self.data.get(k)
    }
}

pub fn standard_env() -&amp;gt; Env {
    // An environment with some Scheme standard procedures
    let mut env = Env::new();
    // Adding basic arithmetic operations
    env.insert("+".to_string(), Exp::Func(|args: &amp;amp;[Exp]| add(args)));
    env.insert("-".to_string(), Exp::Func(|args: &amp;amp;[Exp]| subtract(args)));

    env
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, our add() and subtract() functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn add(args: &amp;amp;[Exp]) -&amp;gt; Exp {
    let sum = args.iter().fold(0.0, |acc, arg| {
        if let Exp::Atom(Atom::Number(num)) = arg {
            acc + num
        } else {
            panic!("Expected a number");
        }
    });
    Exp::Atom(Atom::Number(sum))
}

pub fn subtract(args: &amp;amp;[Exp]) -&amp;gt; Exp {
    let first = if let Some(Exp::Atom(Atom::Number(n))) = args.iter().next() {
        *n
    } else {
        panic!("Expected a number");
    };
    let result = args.iter().skip(1).fold(first, |acc, arg| {
        if let Exp::Atom(Atom::Number(num)) = arg {
            acc - num
        } else {
            panic!("Expected a number");
        }
    });
    Exp::Atom(Atom::Number(result))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Evaluation:
&lt;/h3&gt;

&lt;p&gt;We are now ready to write our eval() function. As a refresher, right now our Exp can assume 4 values: an Atom (either a Symbol or a Number), a List, or a Func. This is the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn eval(exp: Exp, env: &amp;amp;Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
     match exp {
         Exp::Atom(Atom::Symbol(s)) =&amp;gt; {
             env.get(&amp;amp;s).cloned().ok_or_else(|| panic!("Undefined symbol: {}", s))
         },
         Exp::Atom(Atom::Number(_)) =&amp;gt; Ok(exp),
         Exp::List(list) =&amp;gt; {
             let first = &amp;amp;list[0];
             if let Exp::Atom(Atom::Symbol(ref s)) = first {
                 if let Some(Exp::Func(f)) = env.get(s) {
                     let args = list[1..].iter()
                         .map(|x| eval(x.clone(), env))
                         .collect::&amp;lt;Result&amp;lt;Vec&amp;lt;_&amp;gt;, _&amp;gt;&amp;gt;()?;
                     return Ok(f(&amp;amp;args))
                 } else {
                     panic!("Undefined function: {}", s);
                 }
             } else {
                 panic!("Expected a symbol");
             }
         },
         Exp::Func(_) =&amp;gt; Ok(exp),
     }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Let's make it prettier
&lt;/h3&gt;

&lt;p&gt;We now have functioning code, but an hypothetical input of "(+ 1 1)" will give us the not so pretty stdout of &lt;code&gt;Atom(Number(2.0))&lt;/code&gt;. Not great.&lt;/p&gt;

&lt;p&gt;To make it look better, we need to work with the Display trait of our Atom struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl fmt::Display for Atom {
    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;'_&amp;gt;) -&amp;gt; fmt::Result {
        match self {
            Atom::Symbol(s) =&amp;gt; write!(f, "{}", s),
            Atom::Number(n) =&amp;gt; write!(f, "{}", n),
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we add fmt::Display for Exp as well, so that it uses the Display implementation of Atom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl fmt::Display for Exp {
    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;'_&amp;gt;) -&amp;gt; fmt::Result {
        match self {
            Exp::Atom(atom) =&amp;gt; write!(f, "{}", atom),
            Exp::List(list) =&amp;gt; {
                let formatted_list: Vec&amp;lt;String&amp;gt; =
                    list.iter().map(|exp| format!("{}", exp)).collect();
                write!(f, "({})", formatted_list.join(" "))
            }
            Exp::Func(_) =&amp;gt; write!(f, "&amp;lt;function&amp;gt;"),
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  REPL
&lt;/h3&gt;

&lt;p&gt;One of Lisp's best features is the notion of an interactive read-eval-print loop: a way for a programmer to enter an expression, and see it immediately read, evaluated, and printed, without having to go through a lengthy build/compile/run cycle.&lt;/p&gt;

&lt;p&gt;We can do that by using a loop in our main() function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use std::env;

use rustylisp::{eval, parse, standard_env};

fn main() {
     let args: Vec&amp;lt;String&amp;gt; = env::args().skip(1).collect();

     if args.is_empty() {
         eprintln!("Error: No input provided. Please provide a Lisp expression.");
         std::process::exit(1);
     }

     let env = standard_env();
     let input = args.join(" ");
     let parsed_exp = match parse(input) {
         Ok(exp) =&amp;gt; exp,
         Err(e) =&amp;gt; {
             eprintln!("Error during parsing: {}", e);
             std::process::exit(1);
         }
     };
     let result = match eval(parsed_exp, &amp;amp;env) {
         Ok(res) =&amp;gt; res,
         Err(e) =&amp;gt; {
             eprintln!("Error during evaluation: {}", e);
             std::process::exit(1);
         },
     };
     println!("{:?}", result);
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà, the first implementation of our interpreter is done! We now have a REPL that allows us to perform addition and subtraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: A Better Calculator
&lt;/h2&gt;

&lt;p&gt;Let's add support for multiplication, division, and comparison operators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiplication and division
&lt;/h3&gt;

&lt;p&gt;To add multiplication and division, we need to create two new helper functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn multiply(args: &amp;amp;[Exp]) -&amp;gt; Exp {
    let product = args.iter().fold(1.0, |acc, arg| {
        if let Exp::Atom(Atom::Number(num)) = arg {
            acc * num
        } else {
            panic!("Expected a number");
        }
    });
    Exp::Atom(Atom::Number(product))
}

pub fn divide(args: &amp;amp;[Exp]) -&amp;gt; Exp {
    let first = if let Some(Exp::Atom(Atom::Number(n))) = args.iter().next() {
        *n
    } else {
        panic!("Expected a number");
    };
    let quotient = args.iter().skip(1).fold(first, |acc, arg| {
        if let Exp::Atom(Atom::Number(num)) = arg {
            if *num == 0.0 {
                panic!("Cannot divide by zero")
            }
            acc / num
        } else {
            panic!("Expected a number");
        }
    });
    Exp::Atom(Atom::Number(quotient))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we add them to our Env (since we are at it, let's also add the Pi constant):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use std::f64::consts::PI;
...
pub fn standard_env() -&amp;gt; Env {
    // An environment with some Scheme standard procedures
    let mut env = Env::new();
    // Adding basic arithmetic operations
    env.insert("+".to_string(), Exp::Func(|args: &amp;amp;[Exp]| add(args)));
    env.insert("-".to_string(), Exp::Func(|args: &amp;amp;[Exp]| subtract(args)));
    env.insert("*".to_string(), Exp::Func(|args: &amp;amp;[Exp]| multiply(args)));
    env.insert("/".to_string(), Exp::Func(|args: &amp;amp;[Exp]| divide(args)));
    // Adding pi
    env.insert("pi".to_string(), Exp::Atom(Atom::Number(PI)));

    env
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Booleans
&lt;/h3&gt;

&lt;p&gt;To add support for comparison operators, we first need to introduce booleans (otherwise, what's "(&amp;gt; 1 0)" going to return?); let's do that.&lt;/p&gt;

&lt;p&gt;First we edit our Exp enum&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub enum Exp {
    Bool(bool),
    Atom(Atom),
    List(Vec&amp;lt;Exp&amp;gt;),
    Func(fn(&amp;amp;[Exp]) -&amp;gt; Exp),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;at which point Rust will tell us to update Display&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl fmt::Display for Exp {
    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;'_&amp;gt;) -&amp;gt; fmt::Result {
        match self {
            Exp::Bool(a) =&amp;gt; write!(f, "{}", a),
            ...
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we'll change eval() to consider booleans&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn eval(exp: Exp, env: &amp;amp;Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    match exp {
        Exp::Bool(_) =&amp;gt; Ok(exp),
        Exp::Atom(Atom::Symbol(s)) =&amp;gt; env
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last but not least, we update our atom() function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn atom(token: String) -&amp;gt; Exp {
    match token.as_str() {
        "true" =&amp;gt; Exp::Bool(true),
        "false" =&amp;gt; Exp::Bool(false),
        // Numbers become numbers; every other token is a symbol
        _ =&amp;gt; match token.parse::&amp;lt;f64&amp;gt;() {
            Ok(num) =&amp;gt; Exp::Atom(Atom::Number(num)),
            Err(_) =&amp;gt; Exp::Atom(Atom::Symbol(token)),
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comparison Operators
&lt;/h3&gt;

&lt;p&gt;We are now ready to add comparison operators to our Env:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn standard_env() -&amp;gt; Env {
    ...
    // Adding comparison operators
    env.insert(
        "=".to_string(),
        Exp::Func(|args: &amp;amp;[Exp]| compare(args, "=")),
    );
    env.insert(
        "&amp;gt;".to_string(),
        Exp::Func(|args: &amp;amp;[Exp]| compare(args, "&amp;gt;")),
    );
    env.insert(
        "&amp;lt;".to_string(),
        Exp::Func(|args: &amp;amp;[Exp]| compare(args, "&amp;lt;")),
    );
    env.insert(
        "&amp;gt;=".to_string(),
        Exp::Func(|args: &amp;amp;[Exp]| compare(args, "&amp;gt;=")),
    );
    env.insert(
        "&amp;lt;=".to_string(),
        Exp::Func(|args: &amp;amp;[Exp]| compare(args, "&amp;lt;=")),
    );

    env
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our helper function compare():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub fn compare(args: &amp;amp;[Exp], op: &amp;amp;str) -&amp;gt; Exp {
    if args.len() != 2 {
        panic!("Comparison operators require exactly two arguments");
    }

    let a = if let Exp::Atom(Atom::Number(n)) = args[0] {
        n
    } else {
        panic!("Expected a number");
    };

    let b = if let Exp::Atom(Atom::Number(n)) = args[1] {
        n
    } else {
        panic!("Expected a number");
    };

    let result = match op {
        "=" =&amp;gt; a == b,
        "&amp;gt;" =&amp;gt; a &amp;gt; b,
        "&amp;lt;" =&amp;gt; a &amp;lt; b,
        "&amp;gt;=" =&amp;gt; a &amp;gt;= b,
        "&amp;lt;=" =&amp;gt; a &amp;lt;= b,
        _ =&amp;gt; panic!("Unknown operator"),
    };

    Exp::Bool(result)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a pretty decent calculator, supporting basic arithmetic operations and comparison operators!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Stepan's implementation uses an approach taken from Clojure, where comparison operators can take more than 2 args, and return true if they are in a monotonic order that satisfies the operator. I have instead decided to limit my interpreter to two args only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Almost a Language
&lt;/h2&gt;

&lt;p&gt;At this point, we can build on booleans to add &lt;em&gt;if&lt;/em&gt; statements, and we are going to introduce the &lt;em&gt;define&lt;/em&gt; keyword to allow us to create our own variables and functions within the Env.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define
&lt;/h3&gt;

&lt;p&gt;We need to update our eval() function so that it recognizes the &lt;strong&gt;'define'&lt;/strong&gt; keyword; when it does, it creates a new variable in our Env, using the first arg as the key, and the second as the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn eval(exp: Exp, env: &amp;amp;mut Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
     match exp {
         ...
         Exp::List(list) =&amp;gt; {
             let first = &amp;amp;list[0];
             if let Exp::Atom(Atom::Symbol(ref s)) = first {
                 if s == "define" {
                     if list.len() != 3 {
                         return Err("define requires exactly two arguments".into());
                     }

                     let var_name = match &amp;amp;list[1] {
                         Exp::Atom(Atom::Symbol(name)) =&amp;gt; name.clone(),
                         _ =&amp;gt; return Err("The first argument to define must be a symbol".into()),
                     };

                     let value = eval(list[2].clone(), env)?;
                     env.insert(var_name, value.clone());
                     Ok(value)
                 } else if let Some(Exp::Func(f)) = env.get(s) {
                     // Clone the function to avoid borrowing `env` later
                     let function = f.clone();
                     let args: Result&amp;lt;Vec&amp;lt;Exp&amp;gt;, String&amp;gt; = list[1..]
                         .iter()
                         .map(|x| eval(x.clone(), env))
                         .collect();
                     Ok(function(&amp;amp;args?))
                 } else {
                     Err(format!("Undefined function: {}", s))
                 }
             } else {
                 Err("Expected a symbol".into())
             }
         }
         Exp::Func(_) =&amp;gt; Ok(exp),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have some nice built-in functionality, allowing us to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; (define r 10)
10
&amp;gt; (+ r 5)
15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also further improve on this feature in the next step, so that we will be able to &lt;strong&gt;'define'&lt;/strong&gt; functions and turn this into a full-on language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved &lt;strong&gt;'define'&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To extend the define functionality to support the definition of new functions, we'll need to update the parser, evaluator, and the Env structure to handle function definitions and closures. Specifically, this involves recognizing when a function is being defined, parsing its arguments and body, and storing this in the environment.&lt;/p&gt;

&lt;p&gt;First we update the Exp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub enum Exp {
    Bool(bool),
    ...
    FuncDef {
         params: Vec&amp;lt;Exp&amp;gt;,
         body: Vec&amp;lt;Exp&amp;gt;,
         env: Env,
     },
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the related Display trait&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl fmt::Display for Exp {
    fn fmt(&amp;amp;self, f: &amp;amp;mut fmt::Formatter&amp;lt;'_&amp;gt;) -&amp;gt; fmt::Result {
        match self {
            ...
            Exp::FuncDef{..} =&amp;gt; write!(f, "&amp;lt;function&amp;gt;"),
         }
     }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and finally eval()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn eval(exp: Exp, env: &amp;amp;mut Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    match exp {
        ...
        Exp::List(list) =&amp;gt; {
             let first = &amp;amp;list[0];
             if let Exp::Atom(Atom::Symbol(ref s)) = first {
                if s == "define" {
                    if list.len() &amp;lt; 3 {
                         return Err("'define' requires at least two arguments".into());
                     }
                     // Define a new function
                     if let Exp::List(ref func) = list[1] {
                         if let Exp::Atom(Atom::Symbol(ref func_name)) = func[0] {
                             let params = func[1..].to_vec();
                             let body = list[2..].to_vec();
                             let lambda = Exp::FuncDef {
                                 params,
                                 body,
                                 env: env.clone(),
                             };
                             env.insert(func_name.clone(), lambda);
                             return Ok(Exp::Atom(Atom::Symbol(func_name.clone())));
                         }
                     // Define a new variable
                     } else if let Exp::Atom(Atom::Symbol(ref var_name)) = list[1] {
                         let value = eval(list[2].clone(), env)?;
                         env.insert(var_name.clone(), value.clone());
                         return Ok(value);
                     } else {
                         return Err("Invalid define syntax".into());
                     }
                 } else if let Some(exp) = env.get(s) {
                     if let Exp::Func(f) = exp {
                         // Clone the function to avoid borrowing `env` later
                         let function = f.clone();
                         let args: Result&amp;lt;Vec&amp;lt;Exp&amp;gt;, String&amp;gt; = list[1..]
                             .iter()
                             .map(|x| eval(x.clone(), env))
                             .collect();
                         return Ok(function(&amp;amp;args?));
                     } else if let Exp::FuncDef { params, body, env: closure_env } = exp {
                         // Clone `env` to avoid borrowing later
                         let env_clone = &amp;amp;mut env.clone();
                         let args: Result&amp;lt;Vec&amp;lt;Exp&amp;gt;, String&amp;gt; = list[1..]
                             .iter()
                             .map(|x| eval(x.clone(), env_clone))
                             .collect();
                         let mut local_env = closure_env.clone();
                         for (param, arg) in params.iter().zip(args?) {
                             if let Exp::Atom(Atom::Symbol(param_name)) = param {
                                 local_env.insert(param_name.clone(), arg);
                             } else {
                                 return Err("Invalid parameter name".into());
                             }
                         }
                         let mut result = Exp::Bool(false);
                         for exp in body {
                             result = eval(exp.clone(), &amp;amp;mut local_env)?;
                         }
                         return Ok(result);
                     }
                 }
                 return Err(format!("Undefined function: {}", s));
             } else {
                 Err("Expected a symbol".into())
             }
         }
         Exp::Func(_) =&amp;gt; Ok(exp),
         Exp::FuncDef { .. } =&amp;gt; {
             Err("Unexpected function definition".into())
         }
     }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the changes above, we now have the possibility to do something like this, which I think is very cool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; (define (square x) (* x x))
square
&amp;gt; (square 5)
25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  If statements (and some refactoring)
&lt;/h3&gt;

&lt;p&gt;As mentioned above, we can build on our booleans to implement if statements. This is the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn eval_if(list: &amp;amp;[Exp], env: &amp;amp;mut Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    if list.len() &amp;lt; 4 {
        return Err("'if' requires at least three arguments".into());
    }
    let condition = eval(list[1].clone(), env)?;
    match condition {
        Exp::Bool(true) =&amp;gt; eval(list[2].clone(), env),
        Exp::Bool(false) =&amp;gt; eval(list[3].clone(), env),
        _ =&amp;gt; Err("Invalid condition in if expression".into()),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the eval() function is getting quite long, we handle 'define' and 'if' separately&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn eval_define(list: &amp;amp;[Exp], env: &amp;amp;mut Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
     if list.len() &amp;lt; 3 {
         return Err("'define' requires at least two arguments".into());
     }
     // Define a new function
     if let Exp::List(ref func) = list[1] {
         if let Exp::Atom(Atom::Symbol(ref func_name)) = func[0] {
             let params = func[1..].to_vec();
             let body = list[2..].to_vec();
             let lambda = Exp::FuncDef {
                 params,
                 body,
                 env: env.clone(),
             };
             env.insert(func_name.clone(), lambda);
             return Ok(Exp::Atom(Atom::Symbol(func_name.clone())));
         } else {
             return Err("Invalid define syntax".into());
         }
     // Define a new variable
     } else if let Exp::Atom(Atom::Symbol(ref var_name)) = list[1] {
         let value = eval(list[2].clone(), env)?;
         env.insert(var_name.clone(), value.clone());
         return Ok(value);
     } else {
         return Err("Invalid define syntax".into());
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn eval(exp: Exp, env: &amp;amp;mut Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    match exp {
        ...
        Exp::List(list) =&amp;gt; {
             let first = &amp;amp;list[0];
             if let Exp::Atom(Atom::Symbol(ref s)) = first {
                 match s.as_str() {
                     "define" =&amp;gt; eval_define(&amp;amp;list, env),
                     "if" =&amp;gt; eval_if(&amp;amp;list, env),
                     _ =&amp;gt; {
                 // Truncated
                 }
             }
         }
         Exp::Func(_) =&amp;gt; Ok(exp),
         Exp::FuncDef { .. } =&amp;gt; Err("Unexpected function definition".into()),
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have added some new functionality, and can now type something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; (define r 10)
10
&amp;gt; (if (&amp;lt; r 8) true false)
false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: A Full Language
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is where I depart the most from Stepan's implementation. While he introduced a Lambda type for his Exp enum, I have decided to build upon FuncDef. My reasoning was that I &lt;em&gt;did not really need&lt;/em&gt; lambda functions &lt;strong&gt;at all&lt;/strong&gt;.&lt;br&gt;
In the end, typing &lt;code&gt;(define (circle-area r) (* pi (* r r)))&lt;/code&gt; was perfectly valid syntax with my implementation. What I &lt;em&gt;really needed&lt;/em&gt; was a scoped environment for new functions, which would also allow for recursion because the key was for &lt;em&gt;the function to access itself&lt;/em&gt;, and if this is not magic, I don't know what is.&lt;/p&gt;

&lt;p&gt;What's &lt;em&gt;even better&lt;/em&gt; is that all of the above could be achieved [drumroll] &lt;strong&gt;with one single line of code&lt;/strong&gt;: &lt;code&gt;local_env.insert(s.clone(), exp.clone());&lt;/code&gt;&lt;br&gt;
That's it. One line.&lt;/p&gt;

&lt;p&gt;The full eval() function is below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn eval(exp: Exp, env: &amp;amp;mut Env) -&amp;gt; Result&amp;lt;Exp, String&amp;gt; {
    match exp {
        Exp::Bool(_) =&amp;gt; Ok(exp),
        Exp::Atom(Atom::Symbol(s)) =&amp;gt; env
            .get(&amp;amp;s)
            .cloned()
            .ok_or_else(|| format!("Undefined symbol: {}", s)),
        Exp::Atom(Atom::Number(_)) =&amp;gt; Ok(exp),
        Exp::List(list) =&amp;gt; {
            let first = &amp;amp;list[0];
            if let Exp::Atom(Atom::Symbol(ref s)) = first {
                match s.as_str() {
                    "define" =&amp;gt; eval_define(&amp;amp;list, env),
                    "if" =&amp;gt; eval_if(&amp;amp;list, env),
                    _ =&amp;gt; {
                        if let Some(exp) = env.get(s) {
                            match exp {
                                Exp::Func(f) =&amp;gt; {
                                    // Clone the function to avoid borrowing `env` later
                                    let function = f.clone();
                                    let args: Result&amp;lt;Vec&amp;lt;Exp&amp;gt;, String&amp;gt; =
                                        list[1..].iter().map(|x| eval(x.clone(), env)).collect();
                                    Ok(function(&amp;amp;args?))
                                }
                                Exp::FuncDef {
                                    params,
                                    body,
                                    env: closure_env,
                                } =&amp;gt; {
                                    // Clone `env` to avoid borrowing later
                                    let env_clone = &amp;amp;mut env.clone();
                                    let args: Result&amp;lt;Vec&amp;lt;Exp&amp;gt;, String&amp;gt; = list[1..]
                                        .iter()
                                        .map(|x| eval(x.clone(), env_clone))
                                        .collect();
                                    let mut local_env = closure_env.clone();
                                    local_env.insert(s.clone(), exp.clone());
                                    for (param, arg) in params.iter().zip(args?) {
                                        if let Exp::Atom(Atom::Symbol(param_name)) = param {
                                            local_env.insert(param_name.clone(), arg);
                                        } else {
                                            return Err("Invalid parameter name".into());
                                        }
                                    }
                                    let mut result = Exp::Bool(false);
                                    for exp in body {
                                        result = eval(exp.clone(), &amp;amp;mut local_env)?;
                                    }
                                    Ok(result)
                                }
                                _ =&amp;gt; Err(format!("Undefined function: {}", s)),
                            }
                        } else {
                            Err(format!("Undefined function: {}", s))
                        }
                    }
                }
            } else {
                Err("Expected a symbol".into())
            }
        }
        Exp::Func(_) =&amp;gt; Ok(exp),
        Exp::FuncDef { .. } =&amp;gt; Err("Unexpected function definition".into()),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the support for lambdas, we can now harness the power of recursion, and write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; (define fact (n) (if (&amp;lt;= n 1) 1 (* n (fact (- n 1)))))
fact
&amp;gt; (fact 5)
120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fin
&lt;/h2&gt;

&lt;p&gt;Rest here weary traveler. You've reached the end of this post.&lt;/p&gt;

&lt;p&gt;My implementation is far from complete, and even further from elegant. I have willingly omitted some parts of my code in order to focus on what I deemed most important, and I have since done some refactoring as well. &lt;/p&gt;

&lt;p&gt;You can find my full project &lt;a href="https://github.com/galzmarc/rustylisp/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Contributions are more than welcome, so if you get to it, send me your thoughts 🙂.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>programming</category>
      <category>coding</category>
    </item>
    <item>
      <title>Fashionably Late to the Party: My Journey with the Cloud Resume Challenge</title>
      <dc:creator>galzmarc</dc:creator>
      <pubDate>Tue, 27 Jun 2023 18:33:15 +0000</pubDate>
      <link>https://dev.to/galzmarc/fashionably-late-to-the-party-my-journey-with-the-cloud-resume-challenge-5f3c</link>
      <guid>https://dev.to/galzmarc/fashionably-late-to-the-party-my-journey-with-the-cloud-resume-challenge-5f3c</guid>
      <description>&lt;p&gt;I suppose this story begins in 2021, right in the middle of Covid. I was bored because of the lockdown, so my pastime of choice had to be either bread baking or something I had just bumped into on Reddit: Stanford’s &lt;a href="https://codeinplace.stanford.edu"&gt;Code in Place&lt;/a&gt;.&lt;br&gt;
Not that I do not appreciate a freshly baked loaf, but I opted for the latter, and oh boy was I hooked. I had never written a single line of code before, but I’ve always been intrigued by technology in general, and I was amazed by the possibilities offered by programming.&lt;br&gt;
After that, hopelessly in love, I dove head first into &lt;a href="https://www.theodinproject.com"&gt;The Odin Project&lt;/a&gt; and started learning web development.&lt;/p&gt;

&lt;p&gt;At that time I was working for Amazon (on the retail side), but I wanted my job to be closer to coding and computers and technology, because those were the things I loved. Enter AWS.&lt;br&gt;
Oh yeah, spoiler alert: I work at AWS. I know this might sounds like cheating, but there’s a catch: I work in customer support, and I can assure you my job is not technical at all.&lt;/p&gt;

&lt;p&gt;Anyway, I am obviously exposed to the whole cloud computing environment, so I decided to learn more about it and I finally got my Cloud Practitioner last January (hence the title). Which is good per se, but kind of left me like “Ok, so what?”, and that's when I came across the &lt;a href="https://cloudresumechallenge.dev"&gt;Cloud Resume Challenge&lt;/a&gt;, and this story can finally really begin.&lt;/p&gt;

&lt;p&gt;The Cloud Resume Challenge has been created by Forrest Brazeal, and it essentially consists in writing a resume in HTML and then deploying it online using the cloud infrastructure of your favorite provider (needless to say I picked AWS, because it’s the one I have the most exposure to).&lt;br&gt;
Completing the challenge provides people with a lot of useful skills in the cloud space like software development, networking, IaC, CI/CD, serverless computing and, most importantly, how to google things (trust me, it’s invaluable).&lt;/p&gt;

&lt;p&gt;Here's how I tackled the challenge:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="//galzmarc.com"&gt;The Resume&lt;/a&gt;: HTML | CSS | JS
&lt;/h3&gt;

&lt;p&gt;This was easy peasy. I already had experience with HTML, CSS and JavaScript, so creating the resume itself was a breeze, and I did not spend too much time on it. Is it pretty? Not really. Could I do better? Probably, but crafting a fancy resume was not the goal here.&lt;br&gt;
I have also decided to use vanilla HTML, CSS and JS because I don’t want to make this heavier and more complicated than it needs to be, and I think it doesn't really makes sense to use React or any other framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Infrastructure Part I: S3 | CloudFront
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PCaX7LHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnlgn4b5dh409q8qvfwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PCaX7LHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnlgn4b5dh409q8qvfwk.png" alt="Frontend architecture" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hosting the resume using an S3 bucket was pretty easy as well, I knew that the S3 documentation had a guide for that so this step wasn’t a challenge either.&lt;br&gt;
The AWS documentation came to the rescue again in setting up CloudFront, as there is a guide for that too. There are some concepts that are not covered, like Origin Access Control and redirecting HTTP to HTTPS, but those issues are either straightforward or easily solved through some googling.&lt;/p&gt;

&lt;p&gt;At this point of the challenge I should also buy a domain name and point it to the CloudFront distribution, but I decide this will be the last step of my challenge. I’ll get back to it later.&lt;/p&gt;

&lt;p&gt;The resume should also include a visitor counter, but this is not a problem; I know JavaScript and I can use localStorage to achieve this.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Backend: API Gateway | Lambda | Python | DynamoDB
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2m6ZJsOi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qeqonbd21u5k19ctdvzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2m6ZJsOi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qeqonbd21u5k19ctdvzj.png" alt="Backend architecture" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of localStorage, the visits counter should be stored in a database, and the challenge recommends DynamoDB. I have used MongoDB before, which is another NoSQL database, so this is not too difficult. I have to tinker a bit with DynamoDB (I am not really sure about partition keys and sort keys and the like) but I manage to set up a table without too many issues. Another step in the bag.&lt;/p&gt;

&lt;p&gt;My JavaScript code however should not talk directly to the database, but I should use API Gateway and Lambda instead.&lt;br&gt;
Once again it’s the AWS documentation to the rescue, as there is a tutorial to build an API Gateway REST API with Lambda integration. Awesome.&lt;br&gt;
In terms of architecture I decide to create two different functions—one to GET from the database and one to PUT (i.e. update the visits counter), and each of them will have a corresponding method in API Gateway.&lt;br&gt;
Writing the Lambda functions requires some time to complete, mainly because I haven’t used Python in a very long time, but it isn’t too bad overall. I test the two functions from the Lambda console and they work, so that’s great. Good, now I can link everything together and set up Lambda proxy integration in API Gateway et voilà, I test it and… I get a server error.&lt;br&gt;
And that, kids, is how I met &lt;del&gt;your mother&lt;/del&gt; CORS.&lt;/p&gt;

&lt;p&gt;It goes without saying that it took me some time to figure out I needed to configure CORS on both API Gateway &lt;em&gt;AND&lt;/em&gt; the response given by Lambda, by adding the proper headers.&lt;br&gt;
Once done however I can quickly modify my JavaScript code to fetch the API endpoint and happily watch the counter on my website go up.&lt;/p&gt;

&lt;p&gt;Hooray, my website is now complete!&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as Code: Terraform
&lt;/h3&gt;

&lt;p&gt;The Challenge actually uses AWS SAM for IaC, but I am going to do what the pros do and use Terraform instead.&lt;br&gt;
I followed a tutorial on YouTube and I went through a couple of guides that set up AWS services using Terraform; after that I always referenced the Hashicorp docs and I managed to complete the frontend part relatively without issues.&lt;br&gt;
The CORS shadow was looming large over the backend part, and it was definitely the most challenging issue to tackle concerning IaC. DynamoDB and the two Lambda functions were quite straightforward, but API Gateway took me days to figure out.&lt;/p&gt;

&lt;p&gt;Despite that, this was definitely my favorite part of the challenge, probably because IaC is the skill I most strongly associate with cloud.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD: Git | GitHub Actions
&lt;/h3&gt;

&lt;p&gt;I split my code into two repositories and push both of them to GitHub.&lt;br&gt;
I also set up GitHub Actions for the repo, so that any time new code is pushed to the repo it triggers a new “terraform apply”. Not too hard after watching some tutorials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Infrastructure Part II: Route 53 | ACM
&lt;/h3&gt;

&lt;p&gt;I had put off this part, but now it’s time to purchase a domain, get the SSL/TLS certificate and configure Route53 to route traffic to the CloudFront distribution. Again I found a guide in the AWS documentation and this was quite straightforward.&lt;br&gt;
Then I spent a fair amount of time writing the Terraform code, but that wasn’t too bad as the documentation provided most of it.&lt;/p&gt;

&lt;p&gt;The nightmare began when I naively deleted the original hosted zone that was automatically created when I registered the domain through Route53. I could easily recreate the hosted zone, but the ACM certificate would not validate for the sake of it.&lt;br&gt;
I have rewritten the Terraform code a hundred times, as I was absolutely convinced the error was there somewhere (spoiler: it wasn’t).&lt;br&gt;
It took me days to figure out I had to update the name servers for the domain itself, but after that it ran as smooth as Tennessee whiskey.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future developments: Go | Docker | Jenkins | ?
&lt;/h3&gt;

&lt;p&gt;So, what’s next? While working my way through the challenge I have identified some things I would like to tackle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My backend (Lambda) code is written in Python, but I am thinking of refactoring it in Golang because that’s what the cool kids use nowadays. I have some practice with Go so this might be a nice way to differentiate my codebase.&lt;/li&gt;
&lt;li&gt;The functions are imported as .zip files, but I suppose it might be possible to Dockerize them instead and use ECR/ECS (?). It’s a development that might be worth further investigation.&lt;/li&gt;
&lt;li&gt;CI/CD is currently set up using GitHub Actions, but a pro would probably use Jenkins instead.&lt;/li&gt;
&lt;li&gt;Open to suggestions: if you have any ideas, I’m all ears!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To sum up, the Cloud Resume Challenge worked its magic like it was supposed to, honing my skills and fueling my passion for programming and the cloud.&lt;br&gt;
I can't wait to see which projects await in my future and to evolve further as a developer.&lt;/p&gt;

&lt;p&gt;May the code be with me.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>challenge</category>
    </item>
  </channel>
</rss>
