Initial commit
This commit is contained in:
commit
36d7774512
|
@ -0,0 +1,2 @@
|
||||||
|
/dist-newstyle
|
||||||
|
/result
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Revision history for bf-repl
|
||||||
|
|
||||||
|
## 0.1.0.0 -- YYYY-mm-dd
|
||||||
|
|
||||||
|
* First version. Released on an unsuspecting world.
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 h7x4 <h7x4@nani.wtf>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,13 @@
|
||||||
|
[![built with nix](https://builtwithnix.org/badge.svg)](https://builtwithnix.org)
|
||||||
|
|
||||||
|
# bf-repl
|
||||||
|
|
||||||
|
A modern REPL for BrainFuck, written in Haskell
|
||||||
|
|
||||||
|
This REPL will let you:
|
||||||
|
|
||||||
|
- [ ] Run bf commands interactively
|
||||||
|
- [ ] Execute bf scripts
|
||||||
|
- [X] Inspect the stack
|
||||||
|
- [ ] Create and save macros
|
||||||
|
- [ ] Format and color highlight bf code
|
|
@ -0,0 +1,42 @@
|
||||||
|
cabal-version: 2.4
|
||||||
|
name: bf-repl
|
||||||
|
version: 0.1.0.0
|
||||||
|
|
||||||
|
synopsis: A modern REPL for BrainFuck, written in Haskell
|
||||||
|
|
||||||
|
homepage: https://git.nani.wtf/h7x4/bf-repl
|
||||||
|
bug-reports: https://git.nani.wtf/h7x4/bf-repl/issues
|
||||||
|
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
|
||||||
|
author: h7x4 <h7x4@nani.wtf>
|
||||||
|
maintainer: h7x4 <h7x4@nani.wtf>
|
||||||
|
|
||||||
|
category: Language,
|
||||||
|
extra-source-files: CHANGELOG.md
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: git://git.nani.wtf/h7x4/bf-repl.git
|
||||||
|
|
||||||
|
executable bf-repl
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules:
|
||||||
|
Base
|
||||||
|
, Evaluate
|
||||||
|
, Formatter
|
||||||
|
, Memory
|
||||||
|
, Parser
|
||||||
|
, REPL
|
||||||
|
|
||||||
|
other-extensions:
|
||||||
|
OverloadedStrings
|
||||||
|
, NamedFieldPuns
|
||||||
|
build-depends:
|
||||||
|
base ^>= 4.16.3.0
|
||||||
|
, hashmap ^>= 1.3.3
|
||||||
|
, text ^>= 2.0.0
|
||||||
|
, repline ^>= 0.4.2
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1663587673,
|
||||||
|
"narHash": "sha256-4C4R/PV8+HjkgVd1Db8AuvHwhQp5vllVqOQEl6YDh3o=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "20dc478985d6545df53f0153f4af125eb014083d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-22.05",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
description = "My haskell project";
|
||||||
|
|
||||||
|
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }: let
|
||||||
|
packageName = "bf-repl";
|
||||||
|
compiler = "ghc924";
|
||||||
|
|
||||||
|
supportedSystems = [ "x86_64-linux" "x86_64-darwin" ];
|
||||||
|
|
||||||
|
haskellOverlay = (final: prev: {
|
||||||
|
haskellPackages = let
|
||||||
|
hpkgs = prev.haskell.packages.${compiler};
|
||||||
|
inherit (prev.lib.trivial) flip pipe;
|
||||||
|
inherit (prev.haskell.lib)
|
||||||
|
appendPatch
|
||||||
|
appendConfigureFlags
|
||||||
|
dontCheck
|
||||||
|
dontHaddock
|
||||||
|
doJailbreak;
|
||||||
|
in hpkgs.override {
|
||||||
|
overrides = hpFinal: hpPrev: {
|
||||||
|
Cabal = hpPrev.Cabal_3_6_3_0;
|
||||||
|
text = hpPrev.text_2_0;
|
||||||
|
parsec = hpPrev.parsec_3_1_15_1;
|
||||||
|
${packageName} = doJailbreak (hpkgs.callCabal2nix packageName ./. { });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
pkgsForAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ haskellOverlay ];
|
||||||
|
};
|
||||||
|
in f system pkgs);
|
||||||
|
|
||||||
|
in {
|
||||||
|
packages = pkgsForAllSystems (system: pkgs: {
|
||||||
|
${packageName} = pkgs.haskellPackages.${packageName};
|
||||||
|
default = self.packages.${system}.${packageName};
|
||||||
|
});
|
||||||
|
|
||||||
|
devShells = pkgsForAllSystems (system: pkgs: {
|
||||||
|
default = pkgs.haskellPackages.shellFor {
|
||||||
|
packages = p: [ p.${packageName} ];
|
||||||
|
withHoogle = false;
|
||||||
|
buildInputs = with pkgs.haskellPackages; [
|
||||||
|
cabal-install
|
||||||
|
ghcid
|
||||||
|
haskell-language-server
|
||||||
|
hlint
|
||||||
|
];
|
||||||
|
shellHook = "export PS1='\\e[1;34m[nix] ${packageName}> \\e[0m'";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
apps = pkgsForAllSystems (system: pkgs: {
|
||||||
|
${packageName} = {
|
||||||
|
program = "${self.packages.${system}.${packageName}}/bin/${packageName}";
|
||||||
|
type = "app";
|
||||||
|
};
|
||||||
|
default = self.apps.${system}.${packageName};
|
||||||
|
});
|
||||||
|
|
||||||
|
hydraJobs = {
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
module Base where
|
||||||
|
|
||||||
|
import qualified Data.HashMap as HM
|
||||||
|
|
||||||
|
data BFAction = MoveRight
|
||||||
|
| MoveLeft
|
||||||
|
| Increment
|
||||||
|
| Decrement
|
||||||
|
| Replace
|
||||||
|
| Print
|
||||||
|
| JumpRight
|
||||||
|
| JumpLeft
|
||||||
|
|
||||||
|
type Address = Int
|
||||||
|
type CodePosition = Int
|
||||||
|
|
||||||
|
type Memory = HM.Map Address Int
|
||||||
|
type JumpTable = HM.Map CodePosition CodePosition
|
||||||
|
|
||||||
|
data State = State { memory :: Memory
|
||||||
|
, pointer :: Int
|
||||||
|
, codePos :: Int
|
||||||
|
, jumpTable :: JumpTable
|
||||||
|
}
|
||||||
|
deriving (Show, Read, Eq)
|
||||||
|
|
||||||
|
initState :: State
|
||||||
|
initState = State { memory = HM.empty
|
||||||
|
, pointer = 0
|
||||||
|
, codePos = 0
|
||||||
|
, jumpTable = HM.empty
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings, NamedFieldPuns #-}
|
||||||
|
|
||||||
|
module Evaluate where
|
||||||
|
|
||||||
|
import Base (State(..), BFAction(..))
|
||||||
|
import Memory
|
||||||
|
import Formatter
|
||||||
|
|
||||||
|
import qualified Data.HashMap as HM
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
|
|
||||||
|
type ActionResult = (State, Maybe (IO ()))
|
||||||
|
|
||||||
|
processAction :: State -> BFAction -> ActionResult
|
||||||
|
processAction s@State {pointer, memory, codePos, jumpTable} instruction = let
|
||||||
|
noIO :: State -> ActionResult
|
||||||
|
noIO s = (s, Nothing)
|
||||||
|
|
||||||
|
jump :: ActionResult
|
||||||
|
jump = case HM.lookup codePos jumpTable of
|
||||||
|
Just jumpTo -> noIO $ s { pointer = jumpTo }
|
||||||
|
Nothing -> (s, Just $ T.putStrLn "ERROR: no matching jump point")
|
||||||
|
in case instruction of
|
||||||
|
MoveRight -> noIO $ s { pointer = pointer + 1 }
|
||||||
|
MoveLeft -> noIO $ s { pointer = pointer - 1 }
|
||||||
|
Increment -> noIO $ s { memory = increment memory pointer }
|
||||||
|
Decrement -> noIO $ s { memory = decrement memory pointer }
|
||||||
|
Replace -> noIO $ s { memory = setMemAdr memory pointer $ getMemAdr memory $ getMemAdr memory pointer }
|
||||||
|
Print -> (s, Just $ prettyPrintState' s)
|
||||||
|
JumpRight -> jump
|
||||||
|
JumpLeft -> jump
|
|
@ -0,0 +1,102 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings, NamedFieldPuns #-}
|
||||||
|
|
||||||
|
module Formatter (
|
||||||
|
prettyFormatState
|
||||||
|
, prettyFormatState'
|
||||||
|
, prettyPrintState
|
||||||
|
, prettyPrintState'
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Base (State(..), Address)
|
||||||
|
import Memory (getMemAdr)
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
|
import Data.String (fromString)
|
||||||
|
|
||||||
|
data PrinterConfig = PrinterConfig { cellCountHorizontal :: Int
|
||||||
|
, cellRowsAbove :: Int
|
||||||
|
, cellRowsBelow :: Int
|
||||||
|
}
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
|
defaultPrinterConfig = PrinterConfig { cellCountHorizontal = 7
|
||||||
|
, cellRowsAbove = 0
|
||||||
|
, cellRowsBelow = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
divideBy2IntoInts :: Bool -> Int -> (Int, Int)
|
||||||
|
divideBy2IntoInts preferLeft n = if preferLeft then (ceiling x, floor x) else (floor x, ceiling x)
|
||||||
|
where
|
||||||
|
x = fromIntegral n / 2.0
|
||||||
|
|
||||||
|
createSideCell :: Bool -> Address -> Int -> T.Text
|
||||||
|
createSideCell isLeft addr value = fromString str
|
||||||
|
where
|
||||||
|
topLength = length $ show addr
|
||||||
|
innerLength = length $ show value
|
||||||
|
len = max topLength innerLength
|
||||||
|
|
||||||
|
topLen2 = divideBy2IntoInts True (len - topLength)
|
||||||
|
fstTopSpc = replicate (fst topLen2) '─'
|
||||||
|
sndTopSpc = replicate (snd topLen2) '─'
|
||||||
|
|
||||||
|
innerLen2 = divideBy2IntoInts True (len - innerLength)
|
||||||
|
fstInnerSpc = replicate (fst innerLen2) ' '
|
||||||
|
sndInnerSpc = replicate (snd innerLen2) ' '
|
||||||
|
|
||||||
|
l x = if isLeft then x else ""
|
||||||
|
r x = if isLeft then "" else x
|
||||||
|
|
||||||
|
str = l "┬" ++ fstTopSpc ++ "[" ++ show addr ++ "]" ++ sndTopSpc ++ r "┬" ++ "\n"
|
||||||
|
++ l "│" ++ " " ++ fstInnerSpc ++ show value ++ sndInnerSpc ++ " " ++ r "│" ++ "\n"
|
||||||
|
++ l "┴" ++ replicate (len + 2) '─' ++ r "┴"
|
||||||
|
|
||||||
|
createLeftCell = createSideCell True
|
||||||
|
createRightCell = createSideCell False
|
||||||
|
|
||||||
|
createMidCell :: Address -> Int -> T.Text
|
||||||
|
createMidCell addr value = fromString str
|
||||||
|
where
|
||||||
|
topLength = length $ show addr
|
||||||
|
innerLength = length $ show value
|
||||||
|
len = max topLength innerLength
|
||||||
|
|
||||||
|
topLen2 = divideBy2IntoInts True (len - topLength)
|
||||||
|
fstTopSpc = replicate (fst topLen2) '─'
|
||||||
|
sndTopSpc = replicate (snd topLen2) '─'
|
||||||
|
|
||||||
|
innerLen2 = divideBy2IntoInts True (len - innerLength)
|
||||||
|
fstInnerSpc = replicate (fst innerLen2) ' '
|
||||||
|
sndInnerSpc = replicate (snd innerLen2) ' '
|
||||||
|
|
||||||
|
str = "╦" ++ fstTopSpc ++ "[" ++ show addr ++ "]" ++ sndTopSpc ++ "╦\n"
|
||||||
|
++ "║ " ++ fstInnerSpc ++ show value ++ sndInnerSpc ++ " ║\n"
|
||||||
|
++ "╩" ++ replicate (len + 2) '═' ++ "╩"
|
||||||
|
|
||||||
|
mergeTextBlocks :: T.Text -> T.Text -> T.Text
|
||||||
|
mergeTextBlocks a b = T.unlines $ zipWith T.append (T.lines a) (T.lines b)
|
||||||
|
|
||||||
|
prettyFormatState :: PrinterConfig -> State -> T.Text
|
||||||
|
prettyFormatState
|
||||||
|
c@PrinterConfig { cellCountHorizontal, cellRowsAbove, cellRowsBelow }
|
||||||
|
s@State {memory, codePos}
|
||||||
|
= foldl1 mergeTextBlocks cells
|
||||||
|
where
|
||||||
|
cellsAround = divideBy2IntoInts False (cellCountHorizontal - 1)
|
||||||
|
|
||||||
|
fetchCellWithOffset offset = getMemAdr memory $ codePos + offset
|
||||||
|
|
||||||
|
cells :: [T.Text]
|
||||||
|
cells = map (\x -> createLeftCell (codePos + x) (fetchCellWithOffset x)) [(negate (fst cellsAround))..(-1)]
|
||||||
|
++ [createMidCell codePos (fetchCellWithOffset 0) ]
|
||||||
|
++ map (\x -> createRightCell (codePos + x) (fetchCellWithOffset x)) [(snd cellsAround)..3]
|
||||||
|
|
||||||
|
prettyFormatState' :: State -> T.Text
|
||||||
|
prettyFormatState' = prettyFormatState defaultPrinterConfig
|
||||||
|
|
||||||
|
prettyPrintState :: PrinterConfig -> State -> IO ()
|
||||||
|
prettyPrintState c s = T.putStrLn $ prettyFormatState c s
|
||||||
|
|
||||||
|
prettyPrintState' :: State -> IO ()
|
||||||
|
prettyPrintState' s = T.putStrLn $ prettyFormatState' s
|
|
@ -0,0 +1,17 @@
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Base
|
||||||
|
import Evaluate
|
||||||
|
import Formatter
|
||||||
|
|
||||||
|
import Text.Printf
|
||||||
|
import Data.String (fromString)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = prettyPrintState' s
|
||||||
|
where
|
||||||
|
s :: State
|
||||||
|
s = foldl f initState [Increment, Increment, Increment, MoveRight, Increment]
|
||||||
|
|
||||||
|
f :: State -> BFAction -> State
|
||||||
|
f state action = fst (processAction state action)
|
|
@ -0,0 +1,25 @@
|
||||||
|
module Memory where
|
||||||
|
|
||||||
|
import Base (Memory)
|
||||||
|
|
||||||
|
import qualified Data.HashMap as HM
|
||||||
|
|
||||||
|
getMemAdr :: Memory -> Int -> Int
|
||||||
|
getMemAdr memory adr = HM.findWithDefault 0 adr memory
|
||||||
|
|
||||||
|
setMemAdr :: Memory -> Int -> Int -> Memory
|
||||||
|
setMemAdr memory adr val = HM.insert adr val memory
|
||||||
|
|
||||||
|
increment :: Memory -> Int -> Memory
|
||||||
|
increment memory adr = HM.alter f adr memory
|
||||||
|
where
|
||||||
|
f :: Maybe Int -> Maybe Int
|
||||||
|
f Nothing = Just 1
|
||||||
|
f (Just i) = Just $ i + 1
|
||||||
|
|
||||||
|
decrement :: Memory -> Int -> Memory
|
||||||
|
decrement memory adr = HM.alter f adr memory
|
||||||
|
where
|
||||||
|
f :: Maybe Int -> Maybe Int
|
||||||
|
f Nothing = Just $ -1
|
||||||
|
f (Just i) = Just $ i - 1
|
|
@ -0,0 +1,82 @@
|
||||||
|
module Parser where
|
||||||
|
|
||||||
|
import Base
|
||||||
|
|
||||||
|
import Data.String (fromString)
|
||||||
|
import Control.Applicative
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
data ASTNode = MoveRight
|
||||||
|
| MoveLeft
|
||||||
|
| Increment
|
||||||
|
| Decrement
|
||||||
|
| Replace
|
||||||
|
| Print
|
||||||
|
| Loop ASTNode
|
||||||
|
|
||||||
|
flattenAST :: [ASTNode] -> [BFAction]
|
||||||
|
flattenAST = concatMap f
|
||||||
|
where
|
||||||
|
f (Loop x) = [JumpLeft] ++ process x ++ [JumpRight]
|
||||||
|
f x = [x]
|
||||||
|
|
||||||
|
data ParserError
|
||||||
|
= EndOfInput
|
||||||
|
| UnexpectedSymbol Char
|
||||||
|
| Empty
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
newtype Parser a = Parser
|
||||||
|
{ runParser :: T.Text -> Either [ParserError] (a, T.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Functor Parser where
|
||||||
|
fmap f (Parser x) = Parser run
|
||||||
|
where
|
||||||
|
run input = case x input of
|
||||||
|
Left err -> Left err
|
||||||
|
Right (out, res) -> Right (f out, rest)
|
||||||
|
|
||||||
|
instance Applicative Parser where
|
||||||
|
pure a = Parser $ \input -> Right (a, input)
|
||||||
|
Parser x <*> Parser y = Parser run
|
||||||
|
where
|
||||||
|
run input = case x input of
|
||||||
|
Left err -> Left err
|
||||||
|
Right (x', rest) -> case y rest of
|
||||||
|
Left err -> Left err
|
||||||
|
Right (y', rest') -> Right (x' y', rest')
|
||||||
|
|
||||||
|
instance Monad Parser where
|
||||||
|
return = pure
|
||||||
|
Parser x >>= y = Parser run
|
||||||
|
where
|
||||||
|
run input = case x input of
|
||||||
|
Left err -> Left err
|
||||||
|
Right (out, rest) ->
|
||||||
|
let Parser x' = y out in x' rest
|
||||||
|
|
||||||
|
instance Alternative Parser where
|
||||||
|
empty = Parser $ \_ -> Left [Empty]
|
||||||
|
Parser x <|> Parser y = Parser run
|
||||||
|
where
|
||||||
|
run input = case x input of
|
||||||
|
Right (out, rest) -> Right (out, rest)
|
||||||
|
Left err -> case y input of
|
||||||
|
Right (out, rest) -> Right (out, rest)
|
||||||
|
Left err' -> Left err <> err'
|
||||||
|
|
||||||
|
satisfy :: (Char -> Bool) -> Parser T.Text
|
||||||
|
satisfy p = Parser run
|
||||||
|
where
|
||||||
|
run "" = Left [EndOfInput]
|
||||||
|
run (x : rest) = if p x then Right (x, rest) else Left [UnexpectedSymbol x]
|
||||||
|
|
||||||
|
char :: Char -> Parser T.Text
|
||||||
|
char c = satisfy (== c)
|
||||||
|
|
||||||
|
string :: T.Text -> Parser T.Text
|
||||||
|
string (x:xs) = do
|
||||||
|
y <- char x
|
||||||
|
ys <- string xs
|
||||||
|
return $ T.append x ys
|
|
@ -0,0 +1 @@
|
||||||
|
module REPL where
|
Loading…
Reference in New Issue