arktype
arktype copied to clipboard
Template literal
Would allow the equivalent TS syntax:
// inferred as `a${string}z`[]
const t = type("`a${string}z`[]")
Internally should probably compile to a regex.
As an example, would this allow for prettifying of an address type (that parses "221B Baker Street, London, NW1 6XE"
) from something along the line of
const address = type("/^\d{1,3}[A-Z]? [A-Za-z ']+, [A-Za-z \-]+, [A-Z]{2}\d{1,2} \d{1,2}[A-Z]{2}$/")
to
const { address } = scope({
houseNumber: /^\d{1,3}[A-Z]?$/,
road: /^[A-Za-z ']+$/,
city: /^[A-Za-z \-]+$/,
postCode: /^[A-Z]{2}\d{1,2} \d{1,2}[A-Z]{2}$/,
address: `${houseNumber} ${road}, ${city}, ${postCode}`
}).compile();
?
If so, this is very much something I'd like to see!
Would also be cool to store the values obtained from the template literals into a tuple, and then have something like
...
const address = type(`${houseNumber} ${road}, ${city}, ${postCode}`)
const myAddress = address.assert("221B Baker Street, London, NW1 6XE")
console.log(`My house number is ${myAddress.literals[0]} and my post code is ${myAddress.literals[3]}!`)
where the .literals
(or w/e it ends up being called) would contain 4 items (the house number, road, city, and post code).
I'd also note that it would be nice to be able to use template literals like this within regexes, as there's certain complex ArkType types that simply can't be replicated in regex, such as morphs. This isn't a huge priority though, as from what I can tell, you can always convert RegEx -> ArkType type (even if you have to use a morph or something), and so not being able to do ArkType type -> RegEx isn't huge issue since you can just change your RegEx to be an ArkType type.
Yes, these kinds of expressions would work, and I hadn't even considered them! It would combine particularly well with https://github.com/arktypeio/arktype/issues/695, as the inferred types could propagate through the template literal!
'123 456'.match(/^(?<a>\d+) (?<b>\d+)$/)
(3) ['123 456', '123', '456', index: 0, input: '456 456', groups: { a: '123', b: '456' }]
Implementation proposals:
- Types inside literal should have
Type<T>{ __unwrappedRegexString }
which is used to generate regexes for matching. If it's not provided,/[^]*?/
is used, but this requires extra limitation of not allowing/.*?.*?/
as that may lead to DDOS
- examples:
let regexes = { 'number': '\d+(\.\d+)?(e[+-]?\d+)?', 'integer': '\d+', 'number%1': '\d+', '1<alpha<7': '[a-zA-Z]{2, 6}', }
-
Types inside literal should have
Type<T>{ __fromString(data:
${T | unknown}): Maybe<T> }
. To check the type, value is firstly parsed by<type>.__fromString(data)
, and then checked- examples :
{ number: s=>+s, string: s=>s }
- examples :
-
Literals are using the following parsing algorithm, using either named match groups or unnamed match tuples:
let x = type('1<=alpha<=5'); type X = typeof x['infer']
let y = type('integer'); type Y = typeof y['infer']
let xy: MorphType<`${X};${Y};`, {x: X, y: Y}> = scope({x, y}).__templateLiteral('`${x};${y};`')
function __parse__template__morph() {
let xR: '[a-zA-Z]{1,5}' = x.__innerRegexString()
let yR: '\\d+' = y.__innerRegexString()
const regexToGroup = (name: 'x', regex: '[a-zA-Z]{1,5}') => `(?<${name}>${regex})`
let resultRegex: /^(?<x>[a-zA-Z]{1,5});(?<y>\d+);$/ =
new RegExp('^' + __noimpl_merge(a, [regexToGroup('x', xR), regexToGroup('y', yR)]) + '$');
let match = 'a 123'.match(resultRegex) // ['a 123', 'a', '123', input: 'a 123', groups: { x: 'a', y: '123' } ]
return type({x, y}).validate(match.groups as {x: 'a', y: '123'})
}