~alcinnz/haskell-stylist

b26bb3d190dcd3f81b5acc6c288afc3645dc1420 — Adrian Cochrane 5 years ago 6a71648
Implement basic parser. TODO test.
7 files changed, 201 insertions(+), 20 deletions(-)

A Stylish.hs
A Stylish/Parse.hs
A Stylish/Parse/Selector.hs
A Stylish/Parse/Utils.hs
D Stylish/Select.hs
A Stylish/Style.hs
M stylish-haskell.cabal
A Stylish.hs => Stylish.hs +8 -0
@@ 0,0 1,8 @@
module Stylish(
        parse,
        StyleSheet, addRule, addAtRule,
        StyleRule,
        Selector, Combinator, SimpleSelector, AttrTest
    ) where

import Stylish.Parse

A Stylish/Parse.hs => Stylish/Parse.hs +102 -0
@@ 0,0 1,102 @@
module Stylish.Parse (
        parse,
        skipAtRule,
        StyleSheet, addRule, addAtRule,
        StyleRule,
        Selector(..), SimpleSelector(..), PropertyTest(..)
    ) where

import Data.CSS.Syntax.Tokens
import Stylish.Parse.Selector
import Parse.Utils

--------
---- Output type class
--------
class StyleSheet s where
    addRule :: s -> StyleRule -> s
    addAtRule :: s -> Text -> [Token] -> (s, [Token])
    addAtRule self _ tokens = (self, skipAtRule tokens)

data StyleRule = StyleRule [Selector] [(Text, [Token])]

--------
---- Basic parsing
--------
parse :: StyleSheet s => s -> Text -> s
parse stylesheet source = parse' stylesheet tokenize source

-- Things to skip.
parse' stylesheet Whitespace:tokens = parse' stylesheet tokens
parse' stylesheet CDO:tokens = parse' stylesheet tokens
parse' stylesheet CDC:tokens = parse' stylesheet tokens
parse' stylesheet Comma:tokens = parse' stylesheet tokens -- TODO issue warnings.

parse' stylesheet [] = stylesheet

parse' stylesheet AtKeyword(kind):tokens = parse' stylesheet' tokens'
    where (stylesheet', tokens') = addAtRule stylesheet kind tokens
