Skip to content

Commit

Permalink
Implement applyRules
Browse files Browse the repository at this point in the history
  • Loading branch information
fizruk committed Jan 11, 2024
1 parent ece7fbd commit cb9d91b
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 94 deletions.
2 changes: 2 additions & 0 deletions eo-phi-normalizer/eo-phi-normalizer.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ library
exposed-modules:
Language.EO.Phi
Language.EO.Phi.Normalize
Language.EO.Phi.Rules.Common
Language.EO.Phi.Syntax
Language.EO.Phi.Syntax.Abs
Language.EO.Phi.Syntax.Lex
Language.EO.Phi.Syntax.Par
Expand Down
83 changes: 2 additions & 81 deletions eo-phi-normalizer/src/Language/EO/Phi.hs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
{-# LANGUAGE LambdaCase #-}

module Language.EO.Phi (
defaultMain,
normalize,
parseProgram,
unsafeParseProgram,
printTree,
module Language.EO.Phi.Syntax.Abs,
module Language.EO.Phi.Syntax,
) where

import Data.Char (isSpace)
import System.Exit (exitFailure)

import Language.EO.Phi.Syntax.Abs
import qualified Language.EO.Phi.Syntax.Abs as Phi
import qualified Language.EO.Phi.Syntax.Par as Phi
import qualified Language.EO.Phi.Syntax.Print as Phi

import Language.EO.Phi.Normalize
import Language.EO.Phi.Syntax

-- | Parse a 'Program' or return a parsing error.
parseProgram :: String -> Either String Phi.Program
Expand Down Expand Up @@ -46,77 +41,3 @@ defaultMain = do
exitFailure
Right program -> do
putStrLn (printTree (normalize program))

-- * Overriding generated pretty-printer

-- | Like 'Phi.printTree', but without spaces around dots and no indentation for curly braces.
printTree :: (Phi.Print a) => a -> String
printTree = shrinkDots . render . Phi.prt 0

-- | Remove spaces around dots.
--
-- >>> putStrLn (shrinkDots "a ↦ ξ . a")
-- a ↦ ξ.a
shrinkDots :: String -> String
shrinkDots [] = []
shrinkDots (' ' : '.' : ' ' : cs) = '.' : shrinkDots cs
shrinkDots (c : cs) = c : shrinkDots cs

-- | Copy of 'Phi.render', except no indentation is made for curly braces.
render :: Phi.Doc -> String
render d = rend 0 False (map ($ "") $ d []) ""
where
rend ::
Int ->
-- \^ Indentation level.
Bool ->
-- \^ Pending indentation to be output before next character?
[String] ->
ShowS
rend i p = \case
"[" : ts -> char '[' . rend i False ts
"(" : ts -> char '(' . rend i False ts
-- "{" :ts -> onNewLine i p . showChar '{' . new (i+1) ts
-- "}" : ";":ts -> onNewLine (i-1) p . showString "};" . new (i-1) ts
-- "}" :ts -> onNewLine (i-1) p . showChar '}' . new (i-1) ts
[";"] -> char ';'
";" : ts -> char ';' . new i ts
t : ts@(s : _)
| closingOrPunctuation s ->
pending . showString t . rend i False ts
t : ts -> pending . space t . rend i False ts
[] -> id
where
-- Output character after pending indentation.
char :: Char -> ShowS
char c = pending . showChar c

-- Output pending indentation.
pending :: ShowS
pending = if p then indent i else id

-- Indentation (spaces) for given indentation level.
indent :: Int -> ShowS
indent i = Phi.replicateS (2 * i) (showChar ' ')

-- Continue rendering in new line with new indentation.
new :: Int -> [String] -> ShowS
new j ts = showChar '\n' . rend j True ts

-- Separate given string from following text by a space (if needed).
space :: String -> ShowS
space t s =
case (all isSpace t, null spc, null rest) of
(True, _, True) -> [] -- remove trailing space
(False, _, True) -> t -- remove trailing space
(False, True, False) -> t ++ ' ' : s -- add space if none
_ -> t ++ s
where
(spc, rest) = span isSpace s

closingOrPunctuation :: String -> Bool
closingOrPunctuation [c] = c `elem` closerOrPunct
closingOrPunctuation _ = False

closerOrPunct :: String
closerOrPunct = ")],;"
8 changes: 1 addition & 7 deletions eo-phi-normalizer/src/Language/EO/Phi/Normalize.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,14 @@ import Numeric (showHex)

import Control.Monad (forM)
import Language.EO.Phi.Syntax.Abs
import Language.EO.Phi.Rules.Common (lookupBinding)

data Context = Context
{ globalObject :: [Binding]
, thisObject :: [Binding]
, totalNuCount :: Int
}

lookupBinding :: Attribute -> [Binding] -> Maybe Object
lookupBinding _ [] = Nothing
lookupBinding a (AlphaBinding a' object : bindings)
| a == a' = Just object
| otherwise = lookupBinding a bindings
lookupBinding _ _ = Nothing

isNu :: Binding -> Bool
isNu (AlphaBinding VTX _) = True
isNu (EmptyBinding VTX) = True
Expand Down
103 changes: 103 additions & 0 deletions eo-phi-normalizer/src/Language/EO/Phi/Rules/Common.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Language.EO.Phi.Rules.Common where

import Language.EO.Phi.Syntax.Abs
import Control.Applicative (asum, Alternative ((<|>)))
import Control.Monad (guard)
import Language.EO.Phi.Syntax

-- $setup
-- >>> :set -XOverloadedStrings

data Context = Context
{ allRules :: [Rule]
}

-- | A rule tries to apply a transformation to the root object, if possible.
type Rule = Context -> Object -> Maybe Object

applyOneRuleAtRoot :: Context -> Object -> [Object]
applyOneRuleAtRoot ctx@Context{..} obj =
[ obj'
| rule <- allRules
, Just obj' <- [rule ctx obj]
]

withSubObject :: (Object -> [Object]) -> Object -> [Object]
withSubObject f root = f root <|>
case root of
Formation bindings ->
Formation <$> withSubObjectBindings f bindings
Application obj bindings -> asum
[ Application <$> withSubObject f obj <*> pure bindings
, Application obj <$> withSubObjectBindings f bindings
]
ObjectDispatch obj a -> ObjectDispatch <$> withSubObject f obj <*> pure a
GlobalDispatch{} -> []
ThisDispatch{} -> []
Termination -> []

withSubObjectBindings :: (Object -> [Object]) -> [Binding] -> [[Binding]]
withSubObjectBindings _ [] = []
withSubObjectBindings f (b:bs) = asum
[ [ b' : bs | b' <- withSubObjectBinding f b ]
, [ b : bs' | bs' <- withSubObjectBindings f bs ]
]

withSubObjectBinding :: (Object -> [Object]) -> Binding -> [Binding]
withSubObjectBinding f = \case
AlphaBinding a obj -> AlphaBinding a <$> withSubObject f obj
EmptyBinding{} -> []
DeltaBinding{} -> []
LambdaBinding{} -> []

applyOneRule :: Context -> Object -> [Object]
applyOneRule = withSubObject . applyOneRuleAtRoot

isNF :: Context -> Object -> Bool
isNF ctx = null . applyOneRule ctx

-- | Apply rules until we get a normal form.
--
-- >>> mapM_ (putStrLn . Language.EO.Phi.printTree) (applyRules (Context [rule6]) "⟦ a ↦ ⟦ b ↦ ⟦ ⟧ ⟧.b ⟧.a")
-- ⟦ ⟧ (ρ ↦ ⟦ ⟧) (ρ ↦ ⟦ ⟧)
applyRules :: Context -> Object -> [Object]
applyRules ctx obj
| isNF ctx obj = [obj]
| otherwise =
[ obj''
| obj' <- applyOneRule ctx obj
, obj'' <- applyRules ctx obj' ]

applyRulesChain :: Context -> Object -> [[Object]]
applyRulesChain ctx obj
| isNF ctx obj = [[obj]]
| otherwise =
[ obj : chain
| obj' <- applyOneRule ctx obj
, chain <- applyRulesChain ctx obj' ]

-- * Yegor's Rules

-- | Rule 6.
rule6 :: Rule
rule6 ctx (ObjectDispatch (Formation bindings) a)
| Just obj <- lookupBinding a bindings = do
guard (isNF ctx obj)
return (Application obj [AlphaBinding Rho (Formation bindings')])
where
bindings' = filter (not . isDispatched) bindings
isDispatched (AlphaBinding a' _) = a == a'
isDispatched _ = False
rule6 _ _ = Nothing

-- * Helpers

-- | Lookup a binding by the attribute name.
lookupBinding :: Attribute -> [Binding] -> Maybe Object
lookupBinding _ [] = Nothing
lookupBinding a (AlphaBinding a' object : bindings)
| a == a' = Just object
| otherwise = lookupBinding a bindings
lookupBinding _ _ = Nothing
100 changes: 100 additions & 0 deletions eo-phi-normalizer/src/Language/EO/Phi/Syntax.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE LambdaCase #-}
module Language.EO.Phi.Syntax where

import Data.Char (isSpace)
import qualified Language.EO.Phi.Syntax.Abs as Phi
import qualified Language.EO.Phi.Syntax.Par as Phi
import qualified Language.EO.Phi.Syntax.Print as Phi
import Data.String (IsString(..))

instance IsString Phi.Object where
fromString = unsafeParseObject

-- | Parse a 'Object' or return a parsing error.
parseObject :: String -> Either String Phi.Object
parseObject input = Phi.pObject tokens
where
tokens = Phi.myLexer input

-- | Parse a 'Object' from a 'String'.
-- May throw an 'error` if input has a syntactical or lexical errors.
unsafeParseObject :: String -> Phi.Object
unsafeParseObject input =
case parseObject input of
Left parseError -> error parseError
Right object -> object

-- * Overriding generated pretty-printer

-- | Like 'Phi.printTree', but without spaces around dots and no indentation for curly braces.
printTree :: (Phi.Print a) => a -> String
printTree = shrinkDots . render . Phi.prt 0

-- | Remove spaces around dots.
--
-- >>> putStrLn (shrinkDots "a ↦ ξ . a")
-- a ↦ ξ.a
shrinkDots :: String -> String
shrinkDots [] = []
shrinkDots (' ' : '.' : ' ' : cs) = '.' : shrinkDots cs
shrinkDots (c : cs) = c : shrinkDots cs

-- | Copy of 'Phi.render', except no indentation is made for curly braces.
render :: Phi.Doc -> String
render d = rend 0 False (map ($ "") $ d []) ""
where
rend ::
Int ->
-- \^ Indentation level.
Bool ->
-- \^ Pending indentation to be output before next character?
[String] ->
ShowS
rend i p = \case
"[" : ts -> char '[' . rend i False ts
"(" : ts -> char '(' . rend i False ts
-- "{" :ts -> onNewLine i p . showChar '{' . new (i+1) ts
-- "}" : ";":ts -> onNewLine (i-1) p . showString "};" . new (i-1) ts
-- "}" :ts -> onNewLine (i-1) p . showChar '}' . new (i-1) ts
[";"] -> char ';'
";" : ts -> char ';' . new i ts
t : ts@(s : _)
| closingOrPunctuation s ->
pending . showString t . rend i False ts
t : ts -> pending . space t . rend i False ts
[] -> id
where
-- Output character after pending indentation.
char :: Char -> ShowS
char c = pending . showChar c

-- Output pending indentation.
pending :: ShowS
pending = if p then indent i else id

-- Indentation (spaces) for given indentation level.
indent :: Int -> ShowS
indent i = Phi.replicateS (2 * i) (showChar ' ')

-- Continue rendering in new line with new indentation.
new :: Int -> [String] -> ShowS
new j ts = showChar '\n' . rend j True ts

-- Separate given string from following text by a space (if needed).
space :: String -> ShowS
space t s =
case (all isSpace t, null spc, null rest) of
(True, _, True) -> [] -- remove trailing space
(False, _, True) -> t -- remove trailing space
(False, True, False) -> t ++ ' ' : s -- add space if none
_ -> t ++ s
where
(spc, rest) = span isSpace s

closingOrPunctuation :: String -> Bool
closingOrPunctuation [c] = c `elem` closerOrPunct
closingOrPunctuation _ = False

closerOrPunct :: String
closerOrPunct = ")],;"
4 changes: 2 additions & 2 deletions eo-phi-normalizer/src/Language/EO/Phi/Syntax/Doc.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions eo-phi-normalizer/src/Language/EO/Phi/Syntax/Par.y

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions eo-phi-normalizer/src/Language/EO/Phi/Syntax/Print.hs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 comment on commit cb9d91b

@0pdd
Copy link

@0pdd 0pdd commented on cb9d91b Jan 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to retrieve PDD puzzles from the code base and submit them to github. If you think that it's a bug on our side, please submit it to yegor256/0pdd:

set -x && set -e && set -o pipefail && cd /tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0 && pdd -v -f /tmp/20240111-1633627-t6tczl [1]: + set -e + set -o pipefail + cd /tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0 + pdd...

Please, copy and paste this stack trace to GitHub:

UserError
set -x && set -e && set -o pipefail && cd /tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0 && pdd -v -f /tmp/20240111-1633627-t6tczl [1]:
+ set -e
+ set -o pipefail
+ cd /tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0
+ pdd -v -f /tmp/20240111-1633627-t6tczl

My version is 0.23.2
Ruby version is 3.1.4 at x86_64-linux
Reading from root dir /tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0
/tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0/.vscode/settings.json is a binary file (48 bytes)
/tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0/flake.lock is a binary file (1494 bytes)
/tmp/0pdd20240111-14-pm6785/Z2l0QGdpdGh1Yi5jb206b2JqZWN0aW9uYXJ5L25vcm1hbGl6ZXIuZ2l0/renovate.json is a binary file (114 bytes)
Reading .envrc ...
Reading .gitattributes ...
Reading .github/workflows/ghc.yml ...
Reading .gitignore ...
Reading README.md ...
Reading cabal.project ...
Reading eo-phi-normalizer/.gitignore ...
Reading eo-phi-normalizer/CHANGELOG.md ...
Reading eo-phi-normalizer/LICENSE ...
Reading eo-phi-normalizer/README.md ...
Reading eo-phi-normalizer/Setup.hs ...
ERROR: ERROR: eo-phi-normalizer/Setup.hs; PDD::Error at eo-phi-normalizer/Setup.hs:28: TODO found, but puzzle can't be parsed, most probably because TODO is not followed by a puzzle marker, as this page explains: https://github.com/cqfn/pdd#how-to-format
If you can't understand the cause of this issue or you don't know how to fix it, please submit a GitHub issue, we will try to help you: https://github.com/cqfn/pdd/issues. This tool is still in its beta version and we will appreciate your feedback. Here is where you can find more documentation: https://github.com/cqfn/pdd/blob/master/README.md.
Exit code is 1

/app/objects/git_repo.rb:73:in `rescue in block in xml'
/app/objects/git_repo.rb:70:in `block in xml'
/app/vendor/ruby-3.1.4/lib/ruby/3.1.0/tempfile.rb:317:in `open'
/app/objects/git_repo.rb:69:in `xml'
/app/objects/puzzles.rb:41:in `deploy'
/app/objects/jobs/job.rb:38:in `proceed'
/app/objects/jobs/job_starred.rb:32:in `proceed'
/app/objects/jobs/job_recorded.rb:31:in `proceed'
/app/objects/jobs/job_emailed.rb:33:in `proceed'
/app/objects/jobs/job_commiterrors.rb:33:in `proceed'
/app/objects/jobs/job_detached.rb:48:in `exclusive'
/app/objects/jobs/job_detached.rb:36:in `block in proceed'
/app/objects/jobs/job_detached.rb:36:in `fork'
/app/objects/jobs/job_detached.rb:36:in `proceed'
/app/0pdd.rb:531:in `process_request'
/app/0pdd.rb:367:in `block in <top (required)>'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1706:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1706:in `block in compile!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1019:in `block (3 levels) in route!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1037:in `route_eval'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1019:in `block (2 levels) in route!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1068:in `block in process_route'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1066:in `catch'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1066:in `process_route'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1017:in `block in route!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1014:in `each'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1014:in `route!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1138:in `block in dispatch!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1109:in `catch'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1109:in `invoke'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1133:in `dispatch!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:949:in `block in call!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1109:in `catch'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1109:in `invoke'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:949:in `call!'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:938:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.8/lib/rack/deflater.rb:44:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-protection-3.0.6/lib/rack/protection/xss_header.rb:20:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-protection-3.0.6/lib/rack/protection/path_traversal.rb:18:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-protection-3.0.6/lib/rack/protection/json_csrf.rb:28:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-protection-3.0.6/lib/rack/protection/base.rb:53:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-protection-3.0.6/lib/rack/protection/base.rb:53:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-protection-3.0.6/lib/rack/protection/frame_options.rb:33:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.8/lib/rack/logger.rb:17:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.8/lib/rack/common_logger.rb:38:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:261:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:254:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.8/lib/rack/head.rb:12:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.8/lib/rack/method_override.rb:24:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:219:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:2018:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1576:in `block in call'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1792:in `synchronize'
/app/vendor/bundle/ruby/3.1.0/gems/sinatra-3.0.6/lib/sinatra/base.rb:1576:in `call'
/app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.8/lib/rack/handler/webrick.rb:95:in `service'
/app/vendor/bundle/ruby/3.1.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:140:in `service'
/app/vendor/bundle/ruby/3.1.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:96:in `run'
/app/vendor/bundle/ruby/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:310:in `block in start_thread'

Please sign in to comment.