Add ', `, ,, reader macros
diff --git a/src/lisp/lisp.c b/src/lisp/lisp.c
index 2d920ea..5006aa3 100644
--- a/src/lisp/lisp.c
+++ b/src/lisp/lisp.c
@@ -248,6 +248,8 @@
 
 bool readint(struct istream *is, value_t *val)
 {
+	skipws(is);
+
 	int number = 0;
 
 	if (!isdigit(is->peek(is)))
@@ -263,8 +265,70 @@
 	return true;
 }
 
+bool readquote(struct istream *is, value_t *val)
+{
+	skipws(is);
+
+	char c = is->peek(is);
+
+	if (c == '\'' || c == '`' || c == ',')
+	{
+		is->get(is);
+
+		if (c == '`' && is->peek(is) == '@')
+		{
+			// This is actually a splice
+			is->get(is);
+			c = '@';
+		}
+
+		// Read the next form and wrap it in the appropriate function
+
+		value_t wrapped;
+		bool has_next = read1(is, &wrapped);
+
+		if (!has_next)
+		{
+			fprintf(stderr, "Expected a form after reader macro char %c\n", c);
+			is->showpos(is, stderr);
+			err("Invalid reader macro");
+			return false;
+		}
+
+		value_t symbol = nil;
+
+		switch (c)
+		{
+		case '\'':
+			symbol = symval("quote");
+			break;
+		case '`':
+			symbol = symval("backquote");
+			break;
+		case ',':
+			symbol = symval("unquote");
+			break;
+		case '@':
+			symbol = symval("splice");
+			break;
+		}
+
+		*val = cons(symbol, cons(wrapped, nil));
+
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
 bool read1(struct istream *is, value_t *val)
 {
+	// This could all be one big short-circuiting || but that is ugly.
+	if (readquote(is, val))
+		return true;
+
 	if (readsym(is, val))
 		return true;
 
@@ -312,12 +376,25 @@
 	value_t v;
 
 	char *a = malloc_aligned(strlen(str) + 1);
+	strcpy(a, str);
 	v = (value_t)a;
 	v |= STRING_TAG;
 
 	return v;
 }
 
+value_t symval(char *str)
+{
+	value_t v;
+
+	char *a = malloc_aligned(strlen(str) + 1);
+	strcpy(a, str);
+	v = (value_t)a;
+	v |= SYMBOL_TAG;
+
+	return v;
+}
+
 bool integerp(value_t v)
 {
 	return (v & INT_MASK) == INT_TAG;
diff --git a/src/lisp/lisp.h b/src/lisp/lisp.h
index 9078b45..56fa09f 100644
--- a/src/lisp/lisp.h
+++ b/src/lisp/lisp.h
@@ -100,8 +100,15 @@
 bool readlist(struct istream *is, value_t *val);
 bool readint(struct istream *is, value_t *val);
 
+/**
+ * Read a quoted form, including `'` (quote) `\`` (backquote) and `,` (unquote)
+ * @returns true if read successfully, and sets `val`.
+ */
+bool readquote(struct istream *is, value_t *val);
+
 value_t intval(int i);
 value_t strval(char *str);
+value_t symval(char *str);
 value_t cons(value_t car, value_t cdr);
 bool read1(struct istream *is, value_t *val);
 value_t read(struct istream *is);
diff --git a/src/lisp/test.lisp b/src/lisp/test.lisp
index 9cfb8b7..3c8f9ea 100644
--- a/src/lisp/test.lisp
+++ b/src/lisp/test.lisp
@@ -17,3 +17,5 @@
   (let1 (unused-but-bound (cons 5 6))
     (let1 (val (cons 1 (cons 2 (cons 3 nil))))
       (calls-gc val))))
+
+'(hello)
\ No newline at end of file