packages feed

hydra-0.15.0: src/main/haskell/Hydra/Sources/Lisp/Syntax.hs

-- | A unified Lisp syntax model covering features common to Clojure, Emacs Lisp, Common Lisp,
-- | and Scheme (R7RS). The model captures the full S-expression structure and special forms
-- | needed for code generation across dialects. Dialect-specific serialization is left to
-- | per-dialect serializers.

module Hydra.Sources.Lisp.Syntax where

-- Standard imports for type-level sources outside of the kernel
import           Hydra.Kernel
import           Hydra.Dsl.Annotations
import           Hydra.Dsl.Bootstrap
import           Hydra.Dsl.Types                 ((>:))
import qualified Hydra.Dsl.Types                 as T
import qualified Hydra.Sources.Kernel.Types.Core as Core
import qualified Data.List                       as L
import qualified Data.Map                        as M
import qualified Data.Set                        as S
import qualified Data.Maybe                      as Y


ns :: Namespace
ns = Namespace "hydra.lisp.syntax"

define :: String -> Type -> Binding
define = datatype ns

lisp :: String -> Type
lisp = typeref ns

module_ :: Module
module_ = Module {
            moduleNamespace = ns,
            moduleDefinitions = (map toTypeDef definitions),
            moduleTermDependencies = [Core.ns],
            moduleTypeDependencies = [Core.ns],
            moduleDescription = Just ("A unified Lisp syntax model covering Clojure, Emacs Lisp, Common Lisp, and Scheme (R7RS)."
      ++ " Designed for code generation from Hydra types and terms.")}
  where
    definitions = toplevel ++ defnTypes ++ expressions ++ specialForms ++ bindings
      ++ patterns ++ literals ++ names ++ collections ++ types ++ modules ++ misc

    -- Top-level constructs
    toplevel = [
      program,
      topLevelForm,
      topLevelFormWithComments]

    -- Definitions
    defnTypes = [
      functionDefinition,
      variableDefinition,
      constantDefinition,
      recordTypeDefinition,
      fieldDefinition,
      macroDefinition]

    -- Expressions
    expressions = [
      expression,
      application,
      lambda,
      variableReference,
      fieldAccess,
      typeAnnotation]

    -- Special forms
    specialForms = [
      ifExpression,
      condExpression,
      condClause,
      caseExpression,
      caseClause,
      andExpression,
      orExpression,
      notExpression,
      doExpression,
      beginExpression,
      quoteExpression,
      quasiquoteExpression,
      unquoteExpression,
      splicingUnquoteExpression]

    -- Let bindings
    bindings = [
      letExpression,
      letKind,
      letBinding,
      simpleBinding,
      destructuringBinding,
      destructuringPattern]

    -- Patterns (for case/match)
    patterns = [
      pattern,
      constructorPattern,
      literalPattern,
      wildcardPattern]

    -- Literals
    literals = [
      literal,
      integerLiteral,
      floatLiteral,
      characterLiteral,
      booleanStyle,
      nilStyle]

    -- Names and symbols
    names = [
      symbol,
      keyword,
      qualifiedSymbol,
      namespaceName]

    -- Collection literals
    collections = [
      listLiteral,
      vectorLiteral,
      mapLiteral,
      mapEntry,
      setLiteral,
      consExpression,
      dottedPair]

    -- Type-related (for dialects with type hints)
    types = [
      typeHint,
      typeSpecifier]

    -- Module system
    modules = [
      moduleDeclaration,
      importDeclaration,
      importSpec,
      exportDeclaration]

    -- Miscellaneous
    misc = [
      comment,
      commentStyle,
      docstring,
      dialect,
      sExpression]


-- ================================================================================================
-- Top-level constructs
-- ================================================================================================

program :: Binding
program = define "Program" $
  doc "A Lisp program, consisting of a sequence of top-level forms" $
  T.record [
    "dialect">:
      doc "The target Lisp dialect" $
      lisp "Dialect",
    "module">:
      doc "Optional module/namespace declaration" $
      T.maybe (lisp "ModuleDeclaration"),
    "imports">:
      doc "Import/require declarations" $
      T.list (lisp "ImportDeclaration"),
    "exports">:
      doc "Export/provide declarations" $
      T.list (lisp "ExportDeclaration"),
    "forms">:
      doc "The top-level forms in the program" $
      T.list (lisp "TopLevelFormWithComments")]

