미라클 글쓰기 6일차

Type Challenge를 하면서 배운 것들을 어디에 응용해 볼까 하다가
간단한 Rule DSL을 Template Literal Type의 union으로 정의해보자는 아이디어가 떠올랐다.
막상 작성해 보니 그리 좋은 아이디어가 아니었던 것 같고, 보완할 것들이 많이 눈에 띄지만
오늘은 바빠서 미라클 글쓰기 할 시간이 별로 없다보니 이정도로 마무리하고 다음에 더 보완을 해보도록 하자.

기본적인 아이디어는 DSL의 문법을 template literal type을 통해 체크한다는 것이다.
include some [string, pattern, exists] // string, pattern, exists 같은 패턴이 주어진 string에 존재하는지 확인
이런 식의 Rule DSL의 문법 체크를 typescript의 type system을 써볼까 했고,
처음엔 괜찮은 아이디어 같았는데 다음과 같은 문제가 있다.

  • DSL의 문법에 융통성이 없다.
  • 어차피 union type 을 string parse 해야 하기 때문에 compile time type check가 별 유용성이 없다.
  • 복잡한 연산자(우선순위, 논리 연산자 등) 추가하려면 파서 라이브러리를 쓰는게 낫다.

import { match } from 'ts-pattern';

// DSL structure
type RuleOperator = 'include' | 'exclude';
type RuleQualifier = 'all' | 'some';
type RulePattern = string[];
type RuleDSLWithArgs = `${RuleOperator} ${RuleQualifier} [${RulePattern[number]}]`;
type RuleDSLWithoutArgs = `${RuleOperator} all`;
type RuleDSL = RuleDSLWithArgs | RuleDSLWithoutArgs;

// Simple Rule Parser
class RuleParser {
    parse(ruleString: RuleDSL): ParsedRule {
        const [operator, qualifier, ...rest] = ruleString.split(' ');
        const patterns = rest.length > 0 ? rest.map(iter => iter.replace(/[\[\],]/g, '')) : [];

        return {
            operator: operator as RuleOperator,
            qualifier: qualifier as RuleQualifier,
            patterns: patterns
        };
    }
}

type ParsedRule = {
    operator: RuleOperator;
    qualifier: RuleQualifier;
    patterns: string[];
}

// Rule Executor
class RuleExecutor {
    execute(rule: ParsedRule, data: string[]): string[] {
        return match(rule)
            .with({ operator: 'include', qualifier: 'all' }, () => data)
            .with({ operator: 'exclude', qualifier: 'all' }, () => [])
            .with({ operator: 'include', qualifier: 'some'},
                ({ patterns }) => data.filter(item => patterns.includes(item)))
            .with({ operator: 'exclude', qualifier: 'some' },
                ({ patterns }) => data.filter(item => !patterns.includes(item)))
            .otherwise(() => []);
    }
}

// Example
const parser = new RuleParser();
const executor = new RuleExecutor();

const includeRule: RuleDSL = 'include some [example, test, sample]';
const excludeRule: RuleDSL = 'exclude some [sample]';
const includeAll: RuleDSL = 'include all';
let parsedRule = parser.parse(includeRule);
const data = ['example1', 'test', 'example2', 'sample'];

let result = executor.execute(parsedRule, data);
console.log(result); // ['example1', 'test']

parsedRule = parser.parse(excludeRule);
result = executor.execute(parsedRule, data);
console.log(result); // ['example1', 'test', 'example2']

parsedRule = parser.parse(includeAll);
result = executor.execute(parsedRule, data);
console.log(result); // ['example1', 'test', 'example2', 'sample']

댓글 남기기

인기 검색어

01010011에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기