templural
Version:
Template function for plural-sensitive formatting
113 lines (87 loc) • 2.59 kB
text/typescript
export type Token = (
[Token.Type.String, string] |
[Token.Type.Integer, string] |
Token.SpecialChar
)
export namespace Token {
export enum Type {
String = 'String',
Integer = 'Integer',
LCurly = '{',
RCurly = '}',
SColon = ';',
Dollar = '$',
Colon = ':',
}
export type SpecialChar = (
Token.Type.LCurly |
Token.Type.RCurly |
Token.Type.SColon |
Token.Type.Dollar |
Token.Type.Colon
)
export function isSpecialChar(char: string): char is SpecialChar {
return char === Token.Type.LCurly || char === Token.Type.RCurly || char === Token.Type.SColon || char === Token.Type.Dollar || char === Token.Type.Colon
}
export function string(value: string): [Type.String, string] {
return [Type.String, value]
}
export function isString(token: Token): token is [Type.String, string] {
return Array.isArray(token) && token[0] === Type.String
}
export function integer(value: string): [Type.Integer, string] {
return [Type.Integer, value]
}
export function isInteger(token: Token): token is [Type.Integer, string] {
return Array.isArray(token) && token[0] === Type.Integer
}
export function toString(token: Token): string {
if (isString(token)) return token[1]
if (isInteger(token)) return token[1].toString()
return token
}
}
export function lex(source: string): Token[] {
return [...new Lexer(source)]
}
class Lexer implements IterableIterator<Token> {
source: string
pos = 0
constructor(source: string) {
this.source = source
}
next(): IteratorResult<Token> {
if (this.ch === undefined) return { done: true, value: null }
if (Token.isSpecialChar(this.ch)) {
const value = this.ch
this.nextPos()
return { value }
}
if (this.ch >= '1' && this.ch <= '9') return { value: this.readInteger() }
return { value: this.readString() }
}
readInteger(): Token {
const { pos } = this
do {
this.nextPos()
} while (this.ch !== undefined && this.ch >= '0' && this.ch <= '9')
if (!Token.isSpecialChar(this.ch)) return this.readString(pos)
return Token.integer(this.source.slice(pos, this.pos))
}
readString(pos = this.pos): Token {
do {
if (this.ch === '\\') this.nextPos()
this.nextPos()
} while (this.ch !== undefined && !Token.isSpecialChar(this.ch))
return Token.string(this.source.slice(pos, this.pos).replace(/\\(.)/g, '$1'))
}
nextPos() {
this.pos++
}
get ch() {
return this.source[this.pos]
}
[Symbol.iterator]() {
return this
}
}