topLevelForm :: Binding
topLevelForm = define "TopLevelForm" $
  doc "A top-level form in a Lisp program" $
  T.union [
    "function">:
      doc "A named function definition" $
      lisp "FunctionDefinition",
    "variable">:
      doc "A global variable definition" $
      lisp "VariableDefinition",
    "constant">:
      doc "A constant definition" $
      lisp "ConstantDefinition",
    "recordType">:
      doc "A record/struct type definition" $
      lisp "RecordTypeDefinition",
    "macro">:
      doc "A macro definition" $
      lisp "MacroDefinition",
    "expression">:
      doc "A bare expression at the top level" $
      lisp "Expression"]

topLevelFormWithComments :: Binding
topLevelFormWithComments = define "TopLevelFormWithComments" $
  doc "A top-level form together with optional documentation" $
  T.record [
    "doc">:
      doc "Optional documentation string" $
      T.maybe (lisp "Docstring"),
    "comment">:
      doc "Optional comment" $
      T.maybe (lisp "Comment"),
    "form">:
      doc "The form itself" $
      lisp "TopLevelForm"]


-- ================================================================================================
-- Definitions
-- ================================================================================================

functionDefinition :: Binding
functionDefinition = define "FunctionDefinition" $
  doc ("A named function definition."
    ++ " Serializes as (defn name [params] body) in Clojure,"
    ++ " (defun name (params) body) in Emacs Lisp and Common Lisp,"
    ++ " (define (name params) body) in Scheme") $
  T.record [
    "name">:
      doc "The function name" $
      lisp "Symbol",
    "params">:
      doc "The parameter list" $
      T.list (lisp "Symbol"),
    "restParam">:
      doc "Optional rest/variadic parameter" $
      T.maybe (lisp "Symbol"),
    "doc">:
      doc "Optional docstring" $
      T.maybe (lisp "Docstring"),
    "typeHints">:
      doc "Optional type hints for parameters and return type" $
      T.list (lisp "TypeHint"),
    "body">:
      doc "The function body (one or more expressions)" $
      T.list (lisp "Expression")]

variableDefinition :: Binding
variableDefinition = define "VariableDefinition" $
  doc ("A global variable definition."
    ++ " Serializes as (def name value) in Clojure,"
    ++ " (defvar name value) in Emacs Lisp and Common Lisp,"
    ++ " (define name value) in Scheme") $
  T.record [
    "name">:
      doc "The variable name" $
      lisp "Symbol",
    "value">:
      doc "The initial value" $
      lisp "Expression",
    "doc">:
      doc "Optional docstring" $
      T.maybe (lisp "Docstring")]

constantDefinition :: Binding
constantDefinition = define "ConstantDefinition" $
  doc ("A constant definition."
    ++ " Serializes as (def ^:const name value) in Clojure,"
    ++ " (defconst name value) in Emacs Lisp,"
    ++ " (defconstant name value) in Common Lisp."
    ++ " Scheme has no dedicated constant form; uses define.") $
  T.record [
    "name">:
      doc "The constant name" $
      lisp "Symbol",
    "value">:
      doc "The constant value" $
      lisp "Expression",
    "doc">:
      doc "Optional docstring" $
      T.maybe (lisp "Docstring")]

recordTypeDefinition :: Binding
recordTypeDefinition = define "RecordTypeDefinition" $
  doc ("A record/struct type definition."
    ++ " Serializes as (defrecord Name [fields]) in Clojure,"
    ++ " (cl-defstruct name fields) in Emacs Lisp,"
    ++ " (defstruct name fields) in Common Lisp,"
    ++ " (define-record-type <Name> ...) in Scheme") $
  T.record [
    "name">:
      doc "The record type name" $
      lisp "Symbol",
    "fields">:
      doc "The field definitions" $
      T.list (lisp "FieldDefinition"),
    "doc">:
      doc "Optional docstring" $
      T.maybe (lisp "Docstring")]

