diff --git a/src/lib/expr.test.ts b/src/lib/expr.test.ts
index eea76f7..3b7b4ce 100644
--- a/src/lib/expr.test.ts
+++ b/src/lib/expr.test.ts
@@ -4,7 +4,7 @@ import { Expr } from "./expr";
 describe("expr", () => {
   test.each([
     ["1", "1"],
-    // ["1 + 2", "3"],
+    ["1 + 2", "3"],
     // ["1 - 2", "-1"],
     // ["1 * 2", "2"],
     // ["1 / 2", "0.5"],
diff --git a/src/lib/expr.ts b/src/lib/expr.ts
index 27cc2fe..9926251 100644
--- a/src/lib/expr.ts
+++ b/src/lib/expr.ts
@@ -10,7 +10,7 @@ export function Expr({}, argv: TextPiece[]): ProcResult {
   const expression = argv.map(AsText).join(" ").trim();
 
   const parser = new ExpressionParser(expression);
-  const result = parser.parsePrefixOp();
+  const result = parser.parseSubExpression(0);
 
   if ("value" in result) {
     return { text: String(result.value) };
@@ -36,17 +36,40 @@ type Value = { value: number };
 type TokenHandler = {
   leftBindingPower: number;
   token: RegExp;
-  parse: (matched: string, parser: ExpressionParser) => Value | ErrorResult;
+  parse: (
+    left: Value,
+    matched: string,
+    parser: ExpressionParser
+  ) => Value | ErrorResult;
 };
 
+function map(
+  value: Value | ErrorResult,
+  op: (value: number) => Value | ErrorResult
+): Value | ErrorResult {
+  if ("error" in value) {
+    return value;
+  } else {
+    return op(value.value);
+  }
+}
+
 const Operators: TokenHandler[] = [
   {
     leftBindingPower: -1,
     token: NUMBER_TOKEN,
-    parse: (matched) => ({ value: Number(matched) }),
+    parse: (left, matched) => ({ value: Number(matched) }),
+  },
+  {
+    leftBindingPower: 10,
+    token: PLUS_TOKEN,
+    parse: ({ value: left }, matched, parser) =>
+      map(parser.parseSubExpression(11), (right) => ({ value: left + right })),
   },
 ];
 
+const ZERO = { value: 0 };
+
 class ExpressionParser {
   pos: number = 0;
 
@@ -64,11 +87,11 @@ class ExpressionParser {
   }
 
   /** Tries to match one of the given tokens & returns the value of the handler. If none match, returns null. */
-  tryAll(options: TokenHandler[]): Value | ErrorResult | null {
+  tryAll(left: Value, options: TokenHandler[]): Value | ErrorResult | null {
     for (const option of options) {
       const match = this.next(option.token);
       if (match) {
-        return option.parse(match, this);
+        return option.parse(left, match, this);
       }
     }
     return null;
@@ -77,10 +100,34 @@ class ExpressionParser {
   parsePrefixOp(): Value | ErrorResult {
     return (
       this.tryAll(
+        ZERO,
         Operators.filter((operator) => operator.leftBindingPower == -1)
       ) ?? { error: "Couldn't parse number" }
     );
   }
+
+  parseSubExpression(rightBindingPower: number): Value | ErrorResult {
+    let value = this.parsePrefixOp();
+    if ("error" in value) {
+      return value;
+    }
+
+    do {
+      const newValue = this.tryAll(
+        value,
+        Operators.filter(
+          (operator) => operator.leftBindingPower > rightBindingPower
+        )
+      );
+
+      if (newValue == null) {
+        break;
+      }
+      value = newValue;
+    } while (!("error" in value));
+
+    return value;
+  }
 }
 
 // parse expression: