Aprende EELisp construyendo proyectos reales. Desde tu primera expresión hasta una calculadora de interés compuesto, un control de gastos y un organizador personal inspirado en Lotus Agenda.
Abre la barra lateral del REPL en EEditor con ⌘⇧L. Aparece como un panel en el lado derecho del editor y permanece abierta mientras cambias de archivo. Escribe una expresión y pulsa ⌘Return para evaluarla. El historial de comandos se guarda entre sesiones — usa las flechas Arriba/Abajo para recuperar comandos anteriores.
En EELisp, todo es una expresión entre paréntesis. El primer elemento es la función y el resto son argumentos:
;; Aritmética — el operador va primero (+ 1 2) ; → 3 (+ 1 2 3 4) ; → 10 (¡variádica!) (* 6 7) ; → 42 (/ 100 3) ; → 33.333... ;; Anidamiento — las expresiones internas se evalúan primero (+ (* 3 4) (- 10 5)) ; → 17 (12 + 5) ;; Cadenas de texto (str "Hello, " "world!") ; → "Hello, world!" (str-upper "hello") ; → "HELLO" ;; Funciones matemáticas (pow 2 10) ; → 1024 (round 3.14159 2) ; → 3.14 (redondear a N decimales) (abs -42) ; → 42 ;; Las comparaciones devuelven true o false (> 10 5) ; → true (= 1 1) ; → true
(+1 2) funciona igual que (+ 1 2).
Usa def para asignar un nombre a un valor:
(def name "Alice") (def age 30) (def pi 3.14159) (str "Hi, " name "! You are " age " years old.") ; → "Hi, Alice! You are 30 years old."
Usa defn para crear funciones con nombre:
;; Una función que duplica un número (defn double (x) (* x 2)) (double 21) ; → 42 ;; Una función con múltiples parámetros (defn greet (name greeting) (str greeting ", " name "!")) (greet "Bob" "Good morning") ; → "Good morning, Bob!" ;; Lógica condicional con if (defn abs-val (n) (if (< n 0) (- 0 n) n)) (abs-val -5) ; → 5 (abs-val 3) ; → 3
Usa fn para funciones de un solo uso sin nombre:
(map (fn (x) (* x x)) '(1 2 3 4 5)) ; → (1 4 9 16 25)
Las listas son el pan de cada día de Lisp. Contienen secuencias ordenadas de valores:
;; Crear listas (def numbers (list 1 2 3 4 5)) (def fruits '("apple" "banana" "cherry")) ;; Acceder a elementos (first numbers) ; → 1 (second numbers) ; → 2 (last numbers) ; → 5 (nth fruits 1) ; → "banana" (length numbers) ; → 5 ;; Construir listas (cons 0 numbers) ; → (0 1 2 3 4 5) (append numbers '(6 7)) ; → (1 2 3 4 5 6 7) (range 1 11) ; → (1 2 3 4 5 6 7 8 9 10)
Las tres operaciones más potentes sobre listas:
;; map — aplica una función a cada elemento (map double (range 1 6)) ; → (2 4 6 8 10) ;; filter — conserva los elementos que cumplen una condición (filter even? (range 1 11)) ; → (2 4 6 8 10) ;; reduce — combina todos los elementos en un solo valor (reduce + 0 (range 1 101)) ; → 5050 (suma del 1 al 100) ;; Combínalos: suma de cuadrados de números pares del 1 al 10 (reduce + 0 (map (fn (x) (* x x)) (filter even? (range 1 11)))) ; → 220 (4 + 16 + 36 + 64 + 100)
Los diccionarios almacenan pares clave-valor usando palabras clave (con el prefijo :):
;; Crear un diccionario (def person {:name "Alice" :age 30 :city "NYC"}) ;; Acceder a valores (dict-get person :name) ; → "Alice" (dict-get person :age) ; → 30 ;; Actualizar (devuelve un nuevo diccionario) (def older (dict-set person :age 31)) (dict-get older :age) ; → 31 ;; Inspeccionar (dict-keys person) ; → (:name :age :city) (dict-values person) ; → ("Alice" 30 "NYC") (dict-has person :email) ; → false ;; Fusionar dos diccionarios (el segundo prevalece en conflictos) (dict-merge person {:email "alice@example.com" :age 31}) ; → {:name "Alice" :age 31 :city "NYC" :email "alice@example.com"}
Vamos a construir una calculadora financiera real que computa el interés compuesto anual. Esta es la fórmula:
A = P × (1 + r/n)^(n×t)
Donde P = capital inicial, r = tasa anual, n = capitalizaciones por año, t = años.
;; Fórmula de interés compuesto usando pow ;; A = P * (1 + r/n)^(n*t) (defn compound-interest (principal rate compounds-per-year years) (let ((r (/ rate 100)) (n compounds-per-year)) (* principal (pow (+ 1 (/ r n)) (* n years))))) ;; Prueba: 1000 $ al 5% anual, capitalización mensual, durante 10 años (round (compound-interest 1000 5 12 10) 2) ; → 1647.01 (¡has ganado 647,01 $ en intereses!)
;; Mostrar el saldo al final de cada año (defn yearly-breakdown (principal rate compounds-per-year years) (println (str "Investment: $" principal " at " rate "%")) (println (str "Compounding: " compounds-per-year "x/year for " years " years")) (println "---") (for-each year (range 1 (+ years 1)) (let ((balance (compound-interest principal rate compounds-per-year year)) (interest (- balance principal))) (println (str "Year " year ": $" (round balance 2) " (+" (round interest 2) " interest)"))))) (yearly-breakdown 10000 7 12 5)
;; Comparar múltiples tasas de interés lado a lado (defn compare-rates (principal years . rates) (println (str "Comparing $" principal " over " years " years:")) (for-each rate rates (let ((final (compound-interest principal rate 12 years)) (gain (round (- final principal) 2))) (println (str " " rate "% → $" (round final 2) " (+" gain ")"))))) (compare-rates 10000 20 3 5 7 10)
. rates) para aceptar un número variable de argumentos. Esto es perfecto para comparar múltiples valores.
EELisp tiene una base de datos SQLite integrada. Vamos a usarla para gestionar contactos — como un mini dBASE.
;; Definir una tabla de contactos con campos tipados (deftable contacts (name:string email:string phone:string city:string age:number)) ;; La tabla está creada. Puedes verificar: (describe contacts) (tables) ; → ("contacts")
;; Insertar contactos — cada uno devuelve el nuevo registro con un ID automático (insert contacts {:name "Alice" :email "alice@mail.com" :phone "555-0101" :city "New York" :age 30}) (insert contacts {:name "Bob" :email "bob@mail.com" :phone "555-0102" :city "San Francisco" :age 25}) (insert contacts {:name "Carol" :email "carol@mail.com" :phone "555-0103" :city "New York" :age 35}) (insert contacts {:name "Dan" :email "dan@mail.com" :phone "555-0104" :city "Chicago" :age 28}) (insert contacts {:name "Eve" :email "eve@mail.com" :phone "555-0105" :city "San Francisco" :age 32})
;; Ver todos los contactos (se muestran como una tabla formateada) (query contacts) ;; Buscar contactos en Nueva York (query contacts :where "city = ?" :params (list "New York")) ;; Buscar contactos mayores de 30, ordenados por nombre (query contacts :where "age > ?" :params (list 30) :order "name") ;; Contar contactos (count-records contacts) ; → 5 ;; Seleccionar solo columnas específicas (query contacts :select "name, city" :order "city")
;; Actualizar la ciudad de Bob (registro ID 2) (update contacts 2 {:city "Los Angeles"}) ;; Verificar el cambio (query contacts :where "name = ?" :params (list "Bob")) ;; Eliminación lógica de Dan (se marca como eliminado pero no desaparece) (delete contacts 4) (count-records contacts) ; → 4 (Dan está oculto) ;; Purgar registros eliminados permanentemente (pack contacts) (count-records contacts) ; → 4
;; Extraer registros de una consulta y procesarlos (def nyc-people (records (query contacts :where "city = ?" :params (list "New York")))) ;; Obtener nombres de contactos en NYC (map (fn (r) (field-get r :name)) nyc-people) ; → ("Alice" "Carol") ;; Edad media de todos los contactos (let ((all (records (query contacts))) (ages (map (fn (r) (field-get r :age)) all))) (/ (reduce + 0 ages) (length ages))) ; → 30.5
Vamos a construir un control de gastos personal con resúmenes por categoría.
(deftable expenses (description:string amount:number category:string date:string)) ;; Añadir algunos gastos (insert expenses {:description "Groceries" :amount 85.50 :category "food" :date "2026-02-01"}) (insert expenses {:description "Electric bill" :amount 120.00 :category "utilities" :date "2026-02-03"}) (insert expenses {:description "Coffee shop" :amount 12.75 :category "food" :date "2026-02-05"}) (insert expenses {:description "Bus pass" :amount 75.00 :category "transport" :date "2026-02-01"}) (insert expenses {:description "Restaurant" :amount 45.00 :category "food" :date "2026-02-10"}) (insert expenses {:description "Internet" :amount 65.00 :category "utilities" :date "2026-02-03"}) (insert expenses {:description "Books" :amount 32.99 :category "education" :date "2026-02-15"})
;; Gasto total (let ((all (records (query expenses)))) (reduce + 0 (map (fn (r) (field-get r :amount)) all))) ; → 436.24 ;; Gasto por categoría (defn category-total (cat) (let ((rows (records (query expenses :where "category = ?" :params (list cat))))) (reduce + 0 (map (fn (r) (field-get r :amount)) rows)))) (println (str "Food: $" (category-total "food"))) (println (str "Utilities: $" (category-total "utilities"))) (println (str "Transport: $" (category-total "transport"))) (println (str "Education: $" (category-total "education")))
;; Los 3 gastos más altos por importe (query expenses :order "amount" :desc true :limit 3)
Un miniproyecto clásico que muestra cómo combinar tablas con funciones auxiliares.
(deftable todos (title:string priority:number done:bool)) ;; Auxiliar: añadir una tarea (defn add-todo (title priority) (insert todos {:title title :priority priority :done false}) (println (str "Added: " title))) ;; Auxiliar: marcar como completada (defn done! (id) (update todos id {:done true}) (println "Done!")) ;; Auxiliar: mostrar tareas pendientes (defn pending () (query todos :where "done = ?" :params (list false) :order "priority")) ;; Auxiliar: mostrar tareas completadas (defn completed () (query todos :where "done = ?" :params (list true)))
;; Añadir tareas (número de prioridad más bajo = más importante) (add-todo "Write README" 1) (add-todo "Fix login bug" 1) (add-todo "Update tests" 2) (add-todo "Refactor CSS" 3) (add-todo "Deploy to staging" 2) ;; Ver tareas pendientes (pending) ;; Completar una tarea (done! 1) ; marcar "Write README" como completada ;; Ver lo que queda (pending) ;; Ver lo completado (completed)
.el y cargarlas con :load todo-helpers.el cada vez que abras el REPL.
EELisp tiene dos comandos de vista interactivos: browse muestra una tabla/cuadrícula navegable, y edit abre un formulario CRUD. Vamos a crear un libro de recetas usando ambos.
Nuestra tabla de recetas usa varios tipos de campo: string para texto corto, number para cantidades, bool para indicadores y memo para notas más largas.
(deftable recipes
(name:string
cuisine:string
servings:number
vegetarian:bool
notes:memo))
(insert recipes {:name "Pasta Carbonara" :cuisine "Italian" :servings 4 :vegetarian false :notes "Use guanciale, not bacon."}) (insert recipes {:name "Vegetable Curry" :cuisine "Indian" :servings 6 :vegetarian true :notes "Toast spices first."}) (insert recipes {:name "Miso Soup" :cuisine "Japanese" :servings 2 :vegetarian true :notes "Don't boil the miso."})
El comando browse abre una tabla/cuadrícula navegable que muestra todos los registros a la vez:
;; Abrir la vista de tabla (browse recipes)
La vista de tabla muestra todos los registros en una cuadrícula. Haz clic en una fila para seleccionarla, o usa Prev/Next para navegar.
browse, edit y defform desde un bloque ```eelisp en tus notas Markdown. Pulsa ⌘⇧Return y el formulario o tabla se abre como un panel lateral interactivo junto al editor — para que puedas ver tus datos y tus notas al mismo tiempo.
El comando edit abre un formulario estilo dBASE para operaciones CRUD completas:
;; Abrir el formulario CRUD (edit recipes)
El formulario muestra un registro a la vez. Puedes:
Tanto browse como edit admiten :where, :params y :order:
;; Navegar solo las recetas vegetarianas (browse recipes :where "vegetarian = ?" :params (list true)) ;; Editar ordenado por cocina (edit recipes :order "cuisine") ;; Comparar con la salida de tabla de texto (query recipes :select (list "name" "cuisine" "servings"))
Por defecto, los datos se almacenan en memoria y se pierden al cerrar la aplicación. Para persistir tus recetas:
;; Crear un archivo de base de datos en tu carpeta :db new recipes ;; Ahora todas las llamadas a deftable/insert/update se persisten ;; Comprobar la base de datos actual :db ;; Volver a memoria :db memory
recipes.lisp y cárgalo con :load recipes.lisp. Si tu carpeta tiene un archivo eelisp.db, el REPL se conecta a él automáticamente cuando abres la carpeta.
El comando defform crea formularios interactivos independientes con campos computados. Los campos computados se actualizan automáticamente cuando cambias los valores de entrada — no se guardan en ninguna base de datos.
Vamos a crear una calculadora que computa el interés compuesto a partir de tres entradas:
(defform compound-interest (principal:number rate:number years:number) :computed ((total-amount (* principal (pow (+ 1 (/ rate 100)) years))) (interest (- total-amount principal))))
Esto crea un formulario con tres campos editables (principal, rate, years) y dos campos computados (total-amount, interest). Prueba a cambiar los valores — los campos computados se actualizan al instante.
La sintaxis es:
(defform form-name (field1:type field2:type ...) :computed ((computed1 expression1) (computed2 expression2)))
deftable: number, string, bool, date, memoConversor de temperatura:
(defform temperature (celsius:number) :computed ((fahrenheit (+ (* celsius 1.8) 32)) (kelvin (+ celsius 273.15))))
Calculadora de IMC:
(defform bmi (weight-kg:number height-cm:number) :computed ((height-m (/ height-cm 100)) (bmi (/ weight-kg (* height-m height-m)))))
:load compound-calculator.lisp para verlo en acción.
EELisp incluye un gestor de información personal inspirado en Lotus Agenda construido sobre la capa de base de datos. Piensa en él como una lista de tareas programable combinada con un organizador de conocimiento — escribes texto libre y la estructura emerge de las categorías.
Los elementos son texto ante todo. Simplemente escribe lo que tengas en mente:
;; Elemento simple (add-item "Finish quarterly report for Sarah") ;; Con fecha de vencimiento y prioridad (add-item "Call dentist to reschedule" :when "2026-03-01" :priority 2) ;; Con una categoría y notas (add-item "Buy groceries" :category "personal" :notes "milk, eggs, bread, coffee") ;; Elemento de trabajo de alta prioridad (add-item "Deploy v2.1 to staging" :when "2026-02-26" :priority 1) ;; Una reunión(add-item "Team standup" :when "2026-02-24" :priority 3)
El comando items muestra todos los elementos en una vista de tabla:
;; Ver todos los elementos (items)
Las categorías son jerárquicas — usa separadores /. También puedes definir grupos exclusivos donde un elemento solo puede pertenecer a un hijo:
;; Categorías de trabajo (defcategory work) (defcategory work/projects) (defcategory work/meetings) (defcategory work/admin) ;; Categorías personales (defcategory personal) (defcategory personal/health) (defcategory personal/errands) ;; Grupo exclusivo: el elemento solo puede ser high, medium O low (defcategory priority :exclusive true :children (high medium low)) ;; Ver el árbol (categories)
Usa assign para etiquetar elementos. Las categorías exclusivas eliminan automáticamente los hermanos:
;; Etiquetar elementos con categorías (assign 1 "work/projects") (assign 2 "personal/health") (assign 4 "work/projects") (assign 5 "work/meetings") ;; Establecer prioridades (assign 1 "priority/high") (assign 4 "priority/high") (assign 2 "priority/medium") ;; Comprobar un elemento (item-get 1)
;; Encontrar todos los elementos de trabajo (items :category "work") ;; Buscar por texto (items :search "quarterly") ;; Solo elementos con prioridad 1 (items :priority 1) ;; Elementos con fecha anterior a marzo (items :when-before "2026-03-01") ;; ¿Cuántos elementos tengo? (item-count)
;; Actualizar propiedades (item-set 1 :priority 1 :when "2026-02-28") ;; Cambiar prioridad — ¡exclusiva! Elimina "high" automáticamente (assign 1 "priority/low") (item-get 1) ; ahora muestra priority/low, no priority/high ;; Marcar elemento como hecho (eliminación suave) (item-done 3) ; "Buy groceries" — ¡hecho! (item-count) ; → 4 (eran 5) ;; Eliminar una categoría (unassign 2 "personal/health")
_items, _categories, _rules), creadas automáticamente. Esto significa que tu agenda persiste si usas un archivo de base de datos (:db myagenda.db). Las categorías son arrays JSON dentro de cada registro de elemento, y las propiedades (when, priority, etc.) se almacenan como un objeto JSON.
Define reglas que categorizan automáticamente los elementos según su contenido. Esto es lo que hizo revolucionario a Lotus Agenda en 1988 — y ahora está en EELisp.
;; Regla: los elementos que contienen "URGENT" obtienen prioridad alta (defrule urgent-flag :when (str-contains text "URGENT") :assign "priority/high") ;; Regla: reuniones y llamadas → work/meetings (defrule meeting-detect :when (or (str-contains text "meeting") (str-contains text "call with") (str-contains text "standup")) :assign "work/meetings") ;; Regla: elementos de compras → personal/errands (defrule errand-detect :when (or (str-contains text "buy ") (str-contains text "groceries")) :assign "personal/errands")
;; Aplicar reglas a todos los elementos (devuelve el número de cambios) (apply-rules) ;; → 3 (elementos que coincidieron con las reglas) ;; Aplicar a un solo elemento (apply-rules 2) ;; Comprobar los resultados (item-get 2) ;; → ahora muestra Categories: priority/high (si contenía "URGENT")
;; Activar auto-aplicación — las reglas se ejecutan en cada add-item (auto-categorize true) ;; ¡Ahora los nuevos elementos se categorizan automáticamente! (add-item "URGENT: Deploy hotfix to production") ;; → asignado automáticamente a priority/high (add-item "Buy milk and eggs this weekend") ;; → asignado automáticamente a personal/errands (add-item "Standup with the team at 9am") ;; → asignado automáticamente a work/meetings
;; Extraer fechas ISO del texto del elemento usando :action (defrule date-extract :when (str-matches text "\\b(\\d{4}-\\d{2}-\\d{2})\\b") :action (item-set id :when (match 1))) ;; Los elementos con fechas obtienen :when automáticamente (add-item "Deadline is 2026-04-01") (item-get 8) ;; → When: 2026-04-01
str-matches devuelve grupos de captura, y (match 1) extrae el primer grupo.;; Listar todas las reglas (rules) ;; → urgent-flag: (str-contains text "URGENT") ;; meeting-detect: (or (str-contains text "meeting") ...) ;; errand-detect: (or (str-contains text "buy ") ...) ;; date-extract: (str-matches text "\\b(\\d{4}-\\d{2}-\\d{2})\\b") ;; Eliminar una regla que ya no necesitas (drop-rule "errand-detect") ;; Desactivar la auto-categorización (auto-categorize false)
_rules. Al evaluarse, los campos de cada elemento (text, notes, id, categories y todas las propiedades) se vinculan como símbolos en el entorno de evaluación de la regla. El atajo :assign genera acciones (assign id "category"), mientras que :action te permite ejecutar cualquier expresión Lisp.
and/or/not, usa has-category para comprobar asignaciones existentes, (get :property) para pruebas de propiedades, y str-matches para patrones regex. Puedes construir lógica de categorización arbitrariamente compleja.
Las vistas son consultas guardadas que filtran, ordenan y agrupan tus elementos. Define una vista una vez y usa show en cualquier momento para ver los últimos elementos que coincidan — como un panel de control en vivo para tu agenda.
Las vistas usan expresiones de filtro — el mismo contexto que las reglas (text, categories, propiedades, has-category, overdue?):
;; Un tablero de todos los elementos de trabajo, agrupados por categoría (defview work-board :source items :filter (has-category "work") :group-by category :sort-by when) ;; Solo elementos de alta prioridad (defview urgent :source items :filter (= priority "1") :sort-by when) ;; Elementos sin categorías (bandeja de entrada sin categorizar) (defview inbox :source items :filter (= (length categories) 0)) ;; Elementos vencidos (defview overdue :source items :filter (overdue?) :sort-by when)
Usa show para mostrar los resultados de una vista. Las vistas agrupadas se representan como un árbol con encabezados de categoría:
;; Mostrar el tablero de trabajo (show work-board)
;; Mostrar elementos urgentes (show urgent)
EELisp proporciona helpers de fecha que son perfectos para filtros de vistas:
;; La fecha de hoy como cadena ISO (today) ; → "2026-02-21" ;; Sumar días, semanas o meses (date-add (today) 7 :days) ; → "2026-02-28" (date-add (today) 1 :months) ; → "2026-03-21" ;; Diferencia en días (date-diff "2026-02-21" "2026-03-01") ; → 8
;; Listar todas las vistas definidas (views) ;; → work-board: filter=(has-category "work") group-by=category sort-by=when ;; urgent: filter=(= priority "1") sort-by=when ;; inbox: filter=(= (length categories) 0) ;; overdue: filter=(overdue?) sort-by=when ;; Eliminar una vista (drop-view "inbox")
_views con sus expresiones de filtro serializadas como Lisp. Cuando usas show en una vista, obtiene todos los elementos, evalúa el filtro en cada uno (vinculando todos los campos del elemento como símbolos), ordena las coincidencias y opcionalmente las agrupa. Los elementos con propiedades faltantes se excluyen silenciosamente — sin errores.
overdue? en filtros para encontrar elementos pasados de su fecha :when. Combina con and/or para vistas complejas: :filter (and (has-category "work") (overdue?)) muestra los elementos de trabajo vencidos.
Conecta tu agenda con fechas del calendario. Visualiza lo que está programado para un día específico, navega por un rango de fechas y añade elementos rápidamente para hoy — todo desde el REPL.
Usa items-on para ver todo lo programado para una fecha específica:
;; ¿Qué hay para el lunes? (items-on "2026-02-24") ;; Usa (today) para fechas dinámicas (items-on (today))
Usa items-between para ver los elementos en un rango de fechas (inclusivo en ambos extremos):
;; ¿Qué hay esta semana? (items-between "2026-02-24" "2026-02-28") ;; Próximos 7 días usando helpers de fecha (items-between (today) (date-add (today) 7 :days)) ;; Todo el mes (items-between "2026-02-01" "2026-02-28")
add-item-today crea un elemento con :when establecido automáticamente a la fecha de hoy:
;; Añadir rápidamente para hoy (add-item-today "Review pull requests" :priority 1) (add-item-today "Quick grocery run" :notes "milk, bread") (add-item-today "Team sync" :category "work/meetings")
Los helpers de fecha y los comandos de calendario funcionan muy bien junto con las vistas:
;; La fecha de hoy como cadena ISO (today) ; → "2026-02-21" ;; Aritmética de fechas (date-add (today) 7 :days) ; → "2026-02-28" (date-add (today) 1 :months) ; → "2026-03-21" (date-diff "2026-02-21" "2026-03-01") ; → 8 (días) ;; Definir una vista para los elementos de esta semana (defview this-week :source items :filter (and (>= when (today)) (<= when (date-add (today) 7 :days))) :sort-by when)
add-item-today es un atajo para (add-item text :when (today)). Admite las mismas opciones :priority, :category y :notes que add-item. Combínalo con las reglas de auto-categorización para una organización sin esfuerzo.
(items-on "date") — mostrar elementos para una fecha específica(items-between "start" "end") — mostrar elementos en un rango de fechas(add-item-today text ...) — añadir elemento con la fecha de hoy(today) — fecha actual como cadena ISO(date-add date n :days/:weeks/:months) — aritmética de fechas(date-diff date1 date2) — días entre dos fechasEl comando add utiliza análisis de lenguaje natural basado en regex para extraer automáticamente fechas, prioridades y personas de tu texto. Combinado con las funciones integradas de HTTP y JSON, EELisp se convierte en una potente herramienta de automatización.
Simplemente escribe lo que tengas en mente — add descifra los metadatos:
;; Las fechas se extraen automáticamente (add "Meet Alice tomorrow for coffee") ;; → :when = fecha de mañana (add "Call Bob next Monday about the project") ;; → :when = fecha del próximo lunes, :who = Bob (add "Review docs end of month") ;; → :when = último día del mes actual (add "Deploy hotfix in 3 days") ;; → :when = dentro de 3 días
Expresa urgencia con palabras o signos de exclamación:
;; Prioridad basada en palabras (add "URGENT fix server crash") ;; → :priority 1 ;; Signos de exclamación: !!! = 1, !! = 2, ! = 3 (add "Review PR !!") ;; → :priority 2 (add "Update README low priority") ;; → :priority 4
Las personas se detectan a partir de @menciones, preposiciones y verbos de acción:
;; @menciones (add "Review code for @carlos") ;; → :who = carlos ;; Patrones Verbo + Nombre (add "Email Sarah March 15 about renewal") ;; → :who = Sarah, :when = March 15 ;; Patrones Preposición + Nombre (add "Meeting with John this weekend") ;; → :who = John, :when = Saturday
Usa smart-parse para ver qué se extraería sin crear un elemento:
(smart-parse "Meet Alice tomorrow for coffee !!")
EELisp incluye funciones genéricas de HTTP y JSON para integración con APIs:
;; Parsear JSON a datos EELisp (json-parse "{\"name\": \"Alice\", \"scores\": [95, 87]}") ;; → {:name "Alice" :scores (95 87)} ;; Convertir EELisp a JSON (json-stringify {:name "Bob" :active true}) ;; → "{\"active\":true,\"name\":\"Bob\"}" ;; Peticiones HTTP (http-get "https://api.example.com/data") ;; → {:status 200 :body "..."} (http-post "https://api.example.com/items" (json-stringify {:title "New item"}) :content-type "application/json") ;; → {:status 201 :body "..."}
2026-03-15), tomorrow/today/yesterday, next Monday, this weekend, in 3 days, in 2 weeks, end of week, end of month, March 15.
add funciona perfectamente con las reglas de auto-categorización. Después de que smart-parsing extrae los metadatos, las reglas siguen ejecutándose — así que (add "URGENT meeting with Bob tomorrow") puede simultáneamente establecer la fecha, la prioridad y activar tu regla "meeting-detect" para asignar work/meetings.
Elementos que se repiten según un calendario y plantillas reutilizables para tareas comunes.
Añade :recur a cualquier llamada add-item. Patrones integrados: :daily, :weekly, :monthly.
(add-item "Team standup" :when "2026-02-24" :recur :weekly :category "work") ;; Item #1: Team standup ;; When: 2026-02-24 ;; Recurrence: weekly ;; Categories: work
Usa (every N :unit) para patrones de recurrencia personalizados:
(add-item "Quarterly review" :when "2026-04-01" :recur (every 3 :months)) (add-item "Biweekly report" :when "2026-02-28" :recur (every 2 :weeks))
Cuando marcas un elemento recurrente como hecho, el sistema crea automáticamente la siguiente ocurrencia:
(item-done 1) ;; Item #4: Team standup ;; When: 2026-03-03 ← avanzado 7 días ;; Recurrence: weekly ;; Categories: work
Las plantillas son planos reutilizables para elementos que creas con frecuencia:
(deftemplate weekly-review :text "Weekly review: reflect on goals" :category "work/admin" :priority 2 :notes "1. What went well?\n2. What to improve?") ;; "Template 'weekly-review' defined"
Usa from-template con sobrecargas opcionales:
(from-template weekly-review :when "2026-03-07") ;; Item #5: Weekly review: reflect on goals ;; When: 2026-03-07 ;; Priority: 2 ;; Categories: work/admin ;; Sobrescribir cualquier campo (from-template weekly-review :when "2026-03-14" :priority 1)
Listar y eliminar plantillas:
(templates) ;; weekly-review — "Weekly review: reflect on goals" [work/admin] !2 (drop-template weekly-review) ;; "Template 'weekly-review' removed"
:recur — p. ej. (deftemplate standup :text "Daily standup" :recur :daily). Cada elemento creado a partir de ella será automáticamente recurrente.Gestiona agendas separadas para trabajo, personal y proyectos. Cada agenda es una base de datos SQLite independiente con sus propios elementos, categorías, reglas, vistas y plantillas.
Usa open-agenda para crear un nuevo archivo de base de datos y cambiar a él:
;; Tu agenda predeterminada ya está activa (add-item "Personal task" :category "personal") ;; Abrir una agenda de trabajo — crea el archivo si no existe (open-agenda "work.db") ;; "Opened agenda 'work' at /path/to/work.db" (add-item "Deploy to staging" :when "2026-03-01" :priority 1)
Usa use-agenda para cambiar. Los elementos están completamente aislados entre agendas:
;; Volver a la predeterminada (use-agenda memory) (items) ;; → solo "Personal task" ;; Cambiar a trabajo (use-agenda work) (items) ;; → solo "Deploy to staging"
;; Ver todas las agendas abiertas (agendas) ;; Agendas: ;; memory — :memory: ;; work [active] — /path/to/work.db ;; Cerrar una agenda que ya no necesitas (close-agenda work)
Haz una copia de seguridad de tu agenda a JSON, o transfiere elementos entre agendas:
;; Exportar la agenda actual (export-agenda "memory" :format :json :path "backup.json") ;; "Exported agenda 'memory' to /path/to/backup.json" ;; Importar a una agenda diferente (open-agenda "archive.db") (import-agenda "backup.json") ;; "Imported: 15 items, 4 categories, 3 rules, 2 views, 1 templates"
work.db se convierte en work, :memory: se convierte en memory. Usa nombres de archivo descriptivos para facilitar el cambio.La barra lateral de EEditor tiene tres modos: Archivos, Calendario y Agenda. Los dos últimos se integran directamente con tu base de datos de agenda EELisp, dándote una visión general visual sin necesidad de escribir consultas.
Haz clic en el icono del portapapeles en el control segmentado de la barra lateral para cambiar al modo Agenda. Consulta automáticamente tu base de datos y organiza los elementos en tres secciones:
:when es anterior a hoy (encabezado rojo)Cada elemento muestra un punto de prioridad (rojo / naranja / amarillo / gris), el texto del elemento, las categorías y una fecha formateada. Pulsa el botón de actualizar para recargar después de añadir elementos en el REPL.
La barra lateral de agenda no duplica la lógica de la base de datos. En su lugar, el AgendaViewModel llama a interpreter.eval() con las mismas funciones integradas de EELisp que usarías en el REPL:
;; Lo que la barra lateral de Agenda ejecuta internamente: (items :when-before "2026-02-23") ;; → Sección Vencidos (items-on "2026-02-23") ;; → Sección Hoy (items-between "2026-02-24" "2026-03-02") ;; → Sección Próximos
La barra lateral de Calendario también se integra con tu agenda. Cada celda de día muestra indicadores de doble punto:
Cuando haces clic en un día, el panel de detalle muestra tanto la sección de Archivos como la de Agenda, para que puedas ver tu actividad de escritura junto con tus tareas programadas.
;; 1. Añade algunos elementos con fechas (add-item "Review PR" :when (today) :priority 1) (add-item "Team meeting" :when (date-add (today) 1 :days) :category "work/meetings") (add-item "Deploy v2" :when (date-add (today) 3 :days) :priority 2) (add-item "Overdue task" :when "2026-01-15") ;; 2. Cambia a la pestaña de Agenda en la barra lateral ;; → Verás: Vencidos (1) | Hoy (1) | Próximos 7 Días (2) ;; 3. Cambia a la pestaña de Calendario en la barra lateral ;; → Hoy tiene un punto naranja; haz clic para ver el elemento
Usa la macro pipe para encadenar operaciones de izquierda a derecha en lugar de anidarlas:
;; En lugar de esta expresión profundamente anidada: (str-join (map str-upper (filter (fn (s) (str-starts-with s "a")) (str-split "apple,avocado,banana,apricot" ","))) ", ") ;; Escribe esto: (pipe "apple,avocado,banana,apricot" (str-split ",") (filter (fn (s) (str-starts-with s "a"))) (map str-upper) (str-join ", ")) ; → "APPLE, AVOCADO, APRICOT"
Escribe tus funciones en un archivo y cárgalas en el REPL:
;; En el REPL, carga un archivo de script: :load ~/scripts/finance.el ;; O haz clic derecho en un archivo .el en la barra lateral y ;; selecciona "Run in REPL"
| Comando | Descripción |
|---|---|
:help | Mostrar todos los comandos del REPL |
:load <file> | Cargar y ejecutar un script |
:db | Mostrar la ruta de la base de datos actual |
:db <path> | Cambiar a un archivo de base de datos |
:db new <name> | Crear un nuevo archivo de base de datos |
:db memory | Cambiar a base de datos en memoria |
:env | Listar todos los símbolos definidos |
:clear | Limpiar la salida |
:reset | Reiniciar el intérprete |
(tables) | Listar todas las tablas en la base de datos actual |
(describe tablename) | Mostrar el esquema de una tabla |
(drop-table tablename) | Eliminar una tabla |
| Patrón | Ejemplo |
|---|---|
| Transformar una lista | (map inc numbers) |
| Filtrar una lista | (filter even? numbers) |
| Agregar una lista | (reduce + 0 numbers) |
| Consultar + procesar | (map (fn (r) (field-get r :name)) (records (query ...))) |
| Condicional | (if (> x 0) "positive" "non-positive") |
| Ámbito local | (let ((x 1) (y 2)) (+ x y)) |
| Bucle | (loop ((i 0)) (= i 10) (recur (inc i))) |
| Matemáticas | (pow 2 10), (round 3.14159 2), (abs -5) |
| Vista de tabla | (browse tasks) |
| Formulario CRUD | (edit tasks) |
| Formulario calculadora | (defform name (fields) :computed ((name expr))) |
| Añadir elemento de agenda | (add-item "text" :when "date" :priority n) |
| Explorar elementos | (items :category "work" :search "text") |
| Definir categoría | (defcategory work/projects) |
| Asignar categoría | (assign 1 "work/projects") |
| Completar elemento | (item-done 1) |
| Definir regla | (defrule name :when (str-contains text "URGENT") :assign "priority/high") |
| Auto-categorizar | (auto-categorize true) |
| Aplicar reglas | (apply-rules) |
| Regla regex | (defrule name :when (str-matches text "pattern") :action expr) |
| Definir vista | (defview name :source items :filter (has-category "work") :group-by category) |
| Mostrar vista | (show work-board) |
| Fecha de hoy | (today), (date-add (today) 7 :days) |
| Elementos para una fecha | (items-on "2026-02-24") |
| Elementos en rango | (items-between (today) (date-add (today) 7 :days)) |
| Añadir rápido hoy | (add-item-today "text" :priority 1) |
| Añadir inteligente (NLP) | (add "Meet Alice tomorrow !!") |
| Vista previa de análisis | (smart-parse "Call Bob next Monday") |
| Parsear JSON | (json-parse "{\"a\": 1}") |
| Convertir a JSON | (json-stringify {:name "Alice"}) |
| HTTP GET | (http-get "https://api.example.com/data") |
| Elemento recurrente | (add-item "Task" :when "2026-03-01" :recur :weekly) |
| Intervalo personalizado | (add-item "Review" :when "2026-04-01" :recur (every 3 :months)) |
| Definir plantilla | (deftemplate standup :text "Standup" :recur :daily) |
| Usar plantilla | (from-template standup :when "2026-03-01") |
| Abrir agenda | (open-agenda "work.db") |
| Cambiar agenda | (use-agenda work) |
| Listar agendas | (agendas) |
| Exportar agenda | (export-agenda "work" :format :json :path "backup.json") |
| Importar agenda | (import-agenda "backup.json") |