fieldDefinition :: Binding
fieldDefinition = define "FieldDefinition" $
  doc "A field in a record type definition" $
  T.record [
    "name">:
      doc "The field name" $
      lisp "Symbol",
    "defaultValue">:
      doc "Optional default value" $
      T.maybe (lisp "Expression")]

macroDefinition :: Binding
macroDefinition = define "MacroDefinition" $
  doc ("A macro definition."
    ++ " Serializes as (defmacro name [params] body) in Clojure,"
    ++ " (defmacro name (params) body) in Emacs Lisp and Common Lisp,"
    ++ " (define-syntax name ...) in Scheme") $
  T.record [
    "name">:
      doc "The macro name" $
      lisp "Symbol",
    "params">:
      doc "The parameter list" $
      T.list (lisp "Symbol"),
    "restParam">:
      doc "Optional rest parameter" $
      T.maybe (lisp "Symbol"),
    "body">:
      doc "The macro body" $
      T.list (lisp "Expression")]


-- ================================================================================================
-- Expressions
-- ================================================================================================

expression :: Binding
expression = define "Expression" $
  doc "A Lisp expression" $
  T.union [
    "application">:
      doc "Function application: (f arg1 arg2 ...)" $
      lisp "Application",
    "lambda">:
      doc "Anonymous function" $
      lisp "Lambda",
    "let">:
      doc "Local variable binding" $
      lisp "LetExpression",
    "if">:
      doc "Conditional expression" $
      lisp "IfExpression",
    "cond">:
      doc "Multi-branch conditional" $
      lisp "CondExpression",
    "case">:
      doc "Case/match dispatch" $
      lisp "CaseExpression",
    "and">:
      doc "Logical and (short-circuiting)" $
      lisp "AndExpression",
    "or">:
      doc "Logical or (short-circuiting)" $
      lisp "OrExpression",
    "not">:
      doc "Logical negation" $
      lisp "NotExpression",
    "do">:
      doc "Sequential evaluation (progn/do/begin)" $
      lisp "DoExpression",
    "begin">:
      doc "Sequential evaluation (explicit begin block)" $
      lisp "BeginExpression",
    "variable">:
      doc "Variable reference" $
      lisp "VariableReference",
    "literal">:
      doc "A literal value" $
      lisp "Literal",
    "list">:
      doc "A list literal" $
      lisp "ListLiteral",
    "vector">:
      doc "A vector literal" $
      lisp "VectorLiteral",
    "map">:
      doc "A map/association literal" $
      lisp "MapLiteral",
    "set">:
      doc "A set literal" $
      lisp "SetLiteral",
    "cons">:
      doc "A cons expression" $
      lisp "ConsExpression",
    "dottedPair">:
      doc "A dotted pair literal" $
      lisp "DottedPair",
    "fieldAccess">:
      doc "Field access on a record/struct" $
      lisp "FieldAccess",
    "typeAnnotation">:
      doc "A type-annotated expression" $
      lisp "TypeAnnotation",
    "quote">:
      doc "A quoted expression" $
      lisp "QuoteExpression",
    "quasiquote">:
      doc "A quasiquoted expression" $
      lisp "QuasiquoteExpression",
    "unquote">:
      doc "An unquoted expression within a quasiquote" $
      lisp "UnquoteExpression",
    "splicingUnquote">:
      doc "A splicing unquote within a quasiquote" $
      lisp "SplicingUnquoteExpression",
    "sExpression">:
      doc "An arbitrary S-expression (escape hatch for dialect-specific forms)" $
      lisp "SExpression"]

application :: Binding
application = define "Application" $
  doc "Function application: (function arg1 arg2 ...)" $
  T.record [
    "function">:
      doc "The function being applied" $
      lisp "Expression",
    "arguments">:
      doc "The arguments" $
      T.list (lisp "Expression")]

