[WIP] Start implementing execution in the new TS version
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Clear extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@@ -13,4 +15,10 @@ export class Clear extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'clear'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.replaceWith('')))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,40 @@ import {
|
||||
UnexpectedEndOfInputError
|
||||
} from "../parse.js";
|
||||
import {Awaitable, ElementType} from "../../util/types.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
|
||||
export type StrLVal = { term: 'variable', name: string }
|
||||
|
||||
export type StrTerm =
|
||||
export type StrRVal =
|
||||
{ term: 'string', value: string, literal?: true }
|
||||
| StrLVal
|
||||
| { term: 'int', value: number }
|
||||
|
||||
export type StrTerm = StrRVal | StrLVal
|
||||
|
||||
export const isStrRVal = (term: StrTerm): term is StrRVal =>
|
||||
term.term === 'string' || term.term === 'int'
|
||||
|
||||
export const unwrapString = (term: StrRVal): string => {
|
||||
if ( term.term === 'int' ) {
|
||||
return String(term.value)
|
||||
}
|
||||
|
||||
return term.value
|
||||
}
|
||||
|
||||
export const unwrapInt = (term: StrRVal): number => {
|
||||
if ( term.term !== 'int' ) {
|
||||
throw new Error('Unexpected error: cannot unwrap term: is not an int')
|
||||
}
|
||||
|
||||
return term.value
|
||||
}
|
||||
|
||||
export const wrapString = (str: string): StrRVal => ({
|
||||
term: 'string',
|
||||
value: str,
|
||||
literal: true,
|
||||
})
|
||||
|
||||
export class ParseContext {
|
||||
constructor(
|
||||
@@ -53,6 +81,11 @@ export class ParseContext {
|
||||
return { term: 'variable', name: input.value }
|
||||
}
|
||||
|
||||
// Check if the token is a valid integer:
|
||||
if ( /^-?[1-9][0-9]*$/.test(input.value) ) {
|
||||
return { term: 'int', value: parseInt(input.value, 10) }
|
||||
}
|
||||
|
||||
// Otherwise, parse it as a string literal:
|
||||
return { term: 'string', value: input.value, literal: input.literal }
|
||||
}
|
||||
@@ -100,6 +133,10 @@ export abstract class Command<TData extends CommandData> {
|
||||
|
||||
abstract getDisplayName(): string
|
||||
|
||||
execute(vm: StrVM, data: TData): Awaitable<StrVM> {
|
||||
return vm // fixme: once implemented by all commands, make abstract
|
||||
}
|
||||
|
||||
protected isKeyword(token: LexInput, keyword: string): boolean {
|
||||
return !token.literal && token.value === keyword
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { LexInput } from "../lexer.js";
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {Command, ParseContext, StrTerm, unwrapString} from "./command.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Contains extends Command<{ find: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { find: StrTerm } {
|
||||
@@ -15,4 +17,11 @@ export class Contains extends Command<{ find: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'contains')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.emptyUnlessCondition(s =>
|
||||
s.includes(ctx.resolveString(data.find)))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export type EncloseData = {
|
||||
left?: StrTerm,
|
||||
@@ -21,4 +23,33 @@ export class Enclose extends Command<EncloseData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'enclose')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: EncloseData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx => {
|
||||
const [left, right] = this.determineSurroundingStrings(
|
||||
data.left ? ctx.resolveString(data.left) : undefined,
|
||||
data.right ? ctx.resolveString(data.right) : undefined,
|
||||
)
|
||||
|
||||
return ctx.replaceSubject(sub =>
|
||||
sub.modify(s => `${left}${s}${right}`))
|
||||
})
|
||||
}
|
||||
|
||||
private determineSurroundingStrings(left?: string, right?: string): [string, string] {
|
||||
if ( !left ) {
|
||||
left = '('
|
||||
}
|
||||
|
||||
if ( !right ) {
|
||||
right = ({
|
||||
'(': ')',
|
||||
'[': ']',
|
||||
'{': '}',
|
||||
'<': '>',
|
||||
})[left] ?? left
|
||||
}
|
||||
|
||||
return [left, right]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
|
||||
export class Lower extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@@ -13,4 +15,10 @@ export class Lower extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'lower'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => s.toLowerCase())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export type LSubData = {
|
||||
offset: StrTerm,
|
||||
@@ -21,4 +23,14 @@ export class LSub extends Command<LSubData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'lsub')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
const offset = ctx.resolveInt(data.offset)
|
||||
const length = data.length ? ctx.resolveInt(data.length) : s.length
|
||||
return s.slice(offset, offset + length)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { LexInput } from "../lexer.js";
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Missing extends Command<{ find: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { find: StrTerm } {
|
||||
@@ -15,4 +17,11 @@ export class Missing extends Command<{ find: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'missing')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { find: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.emptyWhenCondition(s =>
|
||||
s.includes(ctx.resolveString(data.find)))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Prefix extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
@@ -15,4 +17,10 @@ export class Prefix extends Command<{ with: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'prefix')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => `${ctx.resolveString(data.with)}${s}`)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export const QUOTEMARKS = ['"', '\'', '`']
|
||||
|
||||
export const stripQuotemarkLayer = (s: string, marks?: string[]): string => {
|
||||
if ( !marks ) {
|
||||
marks = QUOTEMARKS
|
||||
}
|
||||
|
||||
for ( const mark of marks ) {
|
||||
if ( !s.startsWith(mark) || !s.endsWith(mark) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
s = s.substring(mark.length, s.length - mark.length)
|
||||
break
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
export class Quote extends Command<{ with?: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||
@@ -15,4 +36,18 @@ export class Quote extends Command<{ with?: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'quote')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
let quote = '\''
|
||||
if ( data.with ) {
|
||||
quote = ctx.resolveString(data.with)
|
||||
}
|
||||
|
||||
s = stripQuotemarkLayer(s)
|
||||
return `${quote}${s}${quote}`
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
import {LSubData} from "./lsub.js";
|
||||
|
||||
export type RSubData = {
|
||||
offset: StrTerm,
|
||||
@@ -21,4 +24,18 @@ export class RSub extends Command<RSubData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'rsub')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: LSubData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
const offset = ctx.resolveInt(data.offset)
|
||||
const length = data.length ? ctx.resolveInt(data.length) : s.length
|
||||
return s.split('') // fixme: do the math so we don't have to do this bs
|
||||
.reverse()
|
||||
.slice(offset, offset + length)
|
||||
.reverse()
|
||||
.join('')
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Show extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@@ -13,4 +15,8 @@ export class Show extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'show'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Suffix extends Command<{ with: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with: StrTerm } {
|
||||
@@ -15,4 +17,10 @@ export class Suffix extends Command<{ with: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'suffix')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => `${s}${ctx.resolveString(data.with)}`)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
import {rexEscape} from "../string.js";
|
||||
|
||||
export type TrimData = {
|
||||
type?: 'start'|'end'|'both'|'left'|'right'|'lines',
|
||||
@@ -21,4 +24,32 @@ export class Trim extends Command<TrimData> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'trim')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: TrimData): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
const char = data.char
|
||||
? rexEscape(ctx.resolveString(data.char))
|
||||
: '\\s'
|
||||
|
||||
if ( !data.type || ['start', 'left', 'both'].includes(data.type) ) {
|
||||
const leftRex = new RegExp(`^${char || '\\s'}*`, 's')
|
||||
s = s.replace(leftRex, '')
|
||||
}
|
||||
|
||||
if ( !data.type || ['end', 'right', 'both'].includes(data.type) ) {
|
||||
const rightRex = new RegExp(`${char || '\\s'}*$`, 's')
|
||||
s = s.replace(rightRex, '')
|
||||
}
|
||||
|
||||
if ( data.type === 'lines' ) {
|
||||
s = s.split('\n')
|
||||
.filter(l => l.trim())
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
return s
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {Command, ParseContext, StrTerm} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {stripQuotemarkLayer} from "./quote.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Unquote extends Command<{ with?: StrTerm }> {
|
||||
attemptParse(context: ParseContext): { with?: StrTerm } {
|
||||
@@ -15,4 +18,17 @@ export class Unquote extends Command<{ with?: StrTerm }> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
return this.isKeyword(token, 'unquote')
|
||||
}
|
||||
|
||||
execute(vm: StrVM, data: { with?: StrTerm }): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => {
|
||||
let marks: string[]|undefined = undefined
|
||||
if ( data.with ) {
|
||||
marks = [ctx.resolveString(data.with)]
|
||||
}
|
||||
|
||||
return stripQuotemarkLayer(s, marks)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {Command, ParseContext} from "./command.js";
|
||||
import {LexInput} from "../lexer.js";
|
||||
import {StrVM} from "../vm.js";
|
||||
import {Awaitable} from "../../util/types.js";
|
||||
|
||||
export class Upper extends Command<{}> {
|
||||
isParseCandidate(token: LexInput): boolean {
|
||||
@@ -13,4 +15,10 @@ export class Upper extends Command<{}> {
|
||||
getDisplayName(): string {
|
||||
return 'upper'
|
||||
}
|
||||
|
||||
execute(vm: StrVM): Awaitable<StrVM> {
|
||||
return vm.inPlace(ctx =>
|
||||
ctx.replaceSubject(sub =>
|
||||
sub.modify(s => s.toUpperCase())))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user