From 983f56ce982fe8c42869cc625822fca8b72fed95 Mon Sep 17 00:00:00 2001 From: ningit Date: Tue, 24 Jan 2017 17:59:34 +0100 Subject: [PATCH] Initial commit 2. --- EDA/Arbus.hs | 63 ++++++++++++++++ Main.lhs | 91 +++++++++++++++++++++++ Makefile | 42 +++++++++++ REC/Base.hs | 76 ++++++++++++++++++++ REC/PasoNombre.hs | 63 ++++++++++++++++ REC/PasoValor.hs | 60 ++++++++++++++++ REC/Sintaxis.hs | 160 +++++++++++++++++++++++++++++++++++++++++ doc/manual.en.md | 99 +++++++++++++++++++++++++ doc/manual.es.md | 97 +++++++++++++++++++++++++ doc/manual.fr.md | 99 +++++++++++++++++++++++++ ejemplos/ackermann.rec | 1 + ejemplos/factorial.rec | 2 + ejemplos/nombre.rec | 3 + ejemplos/signo.rec | 5 ++ 14 files changed, 861 insertions(+) create mode 100644 EDA/Arbus.hs create mode 100644 Main.lhs create mode 100644 Makefile create mode 100644 REC/Base.hs create mode 100644 REC/PasoNombre.hs create mode 100644 REC/PasoValor.hs create mode 100644 REC/Sintaxis.hs create mode 100644 doc/manual.en.md create mode 100644 doc/manual.es.md create mode 100644 doc/manual.fr.md create mode 100644 ejemplos/ackermann.rec create mode 100644 ejemplos/factorial.rec create mode 100644 ejemplos/nombre.rec create mode 100644 ejemplos/signo.rec diff --git a/EDA/Arbus.hs b/EDA/Arbus.hs new file mode 100644 index 0000000..2020c40 --- /dev/null +++ b/EDA/Arbus.hs @@ -0,0 +1,63 @@ +-- | Módulo de implementación del tipo abstracto de datos árbol de búsqueda. +module EDA.Arbus ( + Arbus(ArbusVacio), + inserta, + consulta, + esta, + esVacio +) where + +-- | 'Arbus' es un árbol de búsqueda con clave y valor. +-- +-- Los elementos de tipo 'a' se denominarán «claves» y los del tipo 'b' «valores». +-- El tipo 'a' ha de ser ordenable (ser instancia de la clase 'Ord'). +-- +-- Se ha omitido la operación de borrado. +data Arbus a b = + -- | Árbol de búsqueda vacío. + ArbusVacio | + -- | Construye un árbol de búsqueda a partir de unos hijos, una clave y un valor. + Nodo (Arbus a b) a b (Arbus a b) + +-- | Inserta un elemento en el árbol de búsqueda. +inserta :: (Ord a) => a -> b -> Arbus a b -> Arbus a b +inserta c v ArbusVacio = Nodo ArbusVacio c v ArbusVacio +inserta c v (Nodo iz c' v' dr) + | c == c' = Nodo iz c v dr + | c < c' = Nodo (inserta c v iz) c' v' dr + | otherwise = Nodo iz c' v' (inserta c v dr) + +-- | Consulta el valor de una clave. +consulta :: (Ord a) => a -> Arbus a b -> Maybe b +consulta _ ArbusVacio = Nothing +consulta c (Nodo iz c' v dr) + | c == c' = Just v + | c < c' = consulta c iz + | otherwise = consulta c dr + +-- | Comprueba si una clave está en el árbol de búsqueda. +esta :: (Ord a) => a -> Arbus a b -> Bool +esta _ ArbusVacio = False +esta x (Nodo iz c _ dr) + | x == c = True + | x < c = esta x iz + | otherwise = esta x dr + +-- | Comprueba si el árbol es vacío. +esVacio :: Arbus a b -> Bool +esVacio ArbusVacio = True +esVacio _ = False + +-- | Obtiene el recorrido en inorden del árbol, que se corresponde con +-- la lista de pares clave valor ordenada por clave. +inorden :: Arbus a b -> [(a, b)] +inorden ArbusVacio = [] +inorden (Nodo iz c v dr) = inorden iz ++ (c, v):inorden dr + +-- Igualdad por comparación de inórdenes +instance (Eq a, Eq b) => Eq (Arbus a b) where + x == y = inorden x == inorden y + +-- Muestra la lista de pares en inorden. +instance (Show a, Show b) => Show (Arbus a b) where + show arb = "Arbus " ++ show (inorden arb) diff --git a/Main.lhs b/Main.lhs new file mode 100644 index 0000000..6e8cfe0 --- /dev/null +++ b/Main.lhs @@ -0,0 +1,91 @@ +% Intérprete de Rec + +Para documentación general y sobre uso del programa véase la documentación +adjunta separada. + +Detalles de implementación +-------------------------- + +El intérprete se ha dividido en diversos módulos que se encargan de distintas +tareas, como generar el árbol sintáctico abstracto a partir de la sintaxis +concreta o evaluar semánticamente dicho árbol. + +> import EDA.Arbus +> import REC.Base +> import REC.Sintaxis +> import qualified REC.PasoValor as Valor +> import qualified REC.PasoNombre as Nombre +> +> import System.Environment + +Para el análisis sintáctico se ha utilizado la biblioteca de Haskell +*Parsec* (versión 3). + +> import Text.Parsec (parse) +> import Text.Parsec.String (Parser) + +El estado viene representado por un árbol de búsqueda binario que asocia nombres +de función a pares lista de variables-término, que es la representación escogida +para las funciones de Rec. + +> -- | Estado del intérprete de Rec. +> type EstadoRec = Arbus String FuncionRec + +Inicialmente no hay función alguna predefinida. + +> -- | Estado inicial +> estadoInicial :: EstadoRec +> estadoInicial = ArbusVacio + +> -- | Parseador de términos cerrados +> tcerrado :: Parser ExpresionRec +> tcerrado = termino [] + + +La función principal lee texto de la entrada estándar y muestra el resultado en +la salida por defecto. + +> -- | Alias para el tipo de las funciones semánticas +> type FnSemantica = Arbus String FuncionRec -> ExpresionRec -> Resultado +> +> -- | Función principal +> main :: IO () +> main = do +> espec <- getContents +> fn <- averiguaFn getArgs +> mapM_ putStrLn (interpretaLinea fn estadoInicial $ lines espec) +> +> where averiguaFn :: IO [String] -> IO FnSemantica +> +> averiguaFn = fmap (\x -> if x then Nombre.valor else Valor.valor) +> . fmap (any (\arg -> arg == "-n")) + +El programa intentará interpretar cada línea en primer lugar como una declaración de +función. En caso de error se intentará interpretar como término cerrado. + +> interpretaLinea :: FnSemantica -> EstadoRec -> [String] -> [String] +> interpretaLinea _ _ [] = [] +> interpretaLinea fn st (x:xs) = either (\_ -> term:(interpretaLinea fn nst xs)) +> (\_ -> interpretaLinea fn nst xs) decl +> where decl = parse declaracion "" x +> term = interpretaTermino fn st x +> nst = either (\_ -> st) (\(nombre, def) -> inserta nombre def st) decl + +Esta función analiza sintácticamente la expresión dada como un término cerrado +y devuelve el resultado de evaluarla o un mensaje de error apropiado. + +> -- | Intenta parsear un término +> interpretaTermino :: FnSemantica -> EstadoRec -> String -> String +> interpretaTermino fn st exp = either (show) (evaluaTermino fn st) (parse tcerrado "" exp) + +Esta función evalúa semánticamente un término, mostrando el mensaje de error apropiado +en caso de fallo. + +> -- | Evalúa (semánticamente) un término ya parseado y convierte la salida +> evaluaTermino :: FnSemantica -> EstadoRec -> ExpresionRec -> String +> evaluaTermino fn st exp = case (fn st exp) of +> Valor v -> show v +> ENoVar id -> "Error: variable desconocida «" ++ id ++ "»." +> ENoFunc id -> "Error: función desconocida «" ++ id ++ "»." +> EAridad id ao ad -> "Error: aridad incorrecta en la función «" ++ +> id ++ "» (" ++ show ao ++ " en lugar de " ++ show ad ++ ")." diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..157071c --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +# Makefile para el intérprete de REC + +PANDOC := $(shell pandoc -v 2> /dev/null) +FUENTES := $(shell find . -type f -name '*.hs' -or -name '*.lhs') + +.PHONY: info clean clean-doc doc + +# Compilar con GCH +make: + ghc -O Main.lhs -fno-warn-tabs -o rec + +# Eliminar tabulaciones en los archivos fuente +destab: + $(foreach f, ${FUENTES}, expand ${f} > ${f}.expanded; mv ${f}.expanded ${f};) + +# Generar la documentación a nivel de código (con Haddock) +doc: + haddock -h --optghc=-fno-warn-tabs -o doc/ref Main.lhs -t "Ecuaciones recursivas - Lenguaje Rec" + +# Generar la documentación adjunta +ifdef PANDOC +info: Main.htm doc/manual.es.htm doc/manual.en.htm doc/manual.fr.htm + +else +info: + $(error Se necesita Pandoc para generar la documentación) +endif + +%.htm: %.md + pandoc --from=markdown -s --to=html5 $< -o $@ + +%.htm: %.lhs + pandoc --from=markdown+lhs -s --to=html5 $< -o $@ + +# Borrar archivos intermedios generados por GHC +clean: + $(RM) REC/*.hi EDA/*.hi REC/*.o EDA/*.o Main.hi Main.o + +# Borra la documentación generada +clean-doc: + $(RM) -r doc/ref + $(RM) Main.htm doc/*.htm diff --git a/REC/Base.hs b/REC/Base.hs new file mode 100644 index 0000000..1961769 --- /dev/null +++ b/REC/Base.hs @@ -0,0 +1,76 @@ +----------------------------------------------------------------------------- +-- | +-- Module : REC.Base +-- +-- Definiciones básicas de Rec. +-- +-- Sintaxis abstracta y tipo del valor semántico. +-- +----------------------------------------------------------------------------- + +module REC.Base ( + ExpresionRec(..), + FuncionRec, + Resultado(..), + esValor +) where + +-- | Representación abstracta de un término de Rec. +data ExpresionRec = + Literal Integer | + Variable String | + Suma ExpresionRec ExpresionRec | + Resta ExpresionRec ExpresionRec | + Producto ExpresionRec ExpresionRec | + Condicional ExpresionRec ExpresionRec ExpresionRec | + Llamada String [ExpresionRec] + deriving (Show) + +-- | Definición de función del lenguaje Rec. +-- +-- Es un par con la lista de nombres de variables y el +-- término que constituye su definición. + +type FuncionRec = ([String], ExpresionRec) + +-- | Resultado de la evaluación de un término Rec. +-- +-- Desglosa las posibles causas de error como sigue: +data Resultado = + -- | Resultado correcto + Valor Integer | + -- | Referencia a una función desconocida + ENoFunc String | + -- | Referencia a una variable desconocida + ENoVar String | + -- | Llamada a función con una aridad diferente de la de su definición + -- (se indican por ese orden) + EAridad String Int Int + deriving (Show, Eq) + +-- | Aritmética numérica por comodidad. +-- En caso de error prevalece el generado en el término más a la izquierda. +instance Num Resultado where + (Valor n) + (Valor m) = Valor (n + m) + (Valor _) + res = res + res + _ = res + + (Valor n) * (Valor m) = Valor (n * m) + (Valor _) * res = res + res * _ = res + + abs (Valor n) = Valor (abs n) + abs res = res + + signum (Valor n) = Valor (signum n) + signum _ = Valor 1 + + fromInteger n = Valor n + + negate (Valor n) = Valor (negate n) + negate res = res + +-- | Detecta si un resultado es correcto (es decir, no es un error) +esValor :: Resultado -> Bool +esValor (Valor _) = True +esValor _ = False diff --git a/REC/PasoNombre.hs b/REC/PasoNombre.hs new file mode 100644 index 0000000..1623c6a --- /dev/null +++ b/REC/PasoNombre.hs @@ -0,0 +1,63 @@ +----------------------------------------------------------------------------- +-- | +-- Module : REC.PasoNombre +-- +-- Función semántica de paso por nombre. +-- +-- La definición de la función semántica es prácticamente igual a la de paso +-- por valor. Los errores de función desconocida en los parámetros que no +-- lleguen a evaluarse pasarán desapercibidos. +-- +----------------------------------------------------------------------------- + +module REC.PasoNombre ( + valor +) where + +import REC.Base +import EDA.Arbus + +-- | Función semántica de paso por nombre, +-- acorde a la definición del capítulo 9 de +-- /Formal Semantics of Programming Languages/ de Glynn Winksel. + +valor :: Arbus String FuncionRec -- ^ Entorno de funciones + -> ExpresionRec -> Resultado + +valor = evaluar ArbusVacio + + +-- | Definición completa de la función semántica. + +evaluar :: Arbus String Resultado -- ^ Entorno de variables (a resultados) + -> Arbus String FuncionRec -- ^ Entorno de funciones + -> ExpresionRec -> Resultado + +evaluar _ _ (Literal m) = Valor m +evaluar vs fs (Suma izq dr) = evaluar vs fs izq + evaluar vs fs dr +evaluar vs fs (Resta izq dr) = evaluar vs fs izq - evaluar vs fs dr +evaluar vs fs (Producto izq dr) = evaluar vs fs izq * evaluar vs fs dr +evaluar vs fs (Condicional cn rt rf) + | not (esValor cond) = cond + | cond == (Valor 0) = evaluar vs fs rt + | otherwise = evaluar vs fs rf + where cond = evaluar vs fs cn + +evaluar vs _ (Variable id) = comoRes $ consulta id vs + where comoRes (Just res) = res + comoRes Nothing = ENoVar id + +evaluar vs fs (Llamada id ps) = evaluarAux (consulta id fs) + where evaluarAux :: Maybe FuncionRec -> Resultado + + evaluarAux Nothing = ENoFunc id + evaluarAux (Just (args, cuerpo)) + | length ps == length args = evaluar nuevoVar fs cuerpo + | otherwise = EAridad id (length ps) (length args) + + where nuevoVar = evParams args (map (evaluar vs fs) ps) + + evParams :: [String] -> [Resultado] -> Arbus String Resultado + + evParams (n:ns) (r:rs) = inserta n r (evParams ns rs) + evParams _ [] = ArbusVacio diff --git a/REC/PasoValor.hs b/REC/PasoValor.hs new file mode 100644 index 0000000..4ae6f0e --- /dev/null +++ b/REC/PasoValor.hs @@ -0,0 +1,60 @@ +----------------------------------------------------------------------------- +-- | +-- Module : REC.PasoValor +-- +-- Función semántica de paso por valor. +-- +----------------------------------------------------------------------------- + +module REC.PasoValor ( + valor +) where + +import REC.Base +import EDA.Arbus + +-- | Función semántica de paso por valor, +-- acorde a la definición del capítulo 9 de +-- /Formal Semantics of Programming Languages/ de Glynn Winksel. + +valor :: Arbus String FuncionRec -- ^ Entorno de funciones + -> ExpresionRec -> Resultado + +valor = evaluar ArbusVacio + + +-- | Definición completa de la función semántica. + +evaluar :: Arbus String Integer -- ^ Entorno de variables (a valores enteros) + -> Arbus String FuncionRec -- ^ Entorno de funciones + -> ExpresionRec -> Resultado + +evaluar _ _ (Literal m) = Valor m +evaluar vs fs (Suma izq dr) = evaluar vs fs izq + evaluar vs fs dr +evaluar vs fs (Resta izq dr) = evaluar vs fs izq - evaluar vs fs dr +evaluar vs fs (Producto izq dr) = evaluar vs fs izq * evaluar vs fs dr +evaluar vs fs (Condicional cn rt rf) + | not (esValor cond) = cond + | cond == (Valor 0) = evaluar vs fs rt + | otherwise = evaluar vs fs rf + where cond = evaluar vs fs cn + +evaluar vs _ (Variable id) = comoRes $ consulta id vs + where comoRes (Just n) = (Valor n) + comoRes Nothing = ENoVar id + +evaluar vs fs (Llamada id ps) = evaluarAux (consulta id fs) + where evaluarAux :: Maybe FuncionRec -> Resultado + + evaluarAux Nothing = ENoFunc id + evaluarAux (Just (args, cuerpo)) + | length ps == length args = case evParams args (map (evaluar vs fs) ps) of + Right vs' -> evaluar vs' fs cuerpo + Left res -> res + | otherwise = EAridad id (length ps) (length args) + + evParams :: [String] -> [Resultado] -> Either (Resultado) (Arbus String Integer) + + evParams (n:ns) ((Valor r):rs) = (evParams ns rs) >>= (return . (inserta n r)) + evParams _ (r:_) = Left r + evParams _ [] = Right ArbusVacio diff --git a/REC/Sintaxis.hs b/REC/Sintaxis.hs new file mode 100644 index 0000000..50221e0 --- /dev/null +++ b/REC/Sintaxis.hs @@ -0,0 +1,160 @@ +----------------------------------------------------------------------------- +-- | +-- Module : REC.Sintaxis +-- +-- Módulo de interpretación sintáctica del lenguaje Rec. +-- +-- Sigue la sintaxis establecida en el capítulo 9 de +-- /Formal Semantics of Programming Languages/ de Glynn Winskel, +-- con las particularidades que se indiquen. +-- +-- Todos los parseadores ignoran los carácteres extra a la derecha de una +-- expresión. +-- +----------------------------------------------------------------------------- + +module REC.Sintaxis ( + termino, + declaracion +) where + +import Text.Parsec +import Text.Parsec.String (Parser) +import Text.Parsec.Expr +import Control.Applicative((<$>)) +import REC.Base + +------ +-- Definiciones generales +------ + +-- | Número (en decimal) +numero :: Parser Integer +numero = do + n <- many1 digit + spaces + return (read n) + +-- | Indentificador de variables y nombres de función +identif :: Parser String +identif = do + cab <- letter + col <- many alphaNum + spaces + return (cab:col) + +-- | Signatura de una función +signatura :: Parser [String] +signatura = sepBy identif separador + where separador = do + _ <- char ',' + spaces + +-- | Parámetros de llamada a función +parametros :: [String] -> Parser [ExpresionRec] +parametros vs = sepBy (termino vs) separador + where separador = do + _ <- char ',' + spaces + +-- | Operador binario (incluye el espacio consecutivo) +opbinario :: Char -> Parser Char +opbinario c = do + _ <- char c + spaces + return c + +------ +-- Definiciones de elementos del lenguaje +------ + +-- | Literal numérico +literal :: Parser ExpresionRec +literal = Literal <$> numero + + +-- | Variable +variable :: [String] -> Parser ExpresionRec +variable vs = do + id <- identif + variableAux id + where variableAux :: String -> Parser ExpresionRec + variableAux id + | id `elem`vs = return (Variable id) + | otherwise = fail "unexpected variable name" + +-- | Llamada a función +llamada :: [String] -> Parser ExpresionRec +llamada vs = do + id <- identif + _ <- char '(' + ps <- parametros vs + _ <-char ')' + spaces + return (Llamada id ps) + +-- | Paréntesis +parentesis :: [String] -> Parser ExpresionRec +parentesis vs = do + _ <- char '(' + e <- termino vs + _ <- char ')' + spaces + return e + +-- | Condicional +condicional :: [String] -> Parser ExpresionRec +condicional vs = do + _ <- string "if" + spaces + cn <- termino vs + _ <- string "then" + spaces + rt <- termino vs + _ <- string "else" + spaces + rf <- termino vs + spaces + return (Condicional cn rt rf) + + +------ +-- Definiciones finales +------ + +-- | Expresiones básicas del lenguaje +termBase :: [String] -> Parser ExpresionRec +termBase vs = literal <|> try (condicional vs) <|> try (llamada vs) <|> variable vs <|> parentesis vs + +-- | Tabla de operadores del lenguaje +tablaOp = [ + [Prefix (char '-' >> return (Resta (Literal 0)))], + [Infix (opbinario '*' >> return Producto) AssocLeft], + [Infix (opbinario '+' >> return Suma) AssocLeft, + Infix (opbinario '-' >> return Resta) AssocLeft] + ] + +-- | Analiza sintácticamente un término del lenguaje Rec. +-- +-- Permite la utilización de paréntesis, espacios entre términos, considera +-- la precedencia del operador producto y admite - como operador prefijo. + +termino :: [String] -> Parser ExpresionRec +termino vs = buildExpressionParser tablaOp (termBase vs) + +-- | Analiza sintácticamente una declaración simple de función. +-- +-- Asegura (a nivel sintáctico) que las variables empleadas en la definición +-- del término están incluidas en la definición formal de variables anterior. + +declaracion :: Parser (String, FuncionRec) +declaracion = do + id <- identif + _ <- char '(' + vars <- signatura + _ <- char ')' + spaces + _ <- char '=' + spaces + t <- termino vars + return (id, (vars, t)) diff --git a/doc/manual.en.md b/doc/manual.en.md new file mode 100644 index 0000000..bf46c6a --- /dev/null +++ b/doc/manual.en.md @@ -0,0 +1,99 @@ +% Rec interpreter + +This program is an interpreter for a recursion functions language. It is based on the **Rec** language defined in the ninth chapter (*Recursive equations*) of +Glynn Winskel's book *The Formal Semantics of Programming Languages: An Introduction*. + +We must point out some particularities, mainly consequences of the concretion from the abstract language presentation in that book: + +* Parenthesis can be used to remove the ambiguity of the language's textual representation. They can be used to enclose any term. Function call and function definition syntax include parenthesis, which cannot be omitted even if the functions does not receive any parameter. + +* Arithmetic operations associate from the left and the product precede the sum. Spaces can be added freely between operators, literals, variables, function calls... + +* For convenience, a prefix negation operator has been added (-). It can easily be removed if needed. + +* The sets of numerical and functional variables are not fixed in advance, they are defined as the names are used. The identifier of both types of variables should be words composed of letters and numbers, whose first character is a letter. + +* The actual representation of the numbers in the interpreter uses arbitrary precision integers (the Haskell's type `Integer`{.haskell}). + +* Function declaration can be interleaved with term evaluation. When trying to evaluate a term using an undefined function name, an error will be produced. + +* The repeated declaration of the same function variable will overwrite the previous definition, regardless of their arity. + +* The two semantics describes in the book have been implemented in a denotational manner, call-by-value and call-by-name. + + +Interpreter behaviour +--------------------- + +The interpreter process the user input line by line, in a read-eval-print loop (REPL). A line can contain a function definition or a closed term to evaluate. Each line is independent except for the function definitions which make grow the store of functions known by the system. + +After evaluating a term, the result will be shown. Successful function declarations do not produce any text. + +Undefined names can be used at a function definition. That allows defining recursive and mutually recursive functions. + +An informative message will be shown in case of error. Possible errors are: syntactic errors, unknown variable, unknown function or mistaken arity in a function call. + +When a syntactically correct term for which the semantics are not defined is evaluated (the derivation process is not finite) the interpreter will hang. It can be interrupted by typing *Ctrl + C*. + +By default, terms are evaluated using call-by-value semantics. If you want to use call-by-name, you must write the option `-n` as an argument to the program in the command line. + + +Recommendations to the user +--------------------------- + +For the sake of simplicity, no file reading or writing facilities have been incorporated. + +However external solutions are available. In Unix-like systems: + +* Files can be interpreted by a redirection to the standard input: +./rec < file.rec. +* Further writing can be done after interpreting the file: +cat file.rec - | ./rec. +* In order to have the ability to edit lines and maintain the history of inserted lines, the *rlwrap* program is recommended: +rlwrap ./rec + + +Examples +-------- + +Jointly with the interpreter some well known examples are included: + +* *Factorial*. This function is not defined on the negative integers, so that the interpreter will hang if a term like `f(-1)`{.haskell} is evaluated. + +> ```haskell +> f(x) = if x then 1 else (x * f(x-1)) +> ``` + +* *Ackermann's function*. The calculation of `A(3, 9)`{.haskell} takes some time. + +> ```haskell +> A(m, n) = if m then n + 1 else (if n then A(m-1, 1) else A(m-1, A(m, n-1))) +> ``` + + +* *Sign fuction* (exercise 9.1 of the book). `s` calculates its parameter's sign, using the auxiliary function `f`. + +> ```haskell +> s(x) = if x then 0 else f(x, 0 - x) +> f(x, y) = if x then 1 else (if y then (0-1) else f(x-1, y-1)) +> ``` + +An example `nombre.rec` has also been included to show that the call-by-value and call-by-name semantics may differ. + +> ```haskell +> f(x) = f(x) + 1 +> g(x) = 7 +> g(f(2)) +> ``` + +The evaluation of `g(f(2))`{.haskell} ends in the call-by-name mode but it does not in call-by-value, as this semantics requires the evaluation of the unneeded argument `f(2)`{.haskell}, which does not finish. + + +Implementation details +---------------------- + +The program is composed of various modules. In `REC.Base`{.haskell} module, data types are defined for the abstract syntax tree of the language and for the computation results along with its arithmetic. The module `REC.Sintaxis`{.haskell} is charged of the syntax analysis and for that purpose it makes use of the *Parsec* Haskell's library (version 3). Modules `REC.PasoValor`{.haskell} and `REC.PasoNombre`{.haskell} contain the semantic functions for call-by-value and call-by-name. + +These modules use a custom implementation of binary search trees described in `EDA.Arbus`{.haskell} and which can be compared to `Data.Map`{.haskell}. Finally, the `Main.lhs` file evaluates lines from the standard input and shows their results. Here the semantic function is selected according to the program parameters. + +For further information, the code and joint documentation is available in Spanish. diff --git a/doc/manual.es.md b/doc/manual.es.md new file mode 100644 index 0000000..f549e88 --- /dev/null +++ b/doc/manual.es.md @@ -0,0 +1,97 @@ +% Intérprete de Rec + +Este programa es un intérprete de un lenguaje de definición de funciones recursivas sobre los números enteros. Está basado en el lenguaje **Rec** definido en el noveno capítulo (*Recursion equations*) del libro *The Formal Semantics of Programming Languages: An Introduction* de Glynn Winskel. + +Presenta diversas particularidades surgidas en su mayor parte de la necesidad de concretar la presentación abstracta del lenguaje: + +* Se admiten paréntesis para eliminar la ambigüedad de su representación textual. Estos pueden encerrar cualquier término. La llamada a función y la sintaxis para definirlas también incluye paréntesis, que no se pueden omitir ni siquiera cuando la función no reciba parámetros. + +* Las operaciones aritméticas asocian por la izquierda y el producto precede a la suma. Entre operadores, literales, variables, llamadas a función... se pueden añadir espacios libremente. + +* Por comodidad se ha añadido el operador prefijo de cambio de signo (-). En caso de necesidad se puede eliminar sin menor problema. + +* Los conjunto de variables numéricas y de función no están preestablecidos, se construyen con el uso. Los identificadores de ambos tipos de variables han +de ser palabras compuestas por letras y números, cuyo primer carácter sea una letra. + +* La representación concreta de los números en el intérprete utiliza enteros de precisión arbitraria (el tipo `Integer`{.haskell} de Haskell). + +* La declaración de funciones se puede intercalar con la evaluación de términos cerrados. Si se intenta evaluar un término que utilice una función sin +definir se producirá un error. + +* Declaraciones sucesivas de una misma función sobrescribirán las definiciones anteriores, independientemente de su aridad. + +* Se han implementado al estilo denotacional las dos semánticas descritas en el libro, con paso de parámetros por valor y por nombre. + + +Funcionamiento del intérprete +----------------------------- + +El intérprete procesa la entrada del usuario línea a línea, en modo lectura-evaluación-impresión (REPL). Una línea puede contener una definición de función o un término cerrado a evaluar. Cada línea es independiente salvo por las definiciones de función, que se añaden al almacén de funciones disponibles en el sistema. + +Tras evaluar satisfactoriamente un término se mostrará el resultado obtenido. La declaración exitosa de funciones no produce texto alguno. + +Al definir funciones se pueden utilizar funciones que aún no hayan sido definidas, lo que permite expresar funciones recursivas y mutuamente recursivas. + +En caso de error se mostrará un mensaje informativo. Algunos de los posibles errores pueden ser: errores sintácticos, variable desconocida, función desconocida o función llamada con una aridad distinta a la de su declaración. + +En caso de evaluar un término sintácticamente correcto para el que no esté definida la semántica (no exista una derivación finita) el intérprete quedará colgado. Se puede interrumpir el cómputo introduciendo *Ctrl + C*. + +Por defecto los términos se evalúan respecto a la semántica de paso de parámetros por valor. Si se quiere utilizar paso por nombre es preciso indicarlo +mediante la opción `-n` como argumento al programa en la línea de comandos. + + +Recomendaciones de uso +---------------------- + +Por simplicidad no se ha añadido soporte para lectura de archivos ni facilidades de escritura. + +No obstante existen soluciones externas. En sistemas tipo Unix: + +* Se pueden interpretar archivos redirigiéndolos a la entrada estándar: `./rec < archivo.rec`{.bash}. +* Se puede continuar escribiendo tras interpretar el archivo utilizando: `cat archivo.rec - | ./rec`{.bash}. +* Para tener la posibilidad de editar las líneas y conservar el historial de líneas introducidas se recomienda utilizar el programa *rlwrap*: `rlwrap ./rec`{.bash}. + + +Ejemplos +-------- + +Adjuntos al programa se incluyen algunos ejemplos bien conocidos: + +* *Factorial*. La función no está definida sobre los enteros negativos, así que el intérprete se quedará colgado si se evalúa por ejemplo `f(-1)`{.haskell}. + +> ```haskell +> f(x) = if x then 1 else (x * f(x-1)) +> ``` + +* *Función de Ackermann*. El cálculo de `A(3, 9)`{.haskell} se hace esperar. + +> ```haskell +> A(m, n) = if m then n + 1 else (if n then A(m-1, 1) else A(m-1, A(m, n-1))) +> ``` + +* *Función signo* (ejercicio 9.1 del libro). `s` calcula el signo de su parámetro, utilizando la función auxiliar `f`. + +> ```haskell +> s(x) = if x then 0 else f(x, 0 - x) +> f(x, y) = if x then 1 else (if y then (0-1) else f(x-1, y-1)) +> ``` + +Además se ha incluido un ejemplo `nombre.rec` en el que la semántica de paso por valor y la de paso por nombre difieren + +> ```haskell +> f(x) = f(x) + 1 +> g(x) = 7 +> g(f(2)) +> ``` + +pues la evaluación de `g(f(2))`{.haskell} termina en paso por nombre pero no en paso por valor, pues este último modo requiere la evaluación del argumento innecesario `f(2)`{.haskell}, que no termina. + + +Detalles de implementación +-------------------------- + +El programa consta de diversos módulos. En el módulo `REC.Base`{.haskell} se definen los tipos de datos para el árbol de sintaxis abstracta del lenguaje y el resultado de los cómputos junto con su aritmética. El módulo `REC.Sintaxis`{.haskell} es el encargado del análisis sintáctico y para ello utiliza la biblioteca *Parsec* (version 3) de Haskell. A su vez los módulos `REC.PasoValor`{.haskell} y `REC.PasoNombre`{.haskell} contienen las funciones semánticas de paso por valor y paso por nombre respectivamente. + +Estos módulos utilizan una implementación propia de un árbol binario de búsqueda descrito en `EDA.Arbus`{.haskell} comparable al de `Data.Map`{.haskell}. Finalmente el archivo `Main.lhs` se encarga de evaluar las líneas tomadas de la entrada estándar y mostrar los resultados. Aquí se selecciona la función semántica adecuada según se indique en los parámetros. + +Para información más detallada véase el código y la documentación generada junto a él. diff --git a/doc/manual.fr.md b/doc/manual.fr.md new file mode 100644 index 0000000..26b7e6c --- /dev/null +++ b/doc/manual.fr.md @@ -0,0 +1,99 @@ +% Interprète de Rec + +Ce logiciel est un interprète d'un langage de définition de fonctions récursives. Il se fonde sur le langage **Rec** défini dans le neuvième chapitre (*Recursion equations*) du livre *The Formal Semantics of Programming Languages: An Introduction* de Glynn Winksel. + +Il faut marquer certaines particularités concernant essentiellement le besoin de concréter la présentation abstraite du langage : + +* Des parenthèses sont admises pour éliminer l'ambiguïté de sa représentation textuelle. Ils peuvent s'employer pour enfermer n'importe quel terme. L'appel de fonction et la syntaxe pour la définir incluent de parenthèse aussi, qui ne peuvent pas s'omettre même si la fonction ne recevrait aucun paramètre. + +* Les opérations arithmétiques sont associatives à gauche et le produit précède à l'addition. On peut mettre d'espace librement entre opérateurs, littéraux, variables, appels à fonctions... + +* Pour facilité d'usage, l'opérateur préfixe de changement de signe (-) a été inclus. Les cas échéant, il peut s'éliminer sans problème. + +* Les ensembles de variables numériques ou de fonction ne sont pas préétablis. Les identifiants de chaque genre de variable doivent être des paroles composées +de lettres et nombres, dont le premier caractère soit une lettre. + +* La représentation concrète des nombres utilise des entiers multiprécision (le type `Integer`{.haskell} de Haskell). + +* La déclaration de fonctions peut s'intercaler avec l'évaluation de termes fermés. Si on essaie d'évaluer un terme contenant une fonction indéfinie, une erreur se produira. + +* La déclaration répétée d'une même variable fonctionnelle écrasera la déclaration précédente, sans regarder à son arité. + +* On a implémenté les deux sémantiques décrites dans le livre, avec passage de paramètre par valeur et par nom. + + +Fonctionnement de l'interprète +----------------------------- + +L'interprète digère l'entrée de l'utilisateur ligne par ligne. Une ligne peut contenir une définition de fonction ou un terme fermé à être évalué. Chaque +ligne est indépendante sauf pour les définitions de fonctions qui augmentent le magasin de fonctions connues par le système. + +Après l'évaluation satisfactoire d'un terme, le résultat obtenu sera montré. La déclaration réussie d'un fonction ne produise aucun texte. + +Lors de la définition de fonctions, on peut faire référence à fonctions qui n'aient pas été définies auparavant, ce qui permet d'exprimer fonctions récursives et mutuellement récursives. + +En cas d'erreur, un message informatif sera montré. Quelques erreurs possibles sont : erreur syntactique, variable inconnue, fonction inconnue ou fonction appelé avec une arité différente à celle de sa déclaration. + +Lors que un terme fermé syntaxiquement correct mais pour lequel la sémantique n'est pas défini (il n'y pas une dérivation finie) est évalué, l'interprète se +bloquera. Le calcul peut s'interrompre en écrivant *Ctrl + C*. + +Par défaut, les termes sont évalués avec la sémantique de passage de paramètre par valeur. Lors que le passage par nom est voulu, il faut l'indiquer avec l'option `-n` a la ligne de commande. + + +Recommandation pour l'utilisateur +--------------------------------- + +Pour simplicité, il n'y a pas de support pour la lecture de fichiers ou de facilités d'écriture dans le logiciel même. + +Cependant il y a des solutions externes. Pour système type Unix: + +* Des fichiers peuvent être redirigés à la sortie standard : +./rec < fichier.rec. +* On peut continuer à écrire après l'interprétation du fichier avec : +cat archivo.rec - | ./rec. +* Pour avoir la possibilité d'éditer les lignes et préserver l'historique de lignes introduites auparavant, on recommande l'utilisation du logiciel *rlwrap* : rlwrap ./rec + + +Exemples +-------- + +On ajoute des exemples exécutables bien connues : + +* *Factorielle*. La fonction n'est pas définie sur les entiers négatifs, alors l'interprète se rendra en panne lors de l'évaluation d'un terme tel que `f(-1)`{.haskell}. + +> ```haskell +> f(x) = if x then 1 else (x * f(x-1)) +> ``` + +* *Fonction d'Ackermann*. Le calcul de `A(3, 9)`{.haskell} se fait attendre. + +> ```haskell +> A(m, n) = if m then n + 1 else (if n then A(m-1, 1) else A(m-1, A(m, n-1))) +> ``` + +* *Fonction signe* (exercice 9.1 du livre). `s` calcule le signe de son paramètre en utilisant la fonction auxiliaire `f`. + +> ```haskell +> s(x) = if x then 0 else f(x, 0 - x) +> f(x, y) = if x then 1 else (if y then (0-1) else f(x-1, y-1)) +> ``` + +En plus, on a ajouté un exemple `nombre.rec` où les sémantiques de passage par valeur et par nom diffèrent. + +> ```haskell +> f(x) = f(x) + 1 +> g(x) = 7 +> g(f(2)) +> ``` + +car l'évaluation de `g(f(2))`{.haskell} finit avec passage par nom mais pas avec passage par valeur, parce que ce dernier mode a besoin de l'évaluation de l'argument inutile `f(2)`{.haskell}, qui ne finit jamais. + + +Détails d'implémentation +-------------------------- + +Le logiciel est composé de divers modules. Dans le module `REC.Base`{.haskell}, on trouve les types de donnés pour l'arbre syntaxique abstrait de langage et le résultat de calculs, et l'arithmétique de ce derniers. Le module `REC.Sintaxis`{.haskell} est chargé de l'analyse syntactique et pour cela il recourt à la bibliothèque *Parsec* (version 3) de Haskell. Les modules `REC.PasoValor`{.haskell} et `REC.PasoNombre`{.haskell} contiennent les fonctions sémantiques de passage par valeur et passage par nom respectivement. + +Ces modules utilisent une propre implémentation d'un arbre de recherche décrit dans `EDA.Arbus`{.haskell} comparable à `Data.Map`{.haskell}. Finalement le fichier `Main.lhs` s'occupe d'évaluer les lignes pris de l'entrée standard et montrer leurs résultats. Ici on fixe la fonction sémantique adéquate selon l'indication des paramètres. + +Pour une information plus détaillée, le code et la documentation jointe est disponible en castillan. diff --git a/ejemplos/ackermann.rec b/ejemplos/ackermann.rec new file mode 100644 index 0000000..211a206 --- /dev/null +++ b/ejemplos/ackermann.rec @@ -0,0 +1 @@ +A(m, n) = if m then n + 1 else (if n then A(m-1, 1) else A(m-1, A(m, n-1))) diff --git a/ejemplos/factorial.rec b/ejemplos/factorial.rec new file mode 100644 index 0000000..2837e25 --- /dev/null +++ b/ejemplos/factorial.rec @@ -0,0 +1,2 @@ +f(x) = if x then 1 else (x * f(x-1)) +f(1000) diff --git a/ejemplos/nombre.rec b/ejemplos/nombre.rec new file mode 100644 index 0000000..08e296d --- /dev/null +++ b/ejemplos/nombre.rec @@ -0,0 +1,3 @@ +f(x) = f(x) + 1 +g(x) = 7 +g(f(2)) diff --git a/ejemplos/signo.rec b/ejemplos/signo.rec new file mode 100644 index 0000000..b956e73 --- /dev/null +++ b/ejemplos/signo.rec @@ -0,0 +1,5 @@ +s(x) = if x then 0 else f(x, 0 - x) +f(x, y) = if x then 1 else (if y then (0-1) else f(x-1, y-1)) +s(6) +s(0-5) +s(0)