sindre 0.2 → 0.3
raw patch · 12 files changed
+320/−74 lines, 12 filesdep +attoparsecdep −attoparsec-textsetup-changed
Dependencies added: attoparsec
Dependencies removed: attoparsec-text
Files
- Setup.hs +14/−19
- Sindre/Formatting.hs +1/−1
- Sindre/KeyVal.hs +1/−0
- Sindre/Lib.hs +22/−9
- Sindre/Main.hs +4/−5
- Sindre/Parser.hs +9/−0
- Sindre/Widgets.hs +52/−1
- Sindre/X11.hs +5/−15
- sindre.1 +201/−16
- sindre.cabal +3/−3
- sinmenu +2/−3
- sinmenu.1 +6/−2
Setup.hs view
@@ -5,28 +5,15 @@ defaultRegisterFlags,fromFlagOrDefault,Flag(..), defaultCopyFlags) import Distribution.Simple-import Distribution.Simple.LocalBuildInfo- (LocalBuildInfo(..),absoluteInstallDirs)-import Distribution.Simple.Configure (configCompilerAux)+import Distribution.Simple.LocalBuildInfo (absoluteInstallDirs) import Distribution.PackageDescription (PackageDescription(..)) import Distribution.Simple.InstallDirs (InstallDirs(..))-import Distribution.Simple.Program - (Program(..),ConfiguredProgram(..),ProgramConfiguration(..),- ProgramLocation(..),simpleProgram,lookupProgram,- rawSystemProgramConf) import Distribution.Simple.Utils import Distribution.Verbosity-import Data.Char (isSpace, showLitChar)-import Data.List (isSuffixOf,isPrefixOf)-import Data.Maybe (listToMaybe,isJust)+import Data.List (isPrefixOf) import Data.Version-import Control.Monad (when,unless)-import Text.ParserCombinators.ReadP (readP_to_S)-import System.Exit-import System.IO (hGetContents,hClose,hPutStr,stderr)-import System.IO.Error (try)-import System.Process (runInteractiveProcess,waitForProcess)+import Control.Monad (unless) import System.Posix import System.Directory import System.Info (os)@@ -41,6 +28,12 @@ isWindows :: Bool isWindows = os == "mingw" -- XXX +subst :: String -> String -> String -> String+subst t r [] = []+subst t r s@(c:s')+ | t `isPrefixOf` s = r ++ subst t r (drop (length t) s)+ | otherwise = c : subst t r s'+ sindrePostInst a (InstallFlags { installPackageDB = db, installVerbosity = v }) = sindrePostCopy a (defaultCopyFlags { copyDest = Flag NoCopyDest, copyVerbosity = v }) @@ -49,12 +42,14 @@ cd = fromFlagOrDefault NoCopyDest cdf dirs = absoluteInstallDirs pd lbi cd bin = combine (bindir dirs)+ substVersion = subst "VERSION" $ showVersion $ packageVersion $ package pd unless isWindows $ do copyFileVerbose v "sinmenu" (bin "sinmenu") fs <- getFileStatus (bin "sindre") setFileMode (bin "sinmenu") $ fileMode fs putStrLn $ "Installing manpage in " ++ mandir dirs createDirectoryIfMissing True $ mandir dirs `combine` "man1"- copyFileVerbose v "sindre.1" (mandir dirs `combine` "man1" `combine` "sindre.1")- createDirectoryIfMissing True $ mandir dirs `combine` "man1"- copyFileVerbose v "sinmenu.1" (mandir dirs `combine` "man1" `combine` "sinmenu.1")+ withFileContents "sindre.1" $+ writeFileAtomic (mandir dirs `combine` "man1" `combine` "sindre.1") . substVersion+ withFileContents "sinmenu.1" $+ writeFileAtomic (mandir dirs `combine` "man1" `combine` "sinmenu.1") . substVersion
Sindre/Formatting.hs view
@@ -27,7 +27,7 @@ import Data.Attoparsec.Text -import Control.Applicative hiding (many)+import Control.Applicative import Control.Monad import Data.Maybe import qualified Data.Text as T
Sindre/KeyVal.hs view
@@ -34,6 +34,7 @@ import Control.Applicative hiding (many, empty) import Control.Monad.Identity +import Data.Attoparsec.Combinator import Data.Attoparsec.Text import qualified Data.Text as T
Sindre/Lib.hs view
@@ -49,12 +49,21 @@ builtin f = return $ function f -- | A set of pure functions that can work with any Sindre backend.--- Includes the functions @length@, @abs@, @substr@, @index@, @match@,--- @sub@, @gsub@, @tolower@, and @toupper@.+-- Includes the functions @abs@, @atan2@, @cos@, @sin@, @exp@, @log@,+-- @int@, @sqrt@, @length@, @substr@, @index@, @match@, @sub@, @gsub@,+-- @tolower@, and @toupper@. stdFunctions :: forall im. MonadBackend im => FuncMap im stdFunctions = M.fromList- [ ("length", builtin $ return' . lengthFun)- , ("abs" , builtin $ return' . (abs :: Int -> Int))+ [ ("abs" , builtin $ return' . (abs :: Int -> Int))+ , ("atan2", builtin $ \(x::Double) (y::Double) ->+ return' $ atan2 x y)+ , ("cos", builtin $ return' . (cos :: Double -> Double))+ , ("sin", builtin $ return' . (sin :: Double -> Double))+ , ("exp", builtin $ return' . (exp :: Double -> Double))+ , ("log", builtin $ return' . (log :: Double -> Double))+ , ("int", builtin $ return' . (floor :: Double -> Integer))+ , ("sqrt", builtin $ return' . (sqrt :: Double -> Double))+ , ("length", builtin $ return' . lengthFun) , ("substr", builtin $ \(s::String) m n -> return' $ take n $ drop (m-1) s) , ("index", builtin $ \(s::String) t ->@@ -85,12 +94,16 @@ return' $ take i s ++ t ++ s' -- | A set of impure functions that only work in IO backends. -- Includes the @system@ function.-ioFunctions :: forall im.(MonadIO im, MonadBackend im) => FuncMap im+ioFunctions :: (MonadIO m, MonadBackend m) => FuncMap m ioFunctions = M.fromList- [ ("system", builtin $ \s -> do- c <- io $ system s- case c of ExitSuccess -> return' 0- ExitFailure e -> return' e)+ [ ("system", do+ exitval <- setValue "EXITVAL"+ builtin $ \s -> do+ c <- io $ system s+ let v = case c of ExitSuccess -> 0+ ExitFailure e -> e+ execute_ $ exitval $ unmold v+ return' v) , ("osystem", do exitval <- setValue "EXITVAL" return $ function $ \s -> do
Sindre/Main.hs view
@@ -63,7 +63,7 @@ sindreMain prog cm om fm gm args = do setupLocale dstr <- getEnv "DISPLAY" `catch` \(_ :: IOException) -> (return "")- let cfg = AppConfig { cfgDisplay = dstr + let cfg = AppConfig { cfgDisplay = dstr , cfgProgram = prog , cfgBackend = sindreX11override , cfgFiles = M.empty }@@ -84,10 +84,9 @@ (_, nonopts, unrecs, errs) -> do usage <- usageStr options badOptions usage nonopts errs unrecs- badOptions :: String -> [String] -> [String] -> [String] -> IO ()-badOptions usage nonopts errs unrecs = do +badOptions usage nonopts errs unrecs = do mapM_ (err . ("Junk argument: " ++)) nonopts mapM_ (err . ("Unrecognised argument: " ++)) unrecs hPutStr stderr $ concat errs ++ usage@@ -121,7 +120,7 @@ options = [ Option "f" ["file"] (ReqArg (\arg cfg -> do- result <- parseSindre (cfgProgram cfg) arg <$> readFile arg + result <- parseSindre (cfgProgram cfg) arg <$> readFile arg case result of Left e -> error $ show e Right prog -> return $ cfg { cfgProgram = prog })@@ -175,7 +174,7 @@ , programFunctions = [] , programBegin = [] }- + classMap :: ClassMap SindreX11M classMap = M.fromList [ ("Dial", mkDial) , ("Label", mkLabel)
Sindre/Parser.hs view
@@ -366,7 +366,16 @@ literal :: Parser Value literal = pure Number <*> decimal <|> pure Sindre.string <*> stringLiteral+ <|> boolean+ <|> dict <?> "literal value"++boolean :: Parser Value+boolean = lexeme (string "true") *> return truth+ <|> lexeme (string "false") *> return falsity++dict :: Parser Value+dict = lexeme (string "[]") *> return (Dict M.empty) compound :: Parser (P Expr) compound =
Sindre/Widgets.hs view
@@ -17,9 +17,13 @@ module Sindre.Widgets ( mkHorizontally , mkVertically , changeFields+ , Match(..)+ , match+ , filterMatches+ , sortMatches ) where- + import Sindre.Sindre import Sindre.Compiler import Sindre.Runtime@@ -28,6 +32,10 @@ import Control.Monad.State import Control.Applicative +import Data.List+import Data.Maybe+import qualified Data.Text as T+ data Oriented = Oriented { mergeSpace :: [SpaceNeed] -> SpaceNeed , splitSpace :: Rectangle -> [SpaceNeed] -> [Rectangle]@@ -96,3 +104,46 @@ s <- get s' <- m s put s' >> mapM_ (\(k, f) -> changed k (f s) (f s')) fs++-- | The result of using 'match' to apply a user-provided pattern to a+-- string.+data Match = ExactMatch+ | PrefixMatch+ | InfixMatch+ deriving (Eq, Ord, Show)++-- | @match pat s@ applies the pattern @pat@ to @s@ and returns a+-- 'Match' describing the kind of match if any, or 'Nothing'+-- otherwise. The pattern is interpreted as tokens delimited by+-- whitespace, and each token must be present somewhere in @s@.+match :: T.Text -> T.Text -> Maybe Match+match pat s+ | pat == s = Just ExactMatch+ | otherwise =+ case T.words pat of+ [] -> Just PrefixMatch+ pat'@(x:_) | all look pat' -> if x `T.isPrefixOf` s+ then Just PrefixMatch+ else Just InfixMatch+ | otherwise -> Nothing+ where look tok = tok `T.isInfixOf` s++-- | @filterMatches f pat l@ returns only those elements of @l@ that+-- match @pat@, using @f@ to convert each element to a 'T.Text'. The+-- result will be ordered equivalently to @l@+filterMatches :: (a -> T.Text) -> T.Text -> [a] -> [a]+filterMatches f pat = filter (isJust . match pat . f)++-- | @sortMatches f pat l@ returns only those elements of @l@ that+-- match @pat@, using @f@ to convert each element to a 'T.Text'. The+-- result will be reordered such that exact matches come first, then+-- prefixes, then infixes, although original order will be maintained+-- within these three groups.+sortMatches :: (a -> T.Text) -> T.Text -> [a] -> [a]+sortMatches f t ts = map snd $ exacts++prefixes++infixes+ where attach y = do m <- match t $ f y+ return (m, y)+ matches = mapMaybe attach ts+ (exacts, nonexacts) = partition ((==ExactMatch) . fst) matches+ (prefixes, infixes) =+ partition ((==PrefixMatch) . fst) nonexacts
Sindre/X11.hs view
@@ -952,13 +952,13 @@ Right (v,val) -> case parseFormatString v of Left _ -> el- Right s' -> ListElem (pad s') val (textContents s')+ Right s' -> ListElem (pad s') val $ T.toCaseFold $ textContents s' where p = elf <$?> (Nothing, Just <$> KV.value (T.pack "show")) <||> KV.value (T.pack "value") elf s' v' = (fromMaybe v' s', v') pad s' = maybeToList (Bg <$> startBg s') ++ [Text $ T.pack " "] ++ s' ++ [Text $ T.pack " "]- el = ListElem [Text $ T.concat [T.pack " ", s, T.pack " "]] s s+ el = ListElem [Text $ T.concat [T.pack " ", s, T.pack " "]] s $ T.toCaseFold s data NavList = NavList { linePrev :: [ListElem] , lineContents :: Maybe ([(ListElem, Rectangle)],@@ -1057,16 +1057,8 @@ selection l = maybe falsity f $ lineContents $ listLine l where f (_,(c,_),_) = StringV $ valueOf c -refilter :: (T.Text -> T.Text) -> T.Text -> [ListElem] -> [ListElem]-refilter tr f ts =- case T.words $ tr f of- [] -> ts- f'@(x:_) -> exacts++prefixes++infixes- where matches = filter (\t -> all (flip T.isInfixOf $ cmpBy t) f') ts- (exacts, nonexacts) = partition ((==f) . cmpBy) matches- (prefixes, infixes) =- partition (T.isPrefixOf x . cmpBy) nonexacts- cmpBy = filterBy+refilter :: T.Text -> [ListElem] -> [ListElem]+refilter f = sortMatches filterBy (T.toCaseFold f) methInsert :: T.Text -> ObjectM List SindreX11M () methInsert vs = changeFields [("selected", selection)] $ \s -> do@@ -1141,10 +1133,8 @@ -> Constructor SindreX11M mkList cf df dim uf r [] = do visual <- visualOpts r- insensitive <- param "i" <|> return False- let trf = if insensitive then T.toCaseFold else id return $ NewWidget $ List [] T.empty (NavList [] Nothing [])- visual cf df (refilter trf) uf mempty dim+ visual cf df refilter uf mempty dim mkList _ _ _ _ _ _ = error "Lists do not have children" -- | Horizontal dmenu-style list containing a list of elements, one of
sindre.1 view
@@ -1,4 +1,4 @@-.TH SINDRE 1 sindre\-0.2+.TH SINDRE 1 sindre\-VERSION .SH NAME sindre \- GUI programming language .SH SYNOPSIS@@ -7,11 +7,10 @@ [\fB\-f \fIprogram-file\fR] [\fB\-e \fIprogram-text\fR] .SH DESCRIPTION-.SS Overview Sindre is a programming language inspired by Awk that makes it easy to write simple graphical programs in the spirit of dzen, dmenu, xmobar, gsmenu and the like.-.SS Options+.SH OPTIONS .TP .PD 0 .BI \-f " program-file"@@ -69,7 +68,9 @@ .IR override (the default), grab control of the display and stay on top until the program terminates.+ .SH USAGE+ .SS Lexical conventions Identifiers start with a letter and consist of alphanumerics or underscores. Class names start with a capital letter, while object@@ -77,42 +78,51 @@ with // and block comments with /* ... */. Semicolons are used to separate statements and declarations, although they are optional when not needed to resolve ambiguity.+ .SS Overview The Sindre language is extremely similar to Awk in syntax and semantics, although there are subtle differences as well. A program primarily consists of action declarations that have the form+ .TP .IB pattern " { " statements " } "+ .P When an event arrives, each declaration is checked in order, and those-that match have their statements executed. Some patterns also bind-variables while executing the statements, like a function call. The-statement+whose pattern matches have their statements executed. Some patterns+also bind variables while executing the statements, like a function+call. The statement .B next can be used to immediately stop further processing of an event. Additionally there are a few special declarations. A GUI declaration defines a tree of possibly named widgets, and looks like+ .TP .BI "GUI { " name "=" class "(" parameters ") { " children " } }"+ .P-where both name, parameters and children are optional. Each child-follows the same syntax as the body (the text between the braces) of a-GUI declaration, and should be separated by semicolons. Widget-parameters are of the form+where both name (including the equal sign), parameters (including the+parentheses) and children (including the braces) are optional. Each+child follows the same syntax as the body (the text between the+braces) of a GUI declaration, and should be separated by semicolons.+Widget parameters are of the form+ .P .IB param1 " = " exp1 ", " param2 " = " exp2 ", ... , " paramN " = " expN+ .P and are evaluated left-to-right. A parameter whose value is considered false (see section .BR VALUES )-will be ignored if its value is otherwise not valid for the-paramter. Otherwise, an error will occur if the value is not what the-widget expects (for example, the string "foo" passed as the widget-height).+will be ignored if its value is otherwise not valid for the parameter.+Otherwise, an error will occur if the value is not what the widget+expects (for example, the string "foo" passed as the widget height). .P A global variable declaration looks like+ .TP .IB name = exp+ .P Global variables are initialised before the GUI is created, so they can be used in widget parameters. On the other hand, they cannot@@ -120,13 +130,16 @@ created, use a BEGIN declaration. .P Function are defined as in Awk, and recursion is supported:+ .P .BI "function " name "(" arg1 ", " arg2 ", ..., " argN ") { " statements " }"+ .P Arguments are lexically scoped within the function. If a function is called with fewer arguments than given in its declaration, the leftovers are given a false value. This is the only way to emulate local variables.+ .SS Patterns .TP .B BEGIN@@ -153,12 +166,49 @@ function call. .TP .BI $ class ( name ")->" event ( name1 ", " name2 ", ..., " nameN )-As above, but matches when a widget of the given class sends the named event.+As above, but matches when any widget of the given class sends the+named event. .I name will be bound to the widget that emitted the event. .TP .IB pat1 " || " ... " || " patN Matches if any of the patterns, checked left-to-right, match.++.SS Statements+If-conditions, while-loops and for-loops are supported with the same+syntax as in Awk, except that braces are always mandatory. All+variables, except for function parameters, are global and initialised+to false at program startup. A loop can be stopped with+.B continue+or+.BR break ,+with usual C semantics, and+.B return+can be used to exit early from a function.++.SS Expressions and Values+All values, except for objects, are always passed by value in+arguments and return values. Sindre supports numbers (integers and+decimal syntax), dictionaries, strings and objects. Boolean values+are canonically represented as integers, with the number 0 being false+and any other value considered true. Strings follow the Haskell+literal syntax (which is essentially identical to that of C). Objects+can be used as event sources, as mentioned above, and have methods and+fields. A method call has the syntax object.method(args) and a field+is object.field, and can be used as an lvalue. Dictionaries differ+significantly from those in Awk, as they have no special syntactical+treatment. An empty dictionary is written as+.B []+and elements can be added/changed by using the usual Awk-like syntax+.BR foo["bar"]=4 ,+although the variable must already contain a dictionary or you will+get an error (so use+.B foo=[]+to initialise). Keys and values can have any type.+Multidimensional dictionaries are only supported by making the values+dictionaries themselves, and has no special syntax, and arrays are+merely dictionaries with integral keys.+ .SS Multiple Fragments When multiple .B \-f@@ -167,12 +217,14 @@ options are used, Sindre conceptually concatenates the given program text fragments in the order of the options. There are two differences from plain concatenation, however:+ .TP .B Duplicate definitions A program fragment is normally not allowed to define two global variables or functions with the same name, nor to contain two GUI declarations. When the above options are used, redefinitions of previous definitions appearing in later fragments take precedence.+ .TP .B Event handling priority Event handlers are run from top to bottom in terms of the program@@ -186,24 +238,157 @@ will print "baz foo bar" whenever the event .B obj->ev()-happens. BEGIN declaration are similarly executed in reverse order.+happens. BEGIN declarations are similarly executed in reverse order. .ft B sindre -e 'BEGIN { print "I go last" }' -e 'BEGIN { print "I go first" }' .ft R+.SS Special Variables+.TP "\w'ENVIRON'u+1n"+.B RSTART+After regular expression matching, this variable will be set to the+index (1-based) of the match.+.TP+.B RLENGTH+The length of the most recent regular expression match.+.TP+.B ENVIRON+A dictionary containing the environment variables of the Sindre+process. Note that changing this dictionary currently has no effect+on the environment.+.TP+.B EXITVAL+Whenever an external program has been run, this variable will contain+its exit value.++.SS Numeric functions+.TP "\w'atan2(x, y)'u+1n"+.BI abs( n )+The numeric value of+.IR n .+.TP+.BI atan2( x , " y" )+Arctangent of+.I x/y+in radians.+.TP+.BI cos( x )+Cosine of+.IR x ,+in radians.+.TP+.BI sin( x )+Sine of+.IR x ,+in radians.+.TP+.BI exp( x )+Natural exponent of+.IR x .+.TP+.BI log( x )+Natural logarithm of+.IR x .+.TP+.BI int( x )+.I x+truncated to an integer.+.TP+.TP+.BI sqrt( x )+The square root of+.IR x .++.SS String Functions+.PP+Note that indexes are 1-based.+.PP+.TP "\w'substr(s, m, n)'u+1n"+.BI length( s )+Returns the number of characters in+.I s+.TP+.BI substr( s , " m" , " n" )+Return+.I n+characters of+.IR s ,+starting from character number+.IR m .+If either+.IR n or m+is out of bounds, the resulting string may be less than+.I n+characters.+.TP+.BI index( s , " t" )+Return the index at which+.I t+is found in+.IR s ,+or 0 if+.I t+is not present.+.TP+.BI match( s , " r" )+Match the regular expression+.I r+against+.IR t ,+returning the index of the first match, as well as setting+.B RMATCH+and+.BR RLENGTH .+.TP+.BI gsub( r , " t" , " s" )+For each match of the regular expression+.I r+in+.IR s ,+return a new string where each of those matches is replaced with+.IR t .+.TP+.BI sub( r , " t" , " S" )+Like+.IR sub ,+but only the first match is replaced.+.TP+.BI tolower( s )+Return an all-lowercase version of+.IR s .+.TP+.BI toupper( s )+Return an all-uppercase version of+.IR s .++.SS System Functions+.TP "\w'osystem(s)'u+1n"+.BI osystem( s )+Run+.I s+as a shell command and return its output.+.TP+.BI system( s )+Run+.I s+as a shell command and return its exit value.+ .SH EXIT STATUS Sindre returns a .B 0 exit status on success, and .B 1 if there was an internal problem.+ .SH EXAMPLES See the examples/ subdirectory of the Sindre source tree.+ .SH SEE ALSO .BR dmenu (1), .BR awk (1), .BR sinmenu (1)+ .SH BUGS The syntax and semantics for local variables are inherited from Awk, and are rather ugly. It is possible to write programs that have no
sindre.cabal view
@@ -1,5 +1,5 @@ name: sindre-version: 0.2+version: 0.3 homepage: http://sigkill.dk/programs/sindre synopsis: A programming language for simple GUIs description:@@ -35,7 +35,7 @@ build-depends: X11>=1.5.0.0 && < 1.6, X11-xshape>=0.1.1, X11-rm>=0.2, mtl, base >= 4.3 && < 5, containers, parsec>=3.1, array>=0.3, x11-xim>=0.0.6, setlocale, regex-pcre, process,- text, bytestring, unix, attoparsec-text>=0.8.2,+ text, bytestring, unix, attoparsec>=0.10, permute, utf8-string>=0.3 ghc-options: -funbox-strict-fields -Wall@@ -63,7 +63,7 @@ build-depends: X11>=1.5.0.0 && < 1.6, X11-xshape>=0.1.1, X11-rm, mtl, base >= 4.3 && < 5, containers, parsec>=3.1, array>=0.3, x11-xim>=0.0.5, setlocale, regex-pcre, process,- text, bytestring, unix, attoparsec-text>=0.8.2,+ text, bytestring, unix, attoparsec>=0.10.1.0, permute, utf8-string>=0.3 pkgconfig-depends: xft default-language: Haskell2010
@@ -4,7 +4,7 @@ Horizontally@bottom?"bot":"top" { prompt=Label(label=pstring,highlight=1, fg=ffg, bg=fbg); input=Input(minwidth=200, font=font, fg=fg, bg=bg, ffg=ffg, fbg=fbg);- list=HList(i=ins, font=font, fg=fg, bg=bg, ffg=ffg, fbg=fbg);+ list=HList(font=font, fg=fg, bg=bg, ffg=ffg, fbg=fbg); } } EOF@@ -20,7 +20,7 @@ } Vertically { input=Input(minwidth=0, font=font, fg=fg, bg=bg, ffg=ffg, fbg=fbg);- list=VList(lines=lines, i=ins, font=font, fg=fg, bg=bg, ffg=ffg, fbg=fbg);+ list=VList(lines=lines, font=font, fg=fg, bg=bg, ffg=ffg, fbg=fbg); } } }@@ -34,7 +34,6 @@ option pstring (-p,--prompt,"Set the input prompt", "prompt", "") option bottom (-b,,"Appear at bottom of screen") option contents(-c,,"Starting contents of buffer", "text", "")-option ins(-i,,"Case-insensitive list matching") option font(,--font,"Font used for text", "font") option fg(,--nf,"Normal foreground colour", "colour") option bg(,--nb,"Normal background colour", "colour")
@@ -1,4 +1,4 @@-.TH SINMENU 1 sinmenu\-1.0+.TH SINMENU 1 sinmenu\-VERSION .SH NAME sinmenu \- slower dmenu that uses more memory .SH SYNOPSIS@@ -67,5 +67,9 @@ .TP .B Escape (C\-c) or C\-g Exit without selecting an item, returning failure.+.TP+.B C\-y+Paste the current X solution to the input field, using+.BR sselp (1). .SH SEE ALSO-.BR sindre (1), dmenu (1)+.BR sindre "(1), " dmenu "(1), " sselp (1)