parse' stylesheet tokens = parse' addRule stylesheet StyleRule selectors properties tokens'
    where (selectors, block) = parseSelectors tokens
        (properties, tokens') = parseProperties block
parse' stylesheet tokens = parse' (addRule stylesheet rule) tokens'
    where (rule, tokens') = concatP (StyleRule) (parseSelector) (parseProperties)

--------
---- Property parsing
--------
parseProperties LeftCurlyBracket:tokens = parseProperties' tokens
parseProperties Whitespace:tokens = parseProperties tokens

parseProperties' Whitespace:tokens = parseProperties' tokens
parseProperties' (Ident name):tokens
    | Colon:tokens' <- skipSpace tokens =
        concatP (appendProp) (scanValue) (parseProperties') tokens'
    where appendProp value tail = (name, value):tail
parseProperties' RightCurlyBracket:tokens = [], tokens
parseProperties' tokens = parseProperties' skipValue tokens

--------
---- Skipping/Scanning utilities
--------
skipAtRule Semicolon:tokens = tokens
skipAtRule LeftCurlyBracket:tokens = skipBlock tokens

skipAtRule LeftParen:tokens = skipAtRule skipBlock tokens
skipAtRule LeftSquareBracket:tokens = skipAtRule skipBlock tokens
-- To ensure parens are balanced, should already be handled.
skipAtRule RightCurlyBracket:tokens = RightCurlyBracket:tokens
skipAtRule RightParen:tokens = RightParen:tokens
skipAtRule RightSquareBracket:tokens = RightSquareBracket:tokens

skipAtRule _:tokens = skipAtRule tokens

scanValue Semicolon:tokens = ([], tokens)
scanValue Whitespace:tokens = scanValue tokens

scanValue LeftCurlyBracket:tokens = scanInner tokens scanValue
scanValue LeftParen:tokens = scanInner tokens scanValue
scanValue LeftSquareBracket:tokens = scanInner tokens scanValue
scanInner tokens cb = concatP (++) (scanBlock) (cb)
-- To ensure parens are balanced, should already be handled.
scanValue RightCurlyBracket:tokens = ([], RightCurlyBracket:tokens)
scanValue RightParen:tokens = ([], RightParen:tokens)
scanValue RightSquareBracket:tokens = ([], RightSquareBracket:tokens)

scanValue tokens = capture scanValue tokens

scanValue tokens = snd scanValue tokens

-- TODO assert closing tags are correct
--    But what should the error recovery be?
scanBlock RightCurlyBracket:tokens = ([RightCurlyBracket], tokens)
scanBlock RightParen:tokens = ([RightParen], tokens)
scanBlock RightSquareBracket:tokens = ([RightSquareBracket], tokens)

scanBlock LeftCurlyBracket:tokens = scanInner tokens scanBlock
scanBlock LeftParen:tokens = scanInner tokens scanBlock
scanBlock RightSquareBracket:tokens = scanInner tokens scanBlock

scanBlock tokens = capture scanBlock tokens

skipBlock tokens = snd scanBlock tokens

A Stylish/Parse/Selector.hs => Stylish/Parse/Selector.hs +49 -0
@@ 0,0 1,49 @@
module Stylish.Parse.Selector(
        Selector(..), SimpleSelector(..), PropertyTest(..),
        parseSelectors
    ) where

import Data.CSS.Syntax.Tokens
import Utils

data Selector = Selector [SimpleSelector]
data SimpleSelector = Tag Text | Id Text | Class Text | Property Text PropertyTest |
    Child | Descendant | Adjacent | Sibling
data PropertyTest = Exists | Equals Text | Suffix Text | Prefix Text | Substring Text |
    Include Text | Dash Text

parseSelectors :: [Token] -> (Selector, [Token])
parseSelectors tokens = concatP (:) (parseSelector) (parseSelectorsTail) skipSpace tokens
parseSelectorsTail Comma:tokens = parseSelectors token
parseSelectorsTail tokens = ([], tokens)

parseSelector' op tokens = (op:selector, tokens')
    where (selector, tokens') = parseSelector tokens

parseSelector Ident tag:tokens = parseSelector' Tag tag tokens
parseSelector Hash _ id:tokens = parseSelector' Id id tokens
parseSelector Delim '.':Ident class_:tokens = parseSelector' Class class_ tokens
parseSelector LeftSquareBracket:Ident prop:tokens =
        concatP (appendPropertySel) (parsePropertySel) (parseSelector) tokens
    where appendPropertySel test selector = (Property prop test):selector

parseSelector Whitespace:tokens = parseCombinator skipSpace tokens
parseSelector Delim c:tokens = parseCombinator Delim c:tokens
parseSelector tokens = ([], tokens)

parseCombinator Delim '>':tokens = parseSelector' Child skipSpace tokens
parseCombinator Delim '~':tokens = parseSelector' Sibling skipSpace tokens
parseCombinator Delim '+':tokens = parseSelector' Adjacent skipSpace tokens
parseCombinator tokens = parseSelector' Descendant tokens

parsePropertySel RightSquareBracket:tokens = (Exists, tokens)
parsePropertySel Delim '=':tokens = parsePropertyVal (Equals) tokens
parsePropertySel SuffixMatch:tokens = parsePropertyVal (Suffix) tokens
parsePropertySel PrefixMatch:tokens = parsePropertyVal (Prefix) tokens
parsePropertySel SubstringMatch:tokens = parsePropertyVal (Substring) tokens
parsePropertySel IncludeMatch:tokens = parsePropertyVal (Include) tokens
parsePropertySel DashMatch:tokens = parsePropertyVal (Dash) tokens
parsePropertySel tokens = (Exists, skipBlock tokens)

parsePropertyVal wrapper Ident val:RightSquareBracket:tokens = (wrapper val, tokens)
parsePropertyVal wrapper String val:RightSquareBracket:tokens = (wrapper val, tokens)

A Stylish/Parse/Utils.hs => Stylish/Parse/Utils.hs +15 -0
@@ 0,0 1,15 @@
module Stylish.Parse.Utils(
        concatP, capture, skipSpace
    ) where

import Data.CSS.Syntax.Tokens

concatP join left right tokens = (join x y, remainder)
    where (x, tokens') = left tokens
        (y, remainder) = right tokens'

capture cb token:tokens = (token:captured, tokens')
    where (captured, tokens') = cb tokens

skipSpace Whitespace:tokens = skipSpace tokens
skipSpace tokens = tokens

D Stylish/Select.hs => Stylish/Select.hs +0 -18
@@ 1,18 0,0 @@
module Stylish.Select(
        DocumentNode(..),
        DocumentAttribute(..)
    ) where

data DocumentNode = DocumentNode {
    parent :: DocumentNode,
    prev :: DocumentNode,
    name :: Text,
    namespace :: Text,
    attributes :: [DocumentAttribute] -- Sorted alphabetically by name.
}

data DocumentAttribute = DocumentAttribute {
    name :: Text,
--    namespace :: Text, -- TODO
    value :: Text
}

A Stylish/Style.hs => Stylish/Style.hs +25 -0
@@ 0,0 1,25 @@
module Stylish.Style(
        style,
        Style(..),
        Element(..),
        Attribute(..),
    ) where

style :: Style a => Stylist a -> Element -> a
-- Stylist consists of internal types, implements StyleSheet.

class Style s where
    initial :: s
    shorthand :: Text -> [Token] -> [(Text, [Token])]
    shorthand name value
        | Just _ <- longhand initial initial name value = (name, value)
        | otherwise = []
    longhand :: s -> s -> Text -> [Token] -> Maybe s

data Element = Element {
    prev :: Maybe Element,
    parent :: Maybe Element,
    name :: Text,
    attrs :: [Attribute]
}
data Attribute = Attr Text Text

M stylish-haskell.cabal => stylish-haskell.cabal +2 -2
@@ 48,7 48,7 @@ cabal-version:       >=1.10

library
  -- Modules exported by the library.
  -- exposed-modules:     Stylish.Select
  -- exposed-modules:     Stylish.Parse
  
  -- Modules included in this library but not exported.
  -- other-modules:       


@@ 57,7 57,7 @@ library
  -- other-extensions:    
  
  -- Other library packages from which modules are imported.
  build-depends:       base >=4.9 && <4.10
  build-depends:       base >=4.9 && <4.10, css-syntax
  
  -- Directories containing source files.
  -- hs-source-dirs: