aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval.ts1
-rw-r--r--src/parser.ts62
-rw-r--r--tests/parser.spec.ts63
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'])],
+ }),
+ ])
+ })
})