diff options
| author | Akshay Nair <phenax5@gmail.com> | 2023-08-13 12:05:41 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2023-08-13 12:05:41 +0530 |
| commit | 1e8135bf0e24e39f4dc3f8b0a13241ddf0cf475c (patch) | |
| tree | ffc8cfa72e60b29921916607b352ba5139b34eee | |
| parent | a61f5ec92a9382b0c25fae9a344da0d7eb21b702 (diff) | |
| download | css-everything-1e8135bf0e24e39f4dc3f8b0a13241ddf0cf475c.tar.gz css-everything-1e8135bf0e24e39f4dc3f8b0a13241ddf0cf475c.zip | |
feat: selector parser + pair parser implemented
| -rw-r--r-- | src/eval.ts | 1 | ||||
| -rw-r--r-- | src/parser.ts | 62 | ||||
| -rw-r--r-- | tests/parser.spec.ts | 63 |
3 files changed, 112 insertions, 14 deletions
diff --git a/src/eval.ts b/src/eval.ts index 9f053c2..3a4e4f9 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -113,6 +113,7 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) => return actions.getAttribute(id as string | undefined, name) } }, + 'prevent-default': async () => actions.withEvent(e => e.preventDefault()), request: async () => { diff --git a/src/parser.ts b/src/parser.ts index 3279f57..a6c3928 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3,12 +3,25 @@ import * as P from './utils/parser-comb' export type CSSUnit = '' | 's' | 'ms' +export type SelectorComp = Enum<{ + ClassName: string + Attr: readonly [string, string] +}> +export const SelectorComp = constructors<SelectorComp>() + export type Expr = Enum<{ Call: { name: string; args: Expr[] } Identifier: string VarIdentifier: string LiteralString: string LiteralNumber: { value: number; unit: CSSUnit } + + Pair: { key: string; value: Expr } + Selector: { + tag: string | undefined + id: string + selectors: Array<SelectorComp> + } }> export const Expr = constructors<Expr>() @@ -36,28 +49,61 @@ const callExprParser = (input: string) => ([name, args]) => Expr.Call({ name, args }), )(input) -const stringLiteralParser: P.Parser<Expr> = P.map( - P.or([ - P.between(singleQuote, P.regex(/^[^']*/), singleQuote), - P.between(doubleQuote, P.regex(/^[^"]*/), doubleQuote), - ]), +const stringLiteralParser = P.or([ + P.between(singleQuote, P.regex(/^[^']*/), singleQuote), + P.between(doubleQuote, P.regex(/^[^"]*/), doubleQuote), +]) +const stringLiteralExprParser: P.Parser<Expr> = P.map( + stringLiteralParser, Expr.LiteralString, ) const numberParser = P.regex(/^[-+]?((\d*\.\d+)|\d+)/) - const numberExprParser: P.Parser<Expr> = P.map( P.zip2(numberParser, P.optional(P.regex(/^(s|ms)/i))), ([value, unit]) => Expr.LiteralNumber({ value: Number(value), unit: (unit ?? '') as CSSUnit }), ) +const tagP = identifierParser +const idP = P.prefixed(P.string('#'), identifierParser) +const classP = P.map( + P.prefixed(P.string('.'), identifierParser), + SelectorComp.ClassName, +) +const valueP = P.or([identifierParser, stringLiteralParser, numberParser]) +const attrP = P.map( + P.between( + P.string('['), + P.zip2(P.suffixed(identifierParser, P.string('=')), valueP), + P.string(']'), + ), + SelectorComp.Attr, +) + +const selectorExprParser: P.Parser<Expr> = (input: string) => + P.map( + P.zip2(P.zip2(P.optional(tagP), idP), P.many0(P.or([classP, attrP]))), + ([[tag, id], selectors]) => Expr.Selector({ tag, id, selectors }), + )(input) + +const pairExprParser: P.Parser<Expr> = (input: string) => + P.map( + P.zip2( + P.suffixed(varIdentifierParser, consumeWhitespace(P.string(':'))), + exprParser, + ), + ([key, value]) => Expr.Pair({ key, value }), + )(input) + const exprParser: P.Parser<Expr> = P.or([ - stringLiteralParser, - varIdentifierExprParser, + stringLiteralExprParser, numberExprParser, callExprParser, + selectorExprParser, identifierExprParser, + pairExprParser, + varIdentifierExprParser, ]) const multiExprParser = P.many1(exprParser) diff --git a/tests/parser.spec.ts b/tests/parser.spec.ts index 41fafa3..b03e073 100644 --- a/tests/parser.spec.ts +++ b/tests/parser.spec.ts @@ -1,4 +1,4 @@ -import { Expr, parse } from '../src/parser' +import { Expr, SelectorComp, parse } from '../src/parser' describe('parser', () => { it('should parse function call', () => { @@ -44,13 +44,13 @@ describe('parser', () => { }) it('should parse string literal', () => { - expect(parse(`"hello world toodles \' nice single quote there"`)).toEqual([ - Expr.LiteralString(`hello world toodles \' nice single quote there`), + expect(parse(`"hello world toodles ' nice single quote there"`)).toEqual([ + Expr.LiteralString(`hello world toodles ' nice single quote there`), ]) - expect(parse(` 'hello world toodles \" nice double quote there' `)).toEqual( - [Expr.LiteralString(`hello world toodles \" nice double quote there`)], - ) + expect(parse(` 'hello world toodles " nice double quote there' `)).toEqual([ + Expr.LiteralString(`hello world toodles " nice double quote there`), + ]) }) it('should parse var identifiers', () => { @@ -124,4 +124,55 @@ describe('parser', () => { Expr.LiteralNumber({ value: -3.82, unit: 'ms' }), ]) }) + + it('should parse pair and map expressions', () => { + expect(parse(`--hello: "foobar is here"`)).toEqual([ + Expr.Pair({ + key: '--hello', + value: Expr.LiteralString('foobar is here'), + }), + ]) + + expect( + parse(`map(--hello: "foobar is here", --test-var : var(--other-var))`), + ).toEqual([ + Expr.Call({ + name: 'map', + args: [ + Expr.Pair({ + key: '--hello', + value: Expr.LiteralString('foobar is here'), + }), + Expr.Pair({ + key: '--test-var', + value: Expr.Call({ + name: 'var', + args: [Expr.VarIdentifier('--other-var')], + }), + }), + ], + }), + ]) + }) + + it('should parse complex selectors', () => { + expect(parse(`button#something.my-class[hello=world]`)).toEqual([ + Expr.Selector({ + tag: 'button', + id: 'something', + selectors: [ + SelectorComp.ClassName('my-class'), + SelectorComp.Attr(['hello', 'world']), + ], + }), + ]) + + expect(parse(`#something[data-testid="hello world"]`)).toEqual([ + Expr.Selector({ + tag: undefined, + id: 'something', + selectors: [SelectorComp.Attr(['data-testid', 'hello world'])], + }), + ]) + }) }) |