lambda :: Binding
lambda = define "Lambda" $
  doc ("An anonymous function."
    ++ " Serializes as (fn [params] body) in Clojure,"
    ++ " (lambda (params) body) in Emacs Lisp, Common Lisp, and Scheme."
    ++ " If name is provided, emits (fn name [params] body) in Clojure for self-reference.") $
  T.record [
    "name">:
      doc "Optional name for self-referential lambdas (Clojure named fn)" $
      T.maybe (lisp "Symbol"),
    "params">:
      doc "The parameter list" $
      T.list (lisp "Symbol"),
    "restParam">:
      doc "Optional rest parameter" $
      T.maybe (lisp "Symbol"),
    "body">:
      doc "The lambda body" $
      T.list (lisp "Expression")]

variableReference :: Binding
variableReference = define "VariableReference" $
  doc "A reference to a variable by name" $
  T.record [
    "name">:
      doc "The variable name" $
      lisp "Symbol",
    "functionNamespace">:
      doc ("Whether to reference from the function namespace."
        ++ " In Lisp-2 dialects (Common Lisp), this emits #'name."
        ++ " In Lisp-1 dialects, this has no effect.") $
      T.boolean]

fieldAccess :: Binding
fieldAccess = define "FieldAccess" $
  doc ("Field access on a record/struct."
    ++ " Serializes as (:field record) in Clojure,"
    ++ " (struct-field record) in Emacs Lisp and Common Lisp,"
    ++ " (record-field record) in Scheme") $
  T.record [
    "recordType">:
      doc "The record type name (used to form accessor name)" $
      lisp "Symbol",
    "field">:
      doc "The field name" $
      lisp "Symbol",
    "target">:
      doc "The expression being accessed" $
      lisp "Expression"]

typeAnnotation :: Binding
typeAnnotation = define "TypeAnnotation" $
  doc "An expression with a type annotation" $
  T.record [
    "expression">:
      doc "The annotated expression" $
      lisp "Expression",
    "type">:
      doc "The type specifier" $
      lisp "TypeSpecifier"]


-- ================================================================================================
-- Special forms
-- ================================================================================================

ifExpression :: Binding
ifExpression = define "IfExpression" $
  doc "Conditional: (if test then else)" $
  T.record [
    "condition">:
      doc "The test expression" $
      lisp "Expression",
    "then">:
      doc "The then branch" $
      lisp "Expression",
    "else">:
      doc "Optional else branch" $
      T.maybe (lisp "Expression")]

condExpression :: Binding
condExpression = define "CondExpression" $
  doc ("Multi-branch conditional."
    ++ " Serializes as (cond test1 expr1 test2 expr2 :else default) in Clojure,"
    ++ " (cond (test1 expr1) (test2 expr2) (t default)) in Emacs Lisp and Common Lisp,"
    ++ " (cond (test1 expr1) (test2 expr2) (else default)) in Scheme") $
  T.record [
    "clauses">:
      doc "The condition-expression pairs" $
      T.list (lisp "CondClause"),
    "default">:
      doc "Optional default expression" $
      T.maybe (lisp "Expression")]

condClause :: Binding
condClause = define "CondClause" $
  doc "A clause in a cond expression" $
  T.record [
    "condition">:
      doc "The test condition" $
      lisp "Expression",
    "body">:
      doc "The result expression" $
      lisp "Expression"]

caseExpression :: Binding
caseExpression = define "CaseExpression" $
  doc ("Case dispatch on a value."
    ++ " Serializes as (case x key1 expr1 key2 expr2 default) in Clojure,"
    ++ " (case x (key1 expr1) (key2 expr2) (otherwise default)) in Common Lisp,"
    ++ " (case x ((key1) expr1) ((key2) expr2) (else default)) in Scheme") $
  T.record [
    "scrutinee">:
      doc "The expression being dispatched on" $
      lisp "Expression",
    "clauses">:
      doc "The case clauses" $
      T.list (lisp "CaseClause"),
    "default">:
      doc "Optional default clause" $
      T.maybe (lisp "Expression")]

caseClause :: Binding
caseClause = define "CaseClause" $
  doc "A clause in a case expression" $
  T.record [
    "keys">:
      doc "The matching keys (one or more datum values)" $
      T.list (lisp "Expression"),
    "body">:
      doc "The result expression" $
      lisp "Expression"]

