Brainfunct

From Esolang
Jump to: navigation, search

Brainfunct is a functional esoteric programming language based on brainfuck, created by User:Koen in 2012 when he observed a lack of functional languages among the plethora of Brainfuck derivatives. A program in brainfunct consists in a succession of declaration of functions. Functions have no name but have a number, corresponding to the order in which they are declared. Functions operates on a tape similar to brainfuck's. The commands are:

Command Description
> Move the pointer to the right
< Move the pointer to the left
+ Increment the memory cell under the pointer
- Decrement the memory cell under the pointer
. Output the character signified by the cell at the pointer
, Input a character and store it in the cell at the pointer
@ Call the function signified by the cell under the pointer

Function declaration

There are two versions of Brainfunct. The only difference is the syntax for function declaration.

Brainfunct-slash

In brainfunct-slash, functions are simply separated by the symbol /. The first function is number 1. The last function is the main function; it cannot be called by other functions, but it is called when the execution starts.

Brainfunct-nested

The great addition of brainfunct-nested is the use of parenthesizing instead of the simple /. This allows for nested functions. Nested functions cannot be called from out of the function where they are declared. The number of a function is the smallest number available within their scope, starting with less deep functions. All commands not included in a function are part of the main function. The following is an example of how functions are numbered in this version:

(1) (2) (((7)6)3) (4) (((8)6)(7)5) main

Sample programs

Truth-machine

////////////////////////////////////////////////.@/,.@

In this program the first 48 functions are no-ops, and the 49th (49 being the ASCII code for '1') is an infinite loop of printing character '1'.

Cat program

>,.<@/+@

Doesn't handle end of file. The implementation below doesn't either, though.

Syntactic sugar

To avoid "flooding" a program with the symbol /, the body of a function may be preceded by the function number. The function number must be written in octal; using this notation, the truth-machine above would become:

61.@/,.@

Implementations

Ocaml

Note: This interpreter supports brainfunct-slash only.

let tape_size = 100;; (* ideally the tape would be unbounded *)
let n_functions = 260;; (* ideally the number of functions would be unbounded *)
let tape = Array.make tape_size 0;;
let ptr = ref 0;;
let f = Array.make n_functions "";;

let make_functions s =
  let i = ref 0 and j = ref 1 and l = String.length s in
  for k = 0 to (l - 1) do
    if s.[k] = '/' then
      begin
      f.(!j) <- String.sub s !i (k - !i);
      i := k+1; incr j
      end
  done;
  f.(0) <- String.sub s !i (l - !i);;

exception Unknown_command of char;;

let rec mtch c =
  match c with
  | '>' -> incr ptr
  | '<' -> decr ptr
  | '+' -> tape.(!ptr) <- succ tape.(!ptr)
  | '-' -> tape.(!ptr) <- pred tape.(!ptr)
  | '.' -> print_char (char_of_int tape.(!ptr)); flush(stdout)
  | ',' -> tape.(!ptr) <- (try int_of_char (input_char stdin) with | End_of_file -> -1)
  | '@' -> interpret tape.(!ptr) false
  | x -> raise (Unknown_command x)
and interpret fn first_time =
  if (fn > 0 || first_time) then
    let l = String.length f.(fn) - 2
    in
    for k = 0 to l do
      mtch f.(fn).[k] done;
    mtch f.(fn).[l + 1];;

make_functions Sys.argv.(1);;

interpret 0 true;;

Haskell

Supports both slashes and parentheses, including in weird nested combinations, essentially desugaring the former to the latter. Does not support the function number syntactic sugar.

import Control.Monad.State (StateT, evalStateT, gets, liftIO, modify)
import Data.Functor ((<$>), (<$))
import System.Environment (getArgs)
import System.IO (hFlush, hIsEOF, hPutStr, stderr, stdin, stdout)
import Text.Parsec (Parsec, between, char, choice, eof, many, parse, sepBy1, spaces, (<|>))

data Tape = Tape { left, right :: [Int] }
data CmdOrFun = Cmd (FunList -> BF) | Fun (FunList -> BF)
type BF = StateT Tape IO ()
type FunList = [BF]

op :: Char -> Parsec [Char] () ()
op c = char c >> spaces

cmdOrSubfun :: Parsec [Char] () CmdOrFun
cmdOrSubfun =
    Fun <$> between (op '(') (op ')') function <|>
    Cmd (\funList -> findFun funList =<< getCurrent)                                        <$ op '@' <|>
    Cmd . const <$> choice [
        modify (\(Tape ls (cur:rs)) -> Tape (cur:ls) rs)                                    <$ op '>',
        modify (\(Tape (l:ls) rs) -> Tape ls (l:rs))                                        <$ op '<',
        modify (\(Tape ls (cur:rs)) -> Tape ls (cur+1:rs))                                  <$ op '+',
        modify (\(Tape ls (cur:rs)) -> Tape ls (cur-1:rs))                                  <$ op '-',
        (getCurrent >>= \c -> liftIO $ putChar (toEnum c) >> hFlush stdout)                 <$ op '.',
        (do c <- liftIO $ do
                e <- hIsEOF stdin
                if e then return (-1) else fromEnum <$> getChar
            modify (\(Tape ls (_:rs)) -> Tape ls (c:rs)))                          <$ op ',' ]
  where
    getCurrent = gets (head . right)
    findFun fs n
      | n > 0, f:_ <- drop (n-1) fs = f
      | otherwise                   = return ()

function :: Parsec [Char] () (FunList -> BF)
function = makeFunction . unslash <$> sepBy1 (many cmdOrSubfun) (op '/')
  where
    unslash blocks = map (Fun . makeFunction) (init blocks) ++ last blocks
    makeFunction cfs funList = sequence_ [cmd funList' | Cmd cmd <- cfs]
      where
        funList' = funList ++ [fun funList' | Fun fun <- cfs]

runBF :: String -> String -> IO ()
runBF sourceName s = case parse (between spaces eof function) sourceName s of
    Left err    -> hPutStr stderr $ "Parse error: " ++ show err
    Right f     -> evalStateT (f []) $ Tape (repeat 0) (repeat 0)

main = runBF "command line" . unwords =<< getArgs

See also

  • pbrain, another brainfuck derivative based on the same idea
  • BFI, also a functional brainfuck derivative, which uses an extra "routine pointer" for function calls