Skip to content

Commit e9e368f

Browse files
committed
@function support
1 parent 914eba8 commit e9e368f

12 files changed

Lines changed: 283 additions & 26 deletions

File tree

css-parser.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ library
4040
, CssParser.At.FontFace
4141
, CssParser.At.FontFeatureValues
4242
, CssParser.At.FontPaletteValues
43+
, CssParser.At.Function
4344
, CssParser.At.Import
4445
, CssParser.At.Keyframe
4546
, CssParser.At.Layer
@@ -92,6 +93,7 @@ test-suite test
9293
, CssParser.Test.Arbitrary.FontFace
9394
, CssParser.Test.Arbitrary.FontFeatureValues
9495
, CssParser.Test.Arbitrary.FontPaletteValues
96+
, CssParser.Test.Arbitrary.Function
9597
, CssParser.Test.Arbitrary.Media
9698
, CssParser.Test.Arbitrary.MonoPair
9799
, CssParser.Test.Arbitrary.Rule

src/CssParser/At/Function.hs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{-# LANGUAGE UndecidableInstances #-}
2+
module CssParser.At.Function where
3+
4+
import CssParser.Ident ( Ident, Var )
5+
import CssParser.Rule.Value ( PropVal, PropVals, PropValsList )
6+
import CssParser.Prelude
7+
import CssParser.Show
8+
( CssShow(..), Embraced(Embraced), ShowSpaceBetween(..), Csl(Csl) )
9+
10+
data AtomicCssType
11+
= Angle
12+
| Color
13+
| CustomIdent
14+
| Image
15+
| Integer
16+
| Length
17+
| LengthPercentage
18+
| Number
19+
| Percentage
20+
| Resolution
21+
| String
22+
| Time
23+
| TranformFunction
24+
| TranformList
25+
| UrlType
26+
deriving (Eq, Ord, Show, Enum, Bounded, Generic)
27+
28+
instance CssShow AtomicCssType where
29+
toCssText = \case
30+
Angle -> "<angle>"
31+
Color -> "<color>"
32+
CustomIdent -> "<custom-ident>"
33+
Image -> "<image>"
34+
Integer -> "<integer>"
35+
Length -> "<length>"
36+
LengthPercentage -> "<length-percentage>"
37+
Number -> "<number>"
38+
Percentage -> "<percentage>"
39+
Resolution -> "<resolution>"
40+
String -> "<string>"
41+
Time -> "<time>"
42+
TranformFunction -> "<tranform-function>"
43+
TranformList -> "<tranform-list>"
44+
UrlType -> "<url>"
45+
46+
data CssLeafType
47+
= AtomicCssType AtomicCssType
48+
| IdentCssType Ident
49+
deriving (Eq, Ord, Show, Generic)
50+
51+
instance CssShow CssLeafType where
52+
toCssText = \case
53+
AtomicCssType x -> toCssText x
54+
IdentCssType x -> toCssText x
55+
56+
data CssType
57+
= Once CssLeafType
58+
| AnyCssType
59+
| CommaSeparated CssLeafType
60+
| SpaceSeparated CssLeafType
61+
| OrLeaf CssLeafType CssType
62+
deriving (Eq, Ord, Show, Generic)
63+
64+
instance CssShow CssType where
65+
toCssText = \case
66+
Once a -> toCssText a
67+
AnyCssType -> "*"
68+
OrLeaf x t -> toCssText x <> " | " <> toCssText t
69+
CommaSeparated a -> toCssText a <> "#"
70+
SpaceSeparated a -> toCssText a <> "+"
71+
72+
data FunArg = FunArg
73+
{ argName :: Var
74+
, argType :: Maybe CssType
75+
, defaultValue :: Maybe PropVal
76+
}
77+
deriving (Eq, Ord, Show, Generic)
78+
79+
instance CssShow FunArg where
80+
toCssText fa =
81+
toCssText fa.argName <>
82+
maybe "" ((" type(" <> ) . (<> ")") . toCssText) fa.argType <>
83+
maybe "" ((" : " <> ) . toCssText) fa.defaultValue
84+
85+
data ConstEntry = ConstEntry Var PropValsList deriving (Show, Eq, Ord, Generic)
86+
instance ShowSpaceBetween ConstEntry ConstEntry where
87+
cssSpace _ _ = ""
88+
instance CssShow ConstEntry where
89+
toCssText (ConstEntry pn pv) =
90+
toCssText pn <> ": " <> toCssText pv <> ";"
91+
92+
data Function r
93+
= Function
94+
{ name :: Var
95+
, args :: [FunArg]
96+
, returns :: Maybe CssType
97+
, localConsts :: [ ConstEntry ]
98+
, result :: PropVals
99+
, atRules :: [r]
100+
} deriving (Eq, Ord, Show, Generic)
101+
102+
instance (ShowSpaceBetween r r, CssShow r) => CssShow (Function r) where
103+
toCssText p =
104+
toCssText p.name <> toCssText (Embraced (Csl p.args)) <>
105+
maybe "" ((" returns type(" <>) . (<> ")"). toCssText) p.returns <>
106+
"{" <> toCssText p.localConsts <> " result: " <> toCssText p.result <> ";" <>
107+
toCssText p.atRules <> "}"

src/CssParser/Lexer.x

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module CssParser.Lexer where
44

55
import Control.Monad ((<=<))
6+
import CssParser.At.Function (AtomicCssType)
7+
import CssParser.At.Function qualified as F
68
import CssParser.At.MediaQuery (MediaType(..))
79
import CssParser.At.Page
810
import CssParser.Fun
@@ -173,6 +175,7 @@ tokens :-
173175
@unicode "-" @range { constoken UnicodeRangeT }
174176
@src { constoken SrcPropT }
175177
"@" { constoken AtT }
178+
"@" @f@u@n@c@t@i@o@n { constoken AtFunctionT }
176179
"@" @font "-" @face { constoken FontFaceT }
177180
"@" @position "-" @try { constoken PositionTryT }
178181
@wo "@" @page $w @wo { constoken PageT }
@@ -213,6 +216,8 @@ tokens :-
213216
@wo "@" @import $w @wo { constoken ImportT }
214217
@wo "@" @keyframes $w @wo { constoken KeyframesT }
215218
@layer { constoken LayerT }
219+
@r@e@s@u@l@t { constoken ResultT }
220+
@r@e@t@u@r@n@s { constoken ReturnsT }
216221
@wo "@" @layer @wo { constoken LayerAtT }
217222
@wo "@" @media $w @wo { constoken MediaT }
218223
@to { constoken ToT }
@@ -234,6 +239,7 @@ tokens :-
234239
@and @wo { constoken AndT }
235240
@selector "(" { constoken SelectorFunT }
236241
@c@a@l@c "(" { constoken CalcFunT }
242+
@t@y@p@e "(" { constoken TypeFunT }
237243
@url "(" { constoken UrlT }
238244
@url "(" @wo [^\"\'][^\)]* ")" { tokenize (UnquotedUrlT . readUnquotedUrl) }
239245
"." { constoken Dot }
@@ -246,6 +252,7 @@ tokens :-
246252
"U+" ("?" | "1")? ("?" | "0")? @updig{1,4} ("-" ("?" | "1")? ("?" | "0")? @updig{1,4})?
247253
{ tokenize (UnicodeRangeVal . drop 2) }
248254
@var @name { tokenize (Var . readIdentifier . drop 2) }
255+
"#" { constoken SharpT }
249256
"#" @name { tokenize (THash . readIdentifier . drop 1) }
250257

251258
@anum { tokenize UnitLessNum }
@@ -307,6 +314,23 @@ tokens :-
307314
@uint "/" @uint { tokenize2 ((pure . RatioT) <=< readRatio) }
308315
"+" { constoken Plus }
309316
"-" { constoken Minus }
317+
318+
@wo "<" @a@n@g@l@e ">" { constoken (SyntaxTypeT F.Angle) }
319+
@wo "<" @c@o@l@o@r ">" { constoken (SyntaxTypeT F.Color) }
320+
@wo "<" @c@u@s@t@o@m "-" @i@d@e@n@t ">" { constoken (SyntaxTypeT F.CustomIdent) }
321+
@wo "<" @i@m@a@g@e ">" { constoken (SyntaxTypeT F.Image) }
322+
@wo "<" @i@n@t@e@g@e@r ">" { constoken (SyntaxTypeT F.Integer) }
323+
@wo "<" @l@e@n@g@t@h ">" { constoken (SyntaxTypeT F.Length) }
324+
@wo "<" @l@e@n@g@t@h "-" @p@e@r@c@e@n@t@a@g@e ">" { constoken (SyntaxTypeT F.LengthPercentage) }
325+
@wo "<" @n@u@m@b@e@r ">" { constoken (SyntaxTypeT F.Number) }
326+
@wo "<" @p@e@r@c@e@n@t@a@g@e ">" { constoken (SyntaxTypeT F.Percentage) }
327+
@wo "<" @r@e@s@o@l@u@t@i@o@n ">" { constoken (SyntaxTypeT F.Resolution) }
328+
@wo "<" @s@t@r@i@n@g ">" { constoken (SyntaxTypeT F.String) }
329+
@wo "<" @t@i@m@e ">" { constoken (SyntaxTypeT F.Time) }
330+
@wo "<" @t@r@a@n@f@o@r@m "-" @f@u@n@c@t@i@o@n ">" { constoken (SyntaxTypeT F.TranformFunction) }
331+
@wo "<" @t@r@a@n@f@o@r@m "-" @l@i@s@t ">" { constoken (SyntaxTypeT F.TranformList) }
332+
@wo "<" @u@r@l ">" { constoken (SyntaxTypeT F.UrlType) }
333+
310334
@wo ">" @wo { constoken Greater }
311335
@wo ">=" @wo { constoken GreaterEqual }
312336
@wo "<" @wo { constoken Less }
@@ -555,6 +579,7 @@ data Token
555579
| Semicolon
556580
| Pipe
557581
| Plus
582+
| SharpT
558583
| Minus
559584
| Greater
560585
| GreaterEqual
@@ -566,8 +591,13 @@ data Token
566591
| PageT
567592
| PageMarginT PageMargin
568593

594+
| ResultT
595+
| ReturnsT
569596
| SelectorFunT
570597
| CalcFunT
598+
| TypeFunT
599+
| SyntaxTypeT AtomicCssType
600+
| AtFunctionT
571601
| ImportantT
572602
| SupportsT
573603
| ScopeT

src/CssParser/Parser.y

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import CssParser.At.Container
77
import CssParser.At.FontFace
88
import CssParser.At.FontFeatureValues
99
import CssParser.At.FontPaletteValues
10+
import CssParser.At.Function qualified as F
1011
import CssParser.At.Import
1112
import CssParser.At.Keyframe
1213
import CssParser.At.Layer
@@ -29,14 +30,14 @@ import CssParser.Lexer
2930
( AlexPosn(AlexPn), TokenLoc(TokenLoc)
3031
, Token
3132
( TIncludes, TEqual, TDashMatch, TPrefixMatch, TSuffixMatch, TSubstringMatch, Ident
32-
, Comma, Plus, Minus, Tilde, Dot, Asterisk, Space, BOpen, BClose, PseudoFunction
33+
, Comma, Plus, SharpT, Minus, Tilde, Dot, Asterisk, Space, BOpen, BClose, PseudoFunction
3334
, PseudoElementT, TN, TNth, TPM, TInt, TNot, TLang, String, THash
3435
, COpen, CClose, Colon, Semicolon, Var, Pipe, AtomicPseudoClassT, Ampersand
3536
, CharsetT, ImportT, MediaT, LayerT, LayerAtT, NamespaceT, CounterStyleT, PropertyT
36-
, NotT, OrT, AndT, OnlyT
37+
, NotT, OrT, AndT, OnlyT, ResultT, ReturnsT
3738
, TOpen, TClose
3839
, Greater, Less, LessEqual, GreaterEqual
39-
, RatioT, ImportantT, MediaTypeT, CalcFunT
40+
, RatioT, ImportantT, MediaTypeT, CalcFunT, TypeFunT, AtFunctionT, SyntaxTypeT
4041
, UrlT, UnquotedUrlT, TWhere, THas, TIs, PageT, PageMarginT
4142
, KeyframesT, ColorProfileT, FontFaceT, SrcPropT, UnicodeRangeT, UnicodeRangeVal
4243
, FontFeatureValuesT, AtT, FontPaletteValuesT, ContainerT, DivT, PositionTryT
@@ -73,6 +74,7 @@ import Prelude
7374
'<' { TokenLoc Less _ _ }
7475
'<=' { TokenLoc LessEqual _ _ }
7576
'+' { TokenLoc Plus _ _ }
77+
'#' { TokenLoc SharpT _ _ }
7678
'-' { TokenLoc Minus _ _ }
7779
'|' { TokenLoc Pipe _ _ }
7880
'~' { TokenLoc Tilde _ _ }
@@ -91,6 +93,8 @@ import Prelude
9193
'@' { TokenLoc AtT _ _ }
9294
important { TokenLoc ImportantT _ _ }
9395
supports { TokenLoc SupportsT _ _ }
96+
result { TokenLoc ResultT _ _ }
97+
returns { TokenLoc ReturnsT _ _ }
9498
viewTransition
9599
{ TokenLoc ViewTransitionT _ _ }
96100
startingStyle
@@ -129,6 +133,9 @@ import Prelude
129133
'uqUrl' { TokenLoc (UnquotedUrlT $$) _ _ }
130134
'selector(' { TokenLoc SelectorFunT _ _ }
131135
'calc(' { TokenLoc CalcFunT _ _ }
136+
'type(' { TokenLoc TypeFunT _ _ }
137+
'@function' { TokenLoc AtFunctionT _ _ }
138+
syntaxType { TokenLoc (SyntaxTypeT $$) _ _ }
132139
'^=' { TokenLoc TPrefixMatch _ _ }
133140
'$=' { TokenLoc TSuffixMatch _ _ }
134141
'*=' { TokenLoc TSubstringMatch _ _ }
@@ -299,9 +306,9 @@ CssRule :: { CssRule }
299306
| 'page' PageSelectorList '{' CssRuleBody '}' { Page (PageSelectorList $2) $4 }
300307
| pageMargin '{' CssRuleBody '}' { PageMarginBlock $1 $3 }
301308
| counterStyle IdKwd '{' CssRuleBody '}' { CounterStyle $2 $4 }
302-
| property Var '{' CssRuleBody '}' { Property (R.Var $2) $4 }
309+
| property Var '{' CssRuleBody '}' { Property $2 $4 }
303310
| keyframes IdKwd Ocb KeyframeList '}' { Keyframes (KeyframeSet (KeyframeSetName $2) $4) }
304-
| colorProf Os Var Ocb ColorPropEntries '}' { ColorProfile (VarProp (R.Var $3)) $5 }
311+
| colorProf Os Var Ocb ColorPropEntries '}' { ColorProfile (VarProp $3) $5 }
305312
| colorProf Os IdKwd Ocb ColorPropEntries '}' { ColorProfile (PropertyName $3) $5 }
306313
| fontFace Os Ocb FontFacePropEntries '}' {% fmap FontFaceBlock (fromEitherM failP (mkFontFace $4)) }
307314
| fontFeatureValues ' ' StrEitherIds Os Ocb FontFeatureValBlocks '}'
@@ -312,15 +319,48 @@ CssRule :: { CssRule }
312319
(mapMaybe rightToMaybe $6))
313320
}
314321
| fontPaletteValues ' ' Var Os Ocb PropEntries '}'
315-
{ FontPaletteValuesBlock (FontPaletteValues (R.Var $3) $6) }
322+
{ FontPaletteValuesBlock (FontPaletteValues $3 $6) }
316323
| container Os ContainerQueryMap ERB { Container (ContainerQueryMap $3) $4 }
317-
| positionTry Os Var Ocb PropEntries '}' { PositionTry (R.Var $3) $5 }
324+
| positionTry Os Var Ocb PropEntries '}' { PositionTry $3 $5 }
318325
| startingStyle Os ERB { StartingStyle $3 }
319326
| viewTransition Os ERB { ViewTransition $3 }
320327
| scope Os SelectorPair ERB { ScopeBlock $3 $4 }
328+
| '@function' Os Function { FunctionBlock $3 }
321329
| '@' supports Os FeatureQuery ERB { Supports (normalize $4) $5 }
322330
| '@' IdKwd Os CommaSeparatedList Os ERB { UnknownGramma $2 (Just (CommaSeparatedList $4)) $6 }
323331
| '@' IdKwd Os ERB { UnknownGramma $2 Nothing $4 }
332+
Function :: { CssFunction }
333+
: Var Op FunArgs Os ')' Os RetType Os Ocb List(LocalConst) FunResult CssFileBody '}'
334+
{ F.Function $1 $3 $7 $10 $11 $12 }
335+
FunArgs :: { [ F.FunArg ] }
336+
: { [] }
337+
| FunArg { [ $1 ] }
338+
| FunArg Os ',' Os FunArgs { $1 : $5 }
339+
FunArg :: { F.FunArg }
340+
: Var { F.FunArg $1 Nothing Nothing }
341+
| Var Os TypeFun { F.FunArg $1 (Just $3) Nothing }
342+
| Var Os TypeFun Os ':' Os PropVal { F.FunArg $1 (Just $3) (Just $7) }
343+
| Var Os ':' Os PropVal { F.FunArg $1 Nothing (Just $5) }
344+
LocalConst :: { F.ConstEntry }
345+
: Var ':' PropValsList ';' { F.ConstEntry $1 (PropValsList $3) }
346+
FunResult :: { PropVals }
347+
: result ':' PropVals ';' { $3 }
348+
RetType :: { Maybe F.CssType }
349+
: { Nothing }
350+
| returns Os TypeFun { Just $3 }
351+
TypeFun :: { F.CssType }
352+
: 'type(' Os CssType Os ')' { $3 }
353+
| CssLeaf { F.Once $1 }
354+
| '*' { F.AnyCssType }
355+
CssType :: { F.CssType }
356+
: '*' { F.AnyCssType }
357+
| CssLeaf { F.Once $1 }
358+
| CssLeaf '#' { F.CommaSeparated $1 }
359+
| CssLeaf '+' { F.SpaceSeparated $1 }
360+
| CssLeaf Os '|' Os CssType { F.OrLeaf $1 $5 }
361+
CssLeaf :: { F.CssLeafType }
362+
: syntaxType { F.AtomicCssType $1 }
363+
| IdKwd { F.IdentCssType $1 }
324364
FeatureQuery :: { FeatureQuery }
325365
: Op MediaFeature ')' { FqMediaFeature $2 }
326366
| Op FeatureQuery ')' { FqParen $2 }
@@ -367,7 +407,7 @@ CQ :: { ContainerQuery }
367407
$8
368408
}
369409
| Ident ':' PropVals { CqFeature (AsIs (CqOpFeature (PlainMf (PropertyName $1) $3))) }
370-
| Var ':' PropVals { CqFeature (AsIs (CqOpFeature (PlainMf (VarProp (R.Var $1)) $3))) }
410+
| Var ':' PropVals { CqFeature (AsIs (CqOpFeature (PlainMf (VarProp $1) $3))) }
371411
| 'not' Op MediaFeature ')' Os BOP CQ { CqBin $6 (Not (CqOpFeature $3)) $7 }
372412
| 'not' Op MediaFeature ')' { CqFeature (Not (CqOpFeature $3)) }
373413
| 'not' Os Ident Os Op CQ ')' { CqFeature (Not (CqApp $3 $6)) }
@@ -414,7 +454,7 @@ PropEntries :: { [PropEntry] }
414454
: List(PropEntry) { $1 }
415455
PropertyName :: { PropertyName }
416456
: IdKwd { PropertyName $1 }
417-
| Var { VarProp (R.Var $1) }
457+
| Var { VarProp $1 }
418458
PropEntry :: { PropEntry }
419459
: PropertyName ':' PropVals ';' { PropEntry $1 $3 }
420460
PageSelectorList
@@ -549,8 +589,9 @@ PropVal :: { PropVal }
549589
: Scalar { IntVal (mkRawNum (fst $1)) (snd $1) }
550590
| 'ratio' { RatioVal $1 }
551591
| PropertyName { propRef $1 }
552-
| PropertyName '/' Os PropertyName { Div $1 $4 }
592+
| PropVal '/' Os PropVal { Div $1 $4 }
553593
| PropertyName Op PropVals ')' { AppFun $1 $3 }
594+
| PropertyName Op ')' { AppConst $1 }
554595
| Str { StrVal $1 }
555596
| 'url(' Str ')' { UrlVal (Url $2) }
556597
| 'uqUrl' { UrlVal (UnquotedUrl (pack $1)) }
@@ -797,6 +838,8 @@ IdKwd :: { R.Ident }
797838
: Ident { $1 }
798839
| MediaKeywordAsIdent { $1 }
799840
| layer { R.Ident "layer" }
841+
| result { R.Ident "result" }
842+
| returns { R.Ident "returns" }
800843
| mediaType { R.Ident (toStrict (toCssText $1)) }
801844
MediaKeywordAsIdent
802845
: 'not' { R.Ident "not" }
@@ -809,8 +852,8 @@ IdTxt :: { Text }
809852
: ident { pack $1 }
810853
Str :: { Text }
811854
: string { pack $1 }
812-
Var :: { R.Ident }
813-
: var { R.Ident (pack $1) }
855+
Var :: { R.Var }
856+
: var { R.Var (R.Ident (pack $1)) }
814857
Embraced(o, p, c)
815858
: o p c { $2 }
816859
Clp : ')' { $1 }

0 commit comments

Comments
 (0)