andExpression :: Binding
andExpression = define "AndExpression" $
  doc "Logical and: (and expr1 expr2 ...)" $
  T.record [
    "expressions">:
      doc "The operand expressions" $
      T.list (lisp "Expression")]

orExpression :: Binding
orExpression = define "OrExpression" $
  doc "Logical or: (or expr1 expr2 ...)" $
  T.record [
    "expressions">:
      doc "The operand expressions" $
      T.list (lisp "Expression")]

notExpression :: Binding
notExpression = define "NotExpression" $
  doc "Logical negation: (not expr)" $
  T.record [
    "expression">:
      doc "The operand expression" $
      lisp "Expression"]

doExpression :: Binding
doExpression = define "DoExpression" $
  doc ("Sequential evaluation of expressions, returning the last."
    ++ " Serializes as (do expr1 expr2 ...) in Clojure,"
    ++ " (progn expr1 expr2 ...) in Emacs Lisp and Common Lisp,"
    ++ " (begin expr1 expr2 ...) in Scheme") $
  T.record [
    "expressions">:
      doc "The expressions to evaluate in sequence" $
      T.list (lisp "Expression")]

beginExpression :: Binding
beginExpression = define "BeginExpression" $
  doc "An explicit begin block (distinct from do for Scheme compatibility)" $
  T.record [
    "expressions">:
      doc "The expressions to evaluate in sequence" $
      T.list (lisp "Expression")]

quoteExpression :: Binding
quoteExpression = define "QuoteExpression" $
  doc "A quoted form: 'expr or (quote expr)" $
  T.record [
    "body">:
      doc "The quoted form" $
      lisp "Expression"]

quasiquoteExpression :: Binding
quasiquoteExpression = define "QuasiquoteExpression" $
  doc "A quasiquoted form: `expr" $
  T.record [
    "body">:
      doc "The quasiquoted form" $
      lisp "Expression"]

unquoteExpression :: Binding
unquoteExpression = define "UnquoteExpression" $
  doc "An unquoted form within a quasiquote: ~expr or ,expr" $
  T.record [
    "body">:
      doc "The unquoted form" $
      lisp "Expression"]

splicingUnquoteExpression :: Binding
splicingUnquoteExpression = define "SplicingUnquoteExpression" $
  doc "A splicing unquote within a quasiquote: ~@expr or ,@expr" $
  T.record [
    "body">:
      doc "The spliced form" $
      lisp "Expression"]


-- ================================================================================================
-- Let bindings
-- ================================================================================================

letExpression :: Binding
letExpression = define "LetExpression" $
  doc ("Local variable bindings."
    ++ " Serializes as (let [x 1 y 2] body) in Clojure (always sequential),"
    ++ " (let ((x 1) (y 2)) body) or (let* ...) in other dialects") $
  T.record [
    "kind">:
      doc "The kind of let (parallel or sequential)" $
      lisp "LetKind",
    "bindings">:
      doc "The variable bindings" $
      T.list (lisp "LetBinding"),
    "body">:
      doc "The body expressions" $
      T.list (lisp "Expression")]

letKind :: Binding
letKind = define "LetKind" $
  doc "The kind of let binding" $
  T.enum ["parallel", "sequential", "recursive"]

letBinding :: Binding
letBinding = define "LetBinding" $
  doc "A single binding in a let expression" $
  T.union [
    "simple">:
      doc "A simple name-value binding" $
      lisp "SimpleBinding",
    "destructuring">:
      doc "A destructuring binding" $
      lisp "DestructuringBinding"]

simpleBinding :: Binding
simpleBinding = define "SimpleBinding" $
  doc "A simple name-value binding in a let expression" $
  T.record [
    "name">:
      doc "The bound variable" $
      lisp "Symbol",
    "value">:
      doc "The value expression" $
      lisp "Expression"]

destructuringBinding :: Binding
destructuringBinding = define "DestructuringBinding" $
  doc "A destructuring binding in a let expression" $
  T.record [
    "pattern">:
      doc "The destructuring pattern" $
      lisp "DestructuringPattern",
    "value">:
      doc "The value to destructure" $
      lisp "Expression"]

destructuringPattern :: Binding
destructuringPattern = define "DestructuringPattern" $
  doc "A destructuring pattern" $
  T.union [
    "sequential">:
      doc "Sequential destructuring: [a b c] in Clojure, (a b c) in others" $
      T.list (lisp "Symbol"),
    "associative">:
      doc "Associative/map destructuring: {:keys [a b]} in Clojure" $
      T.list (lisp "Symbol"),
    "rest">:
      doc "Destructuring with a rest element: [a b & rest] (leading symbols + rest symbol)" $
      T.list (lisp "Symbol")]


-- ================================================================================================
-- Patterns (for case/match)
-- ================================================================================================

pattern :: Binding
pattern = define "Pattern" $
  doc "A pattern for use in case expressions or match forms" $
  T.union [
    "constructor">:
      doc "A constructor pattern (for union/sum type matching)" $
      lisp "ConstructorPattern",
    "literal">:
      doc "A literal pattern" $
      lisp "LiteralPattern",
    "wildcard">:
      doc "A wildcard pattern matching anything" $
      lisp "WildcardPattern",
    "variable">:
      doc "A variable pattern that binds the matched value" $
      lisp "Symbol"]

constructorPattern :: Binding
constructorPattern = define "ConstructorPattern" $
  doc "A constructor pattern matching a tagged value" $
  T.record [
    "constructor">:
      doc "The constructor/tag name" $
      lisp "Symbol",
    "arguments">:
      doc "The sub-patterns for constructor arguments" $
      T.list (lisp "Pattern")]

literalPattern :: Binding
literalPattern = define "LiteralPattern" $
  doc "A pattern matching a literal value" $
  T.record [
    "value">:
      doc "The literal to match" $
      lisp "Literal"]

wildcardPattern :: Binding
wildcardPattern = define "WildcardPattern" $
  doc "A wildcard pattern that matches any value" $
  T.record []


-- ================================================================================================
-- Literals
-- ================================================================================================

literal :: Binding
literal = define "Literal" $
  doc "A Lisp literal value" $
  T.union [
    "integer">:
      doc "An integer literal" $
      lisp "IntegerLiteral",
    "float">:
      doc "A floating-point literal" $
      lisp "FloatLiteral",
    "string">:
      doc "A string literal" $
      T.string,
    "character">:
      doc "A character literal" $
      lisp "CharacterLiteral",
    "boolean">:
      doc "A boolean literal (dialect-specific rendering)" $
      T.boolean,
    "nil">:
      doc "Nil/null/empty list (dialect-specific rendering)" $
      T.unit,
    "keyword">:
      doc "A keyword literal" $
      lisp "Keyword",
    "symbol">:
      doc "A quoted symbol literal" $
      lisp "Symbol"]

integerLiteral :: Binding
integerLiteral = define "IntegerLiteral" $
  doc "An integer literal" $
  T.record [
    "value">:
      doc "The integer value" $
      T.bigint,
    "bigint">:
      doc "Whether this is explicitly a big integer (e.g. 42N in Clojure)" $
      T.boolean]

floatLiteral :: Binding
floatLiteral = define "FloatLiteral" $
  doc "A floating-point literal" $
  T.record [
    "value">:
      doc "The float value" $
      T.bigfloat,
    "precision">:
      doc "Optional precision hint (e.g. 3.14d0 vs 3.14f0 in Common Lisp)" $
      T.maybe T.string]

characterLiteral :: Binding
characterLiteral = define "CharacterLiteral" $
  doc ("A character literal."
    ++ " Concrete syntax varies: \\a (Clojure), ?a (Emacs Lisp), #\\a (Common Lisp, Scheme)") $
  T.record [
    "value">:
      doc "The character value" $
      T.string]

booleanStyle :: Binding
booleanStyle = define "BooleanStyle" $
  doc "The style of boolean literals in a dialect" $
  T.enum ["trueFalse", "tNil", "hashTF"]

nilStyle :: Binding
nilStyle = define "NilStyle" $
  doc "The style of nil/null in a dialect" $
  T.enum ["nil", "emptyList"]


-- ================================================================================================
-- Names and symbols
-- ================================================================================================

