# Task 03: Variable Injection **Phase**: 1 - Core (MVP) **Priority**: High **Estimated Time**: 1-2 hours **Dependencies**: Task 02 (Basic Execution) ## Objective Implement variable injection so that `:var` header arguments work correctly, allowing data to be passed from org-mode into Elixir code blocks. ## Prerequisites - Task 02 completed - Basic execution working ## Background Org-babel allows passing variables to code blocks: ```org #+BEGIN_SRC elixir :var x=5 :var name="Alice" "Hello, #{name}! x = #{x}" #+END_SRC ``` We need to: 1. Convert Elisp values to Elixir syntax 2. Generate Elixir variable assignment statements 3. Prepend these to the code before execution ## Steps ### Step 1: Implement Elisp to Elixir conversion Add to `ob-elixir.el`: ```elisp ;;; Type Conversion (defun ob-elixir--elisp-to-elixir (value) "Convert Elisp VALUE to Elixir literal syntax. Handles: - nil -> nil - t -> true - numbers -> numbers - strings -> quoted strings - symbols -> atoms - lists -> Elixir lists - vectors -> tuples" (cond ;; nil ((null value) "nil") ;; Boolean true ((eq value t) "true") ;; Numbers ((numberp value) (number-to-string value)) ;; Strings ((stringp value) (format "\"%s\"" (ob-elixir--escape-string value))) ;; Symbols become atoms (except special ones) ((symbolp value) (let ((name (symbol-name value))) (cond ((string= name "hline") ":hline") ((string-match-p "^[a-z_][a-zA-Z0-9_]*[?!]?$" name) (format ":%s" name)) (t (format ":\"%s\"" name))))) ;; Vectors become tuples ((vectorp value) (format "{%s}" (mapconcat #'ob-elixir--elisp-to-elixir (append value nil) ", "))) ;; Lists ((listp value) (format "[%s]" (mapconcat #'ob-elixir--elisp-to-elixir value ", "))) ;; Fallback (t (format "%S" value)))) ``` ### Step 2: Implement string escaping ```elisp (defun ob-elixir--escape-string (str) "Escape special characters in STR for Elixir string literal." (let ((result str)) ;; Escape backslashes first (setq result (replace-regexp-in-string "\\\\" "\\\\\\\\" result)) ;; Escape double quotes (setq result (replace-regexp-in-string "\"" "\\\\\"" result)) ;; Escape newlines (setq result (replace-regexp-in-string "\n" "\\\\n" result)) ;; Escape tabs (setq result (replace-regexp-in-string "\t" "\\\\t" result)) result)) ``` ### Step 3: Implement variable assignments function ```elisp ;;; Variable Handling (defun org-babel-variable-assignments:elixir (params) "Return list of Elixir statements assigning variables from PARAMS. Each statement has the form: var_name = value" (mapcar (lambda (pair) (let ((name (car pair)) (value (cdr pair))) (format "%s = %s" (ob-elixir--var-name name) (ob-elixir--elisp-to-elixir value)))) (org-babel--get-vars params))) (defun ob-elixir--var-name (name) "Convert NAME to a valid Elixir variable name. Elixir variables must start with lowercase or underscore." (let ((str (if (symbolp name) (symbol-name name) name))) ;; Ensure starts with lowercase or underscore (if (string-match-p "^[a-z_]" str) str (concat "_" str)))) ``` ### Step 4: Update the execute function Modify `org-babel-execute:elixir` to use variable assignments: ```elisp (defun org-babel-execute:elixir (body params) "Execute a block of Elixir code with org-babel. BODY is the Elixir code to execute. PARAMS is an alist of header arguments. This function is called by `org-babel-execute-src-block'." (let* ((result-type (cdr (assq :result-type params))) (result-params (cdr (assq :result-params params))) ;; Expand body with variable assignments (full-body (org-babel-expand-body:generic body params (org-babel-variable-assignments:elixir params))) (result (ob-elixir--execute full-body result-type))) (org-babel-reassemble-table (org-babel-result-cond result-params result (org-babel-script-escape result)) (org-babel-pick-name (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name (cdr (assq :rowname-names params)) (cdr (assq :rownames params)))))) ``` ### Step 5: Add tests for type conversion Add to `test/test-ob-elixir.el`: ```elisp ;;; Type Conversion Tests (ert-deftest ob-elixir-test-convert-nil () "Test nil conversion." (should (equal "nil" (ob-elixir--elisp-to-elixir nil)))) (ert-deftest ob-elixir-test-convert-true () "Test t conversion." (should (equal "true" (ob-elixir--elisp-to-elixir t)))) (ert-deftest ob-elixir-test-convert-integer () "Test integer conversion." (should (equal "42" (ob-elixir--elisp-to-elixir 42))) (should (equal "-10" (ob-elixir--elisp-to-elixir -10)))) (ert-deftest ob-elixir-test-convert-float () "Test float conversion." (should (equal "3.14" (ob-elixir--elisp-to-elixir 3.14)))) (ert-deftest ob-elixir-test-convert-string () "Test string conversion." (should (equal "\"hello\"" (ob-elixir--elisp-to-elixir "hello")))) (ert-deftest ob-elixir-test-convert-string-escaping () "Test string escaping." (should (equal "\"say \\\"hi\\\"\"" (ob-elixir--elisp-to-elixir "say \"hi\""))) (should (equal "\"line1\\nline2\"" (ob-elixir--elisp-to-elixir "line1\nline2")))) (ert-deftest ob-elixir-test-convert-symbol () "Test symbol conversion to atom." (should (equal ":foo" (ob-elixir--elisp-to-elixir 'foo))) (should (equal ":ok" (ob-elixir--elisp-to-elixir 'ok)))) (ert-deftest ob-elixir-test-convert-list () "Test list conversion." (should (equal "[1, 2, 3]" (ob-elixir--elisp-to-elixir '(1 2 3)))) (should (equal "[\"a\", \"b\"]" (ob-elixir--elisp-to-elixir '("a" "b"))))) (ert-deftest ob-elixir-test-convert-nested-list () "Test nested list conversion." (should (equal "[[1, 2], [3, 4]]" (ob-elixir--elisp-to-elixir '((1 2) (3 4)))))) (ert-deftest ob-elixir-test-convert-vector () "Test vector to tuple conversion." (should (equal "{1, 2, 3}" (ob-elixir--elisp-to-elixir [1 2 3])))) ``` ### Step 6: Add tests for variable injection ```elisp ;;; Variable Injection Tests (ert-deftest ob-elixir-test-variable-assignments () "Test variable assignment generation." (let ((params '((:var . ("x" . 5)) (:var . ("name" . "Alice"))))) (let ((assignments (org-babel-variable-assignments:elixir params))) (should (member "x = 5" assignments)) (should (member "name = \"Alice\"" assignments))))) (ert-deftest ob-elixir-test-var-execution () "Test code execution with variables." (skip-unless (executable-find ob-elixir-command)) (let* ((params '((:var . ("x" . 10)))) (var-lines (org-babel-variable-assignments:elixir params)) (full-body (concat (mapconcat #'identity var-lines "\n") "\nx * 2"))) (should (equal "20" (ob-elixir--execute full-body 'value))))) (ert-deftest ob-elixir-test-var-list () "Test passing list as variable." (skip-unless (executable-find ob-elixir-command)) (let* ((params '((:var . ("data" . (1 2 3))))) (var-lines (org-babel-variable-assignments:elixir params)) (full-body (concat (mapconcat #'identity var-lines "\n") "\nEnum.sum(data)"))) (should (equal "6" (ob-elixir--execute full-body 'value))))) ``` ### Step 7: Test in an org buffer Update `test.org`: ```org * Variable Injection Tests ** Simple variable #+BEGIN_SRC elixir :var x=42 x * 2 #+END_SRC #+RESULTS: : 84 ** String variable #+BEGIN_SRC elixir :var name="World" "Hello, #{name}!" #+END_SRC #+RESULTS: : Hello, World! ** Multiple variables #+BEGIN_SRC elixir :var x=10 :var y=20 x + y #+END_SRC #+RESULTS: : 30 ** List variable #+BEGIN_SRC elixir :var numbers='(1 2 3 4 5) Enum.sum(numbers) #+END_SRC #+RESULTS: : 15 ** Table as variable #+NAME: my-data | a | 1 | | b | 2 | | c | 3 | #+BEGIN_SRC elixir :var data=my-data Enum.map(data, fn [k, v] -> "#{k}=#{v}" end) #+END_SRC ``` ## Acceptance Criteria - [ ] `ob-elixir--elisp-to-elixir` correctly converts all Elisp types - [ ] `org-babel-variable-assignments:elixir` generates valid Elixir code - [ ] `:var x=5` works in org blocks - [ ] `:var name="string"` works with string values - [ ] Multiple `:var` arguments work - [ ] Lists and tables can be passed as variables - [ ] All tests pass: `make test` ## Edge Cases to Consider 1. **Variable name conflicts**: Elixir variables must start with lowercase 2. **Special characters in strings**: Quotes, newlines, backslashes 3. **Empty lists**: Should produce `[]` 4. **Mixed type lists**: `[1, "two", :three]` 5. **hline in tables**: Special symbol for table separators ## Files Modified - `ob-elixir.el` - Add variable handling functions - `test/test-ob-elixir.el` - Add variable tests ## References - [docs/03-org-babel-implementation-guide.md](../docs/03-org-babel-implementation-guide.md) - Variable Handling section - [docs/04-elixir-integration-strategies.md](../docs/04-elixir-integration-strategies.md) - Data Type Conversion section