FOFI originated as a university project in Brazil, designed to help occupational therapists create thematic games for children on the autism spectrum. To learn more (in Portuguese), check out the local coverage: here.
A high-performance, functionally-pure compiler for the FOFI language, implemented in Haskell. FOFI (Framework Orientado para Facilitação Inclusiva) is a simple, statically-typed programming language inspired by both imperative and functional paradigms. Our compiler is built with modular design techniques, providing a clean architecture for lexical analysis, parsing, semantic analysis, and code generation.
FOFI is designed to be easy to learn and powerful enough to demonstrate key concepts of compiler design. It achieves:
β’ Functional-purity: Emphasizing immutable data structures and declarative design.
β’ High-level abstractions: Leveraging Haskell's monadic parser combinators and lazy evaluation.
β’ Readable syntax: Drawing ideas from both imperative and functional styles, making it easy to transition from other languages.
See the FOFI language documentation for more details about the syntax, data types, control structures, and built-in functions.
β’ Lexical Analysis using deterministic finite automata (DFA).
β’ Recursive Descent Parsing with monadic combinators.
β’ Polymorphic Type Inference and advanced semantic checks.
β’ Intermediate Representation in continuation-passing style (CPS).
β’ Optimized JavaScript Code Generation.
β’ Cross-platform: The compiler is written in Haskell using GHC, so it runs on most systems.
To build and install the compiler, make sure you have GHC (Glasgow Haskell Compiler) and Cabal installed.
Then, from the repository root, run:
cabal update
cabal install --only-dependencies
cabal build
This will produce the binary (called "fofi-compiler" by default) in one of Cabal's build directories.
Once compiled, run:
./dist/newstyle/build/.../fofi-compiler <input-file>
The compiler will:
- Lex and parse the input FOFI file.
- Perform semantic checks (type checking, variable declarations, etc.).
- Generate the corresponding JavaScript code (saving it in .js).
A typical command might look like:
fofi-compiler my_program.fofi
If compilation succeeds, you'll see:
Compilation successful!
Check out the newly generated "my_program.fofi.js" file in the same directory.
This repository is organized into key modules, each handling a major phase of compilation. Below are brief overviews of some files and a sample of how they work.
The compiler's top-level behavior is defined in src/Main.hs. It reads command-line arguments, calls into the different phases, and writes the final JavaScript output.
module Main where
import System.Environment (getArgs)
import Lexer (tokenize)
import Parser (parse)
import SemanticAnalyzer (analyze)
import CodeGenerator (generate)
main :: IO ()
main = do
args <- getArgs
case args of
[inputFile] -> do
contents <- readFile inputFile
let tokens = tokenize contents
case parse tokens of
Left error -> putStrLn $ "Parse error: " ++ error
Right ast -> do
case analyze ast of
Left error -> putStrLn $ "Semantic error: " ++ error
Right _ -> do
let code = generate ast
writeFile (inputFile ++ ".js") code
putStrLn "Compilation successful!"
_ -> putStrLn "Usage: fofi-compiler <input-file>"
Lexer.hs transforms raw text into tokens (e.g., identifiers, types, operators):
module Lexer where
import Data.Char (isAlpha, isAlphaNum, isDigit, toLower)
data Token = Token
{ tokenType :: String
, tokenValue :: String
, tokenLine :: Int
} deriving (Show, Eq)
-- Core lexer function
tokenize :: String -> [Token]
tokenize input = ...
Parser.hs converts tokens into an Abstract Syntax Tree (AST). We use a recursive descent approach. Once the AST is built, we can do further checks or transformations:
module Parser where
import Lexer (Token(..))
data AST = ...
-- This data type encapsulates the entire structure of the program.
parse :: [Token] -> Either String AST
parse tokens = ...
SemanticAnalyzer.hs checks for type correctness, variable declarations, etc. It uses a symbol table to ensure everything is well-typed:
module SemanticAnalyzer where
import qualified Data.Map as Map
import Parser (AST(..), Expression(..))
analyze :: AST -> Either String ()
analyze (Program _ varDecls stmts) = ...
CodeGenerator.hs traverses the validated AST and emits JavaScript code:
module CodeGenerator where
import Parser (AST(..), Expression(..))
generate :: AST -> String
generate (Program desc varDecls stmts) = ...
Below is a minimal "Hello, FOFI!" example. For a more in-depth guide, see the FOFI Language Documentation.
Create a file named hello.fofi
:
programa
"This is a simple FOFI program"
var
texto message;
{
message : "Hello, FOFI!";
mostrar(message);
}
Compile it:
fofi-compiler hello.fofi
You'll get hello.fofi.js
. Run it with Node.js (or any JavaScript runtime):
node hello.fofi.js
Expected output:
Hello, FOFI!
For additional examples, including control structures, function usage, and advanced graphics/animation, check out docs/README.md.
We welcome contributions! If you'd like to propose a feature or fix a bug:
- Fork the repository and make your changes in a new branch.
- Create a Pull Request describing your changes.
- Ensure your code passes any relevant tests.
Also, please open an issue if you find any bugs or have feature requests. See CONTRIBUTING.md for details.