symbol :: Binding
symbol = define "Symbol" $
  doc "A Lisp symbol (identifier)" $
  T.wrap T.string

keyword :: Binding
keyword = define "Keyword" $
  doc "A keyword (self-evaluating symbol). Serializes as :name in Clojure, Emacs Lisp, and Common Lisp" $
  T.record [
    "name">:
      doc "The keyword name (without the leading colon)" $
      T.string,
    "namespace">:
      doc "Optional namespace (e.g. my.ns/foo in Clojure)" $
      T.maybe T.string]

qualifiedSymbol :: Binding
qualifiedSymbol = define "QualifiedSymbol" $
  doc ("A namespace-qualified symbol."
    ++ " Serializes as ns/name in Clojure, pkg:name or pkg::name in Common Lisp") $
  T.record [
    "namespace">:
      doc "The namespace or package" $
      T.string,
    "name">:
      doc "The local name" $
      T.string]

namespaceName :: Binding
namespaceName = define "NamespaceName" $
  doc "A namespace or package name" $
  T.wrap T.string


-- ================================================================================================
-- Collection literals
-- ================================================================================================

listLiteral :: Binding
listLiteral = define "ListLiteral" $
  doc "A list literal: '(1 2 3) or (list 1 2 3)" $
  T.record [
    "elements">:
      doc "The list elements" $
      T.list (lisp "Expression"),
    "quoted">:
      doc "Whether to use quote syntax vs constructor syntax" $
      T.boolean]

vectorLiteral :: Binding
vectorLiteral = define "VectorLiteral" $
  doc ("A vector literal."
    ++ " Serializes as [1 2 3] in Clojure and Emacs Lisp,"
    ++ " #(1 2 3) in Common Lisp and Scheme") $
  T.record [
    "elements">:
      doc "The vector elements" $
      T.list (lisp "Expression")]

mapLiteral :: Binding
mapLiteral = define "MapLiteral" $
  doc ("A map/dictionary literal."
    ++ " Serializes as {:a 1 :b 2} in Clojure,"
    ++ " as an alist '((a . 1) (b . 2)) in other dialects") $
  T.record [
    "entries">:
      doc "The key-value pairs" $
      T.list (lisp "MapEntry")]

mapEntry :: Binding
mapEntry = define "MapEntry" $
  doc "A key-value pair in a map literal" $
  T.record [
    "key">:
      doc "The key expression" $
      lisp "Expression",
    "value">:
      doc "The value expression" $
      lisp "Expression"]

setLiteral :: Binding
setLiteral = define "SetLiteral" $
  doc ("A set literal."
    ++ " Serializes as #{1 2 3} in Clojure."
    ++ " Other dialects use a list-based construction.") $
  T.record [
    "elements">:
      doc "The set elements" $
      T.list (lisp "Expression")]

consExpression :: Binding
consExpression = define "ConsExpression" $
  doc "A cons expression: (cons head tail)" $
  T.record [
    "head">:
      doc "The head element" $
      lisp "Expression",
    "tail">:
      doc "The tail (typically a list or another cons)" $
      lisp "Expression"]

dottedPair :: Binding
dottedPair = define "DottedPair" $
  doc "A dotted pair literal: '(a . b). Not available in Clojure." $
  T.record [
    "car">:
      doc "The first element" $
      lisp "Expression",
    "cdr">:
      doc "The second element" $
      lisp "Expression"]


-- ================================================================================================
-- Type-related
-- ================================================================================================

typeHint :: Binding
typeHint = define "TypeHint" $
  doc ("A type hint or annotation."
    ++ " In Clojure: ^Type name. In Common Lisp: (declare (type Type name))."
    ++ " In Scheme and Emacs Lisp: typically unused.") $
  T.record [
    "name">:
      doc "The annotated symbol" $
      lisp "Symbol",
    "type">:
      doc "The type specifier" $
      lisp "TypeSpecifier"]

typeSpecifier :: Binding
typeSpecifier = define "TypeSpecifier" $
  doc "A type specifier" $
  T.union [
    "named">:
      doc "A named type reference" $
      lisp "Symbol",
    "list">:
      doc "A list type" $
      lisp "TypeSpecifier",
    "function">:
      doc "A function type (params and return)" $
      T.list (lisp "TypeSpecifier"),
    "maybe">:
      doc "An optional type" $
      lisp "TypeSpecifier",
    "map">:
      doc "A map type (key and value type specifiers)" $
      T.list (lisp "TypeSpecifier"),
    "set">:
      doc "A set type" $
      lisp "TypeSpecifier",
    "pair">:
      doc "A pair/tuple type (two type specifiers)" $
      T.list (lisp "TypeSpecifier"),
    "either">:
      doc "An either/union type (two type specifiers)" $
      T.list (lisp "TypeSpecifier"),
    "unit">:
      doc "The unit type" $
      T.unit]


-- ================================================================================================
-- Module system
-- ================================================================================================

moduleDeclaration :: Binding
moduleDeclaration = define "ModuleDeclaration" $
  doc ("A module/namespace declaration."
    ++ " Serializes as (ns name ...) in Clojure,"
    ++ " (provide 'name) in Emacs Lisp,"
    ++ " (defpackage :name ... ) (in-package :name) in Common Lisp,"
    ++ " (define-library (name) ...) in Scheme") $
  T.record [
    "name">:
      doc "The module/namespace name" $
      lisp "NamespaceName",
    "doc">:
      doc "Optional module documentation" $
      T.maybe (lisp "Docstring")]

importDeclaration :: Binding
importDeclaration = define "ImportDeclaration" $
  doc ("An import/require declaration."
    ++ " Serializes as (:require [name ...]) in Clojure,"
    ++ " (require 'name) in Emacs Lisp,"
    ++ " (:use :name) or (:import-from :name ...) in Common Lisp,"
    ++ " (import (name)) in Scheme") $
  T.record [
    "module">:
      doc "The module being imported" $
      lisp "NamespaceName",
    "spec">:
      doc "Import specification" $
      lisp "ImportSpec"]

importSpec :: Binding
importSpec = define "ImportSpec" $
  doc "An import specification describing how to import symbols" $
  T.union [
    "all">:
      doc "Import everything" $
      T.unit,
    "alias">:
      doc "Import with an alias: (:require [name :as alias]) in Clojure" $
      lisp "Symbol",
    "only">:
      doc "Import specific symbols: (:require [name :refer [sym1 sym2]]) in Clojure" $
      T.list (lisp "Symbol"),
    "rename">:
      doc "Import with renaming: list of (from, to) symbol pairs" $
      T.list (T.list (lisp "Symbol"))]

exportDeclaration :: Binding
exportDeclaration = define "ExportDeclaration" $
  doc ("An export/provide declaration."
    ++ " Serializes as (provide 'name) in Emacs Lisp,"
    ++ " (:export :sym1 :sym2) in Common Lisp,"
    ++ " (export sym1 sym2) in Scheme."
    ++ " In Clojure, symbols are public by default.") $
  T.record [
    "symbols">:
      doc "The symbols to export" $
      T.list (lisp "Symbol")]


-- ================================================================================================
-- Miscellaneous
-- ================================================================================================

comment :: Binding
comment = define "Comment" $
  doc "A comment" $
  T.record [
    "style">:
      doc "The comment style" $
      lisp "CommentStyle",
    "text">:
      doc "The comment text" $
      T.string]

commentStyle :: Binding
commentStyle = define "CommentStyle" $
  doc "The style of a comment" $
  T.enum ["line", "block", "datum"]

docstring :: Binding
docstring = define "Docstring" $
  doc "A documentation string" $
  T.wrap T.string

dialect :: Binding
dialect = define "Dialect" $
  doc "A Lisp dialect" $
  T.enum ["clojure", "emacsLisp", "commonLisp", "scheme"]

sExpression :: Binding
sExpression = define "SExpression" $
  doc ("A raw S-expression. This is an escape hatch for expressing arbitrary Lisp forms"
    ++ " that do not fit into the structured AST above.") $
  T.union [
    "atom">:
      doc "An atomic value" $
      T.string,
    "list">:
      doc "A list of S-expressions" $
      T.list (lisp "SExpression")]