Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // === TOKENIZER ===
- function tokenize(input) {
- const tokens = [];
- let i = 0;
- function match(str) {
- return input.startsWith(str, i);
- }
- function advance(n) {
- i += n;
- }
- function push(type, value = null) {
- tokens.push({ type, value: value ?? type });
- }
- while (i < input.length) {
- if (match('\n*\n')) {
- push('T_CK', '\n*\n');
- advance(3);
- } else if (match('@sel(')) {
- push('T_SEL_OPEN', '@sel(');
- advance(5);
- } else if (match('@ed()')) {
- push('T_EDIT', '@ed()');
- advance(6);
- } else if (match(')')) {
- push('T_PAREN_CLOSE', ')');
- advance(1);
- } else if (match('/')) {
- push('T_SLASH', '/');
- advance(1);
- } else {
- const start = i;
- while (
- i < input.length &&
- !match('\n*\n') &&
- !match('@sel(') &&
- !match('@ed()') &&
- !match(')') &&
- !match('/')
- ) {
- i++;
- }
- const text = input.slice(start, i).trim();
- if (text) {
- push('T_TEXT', text);
- }
- }
- }
- return tokens;
- }
- // === PARSER ===
- function parse(tokens) {
- let pos = 0;
- let clCounter = 1;
- let eCounter = 1;
- function peek(type) {
- return tokens[pos]?.type === type;
- }
- function consume(type) {
- const token = tokens[pos];
- if (!token || token.type !== type) {
- throw new Error(`Expected ${type}, got ${token?.type} at position ${pos}`);
- }
- pos++;
- return token;
- }
- function parseText() {
- const token = consume('T_TEXT');
- return { type: 'text', value: token.value };
- }
- function parseEdit() {
- consume('T_EDIT');
- return { type: 'edit', id: `e-${eCounter++}` };
- }
- function parseDropdown() {
- consume('T_SEL_OPEN');
- const options = [];
- options.push(consume('T_TEXT').value);
- while (peek('T_SLASH')) {
- consume('T_SLASH');
- options.push(consume('T_TEXT').value);
- }
- consume('T_PAREN_CLOSE');
- return { type: 'dropdown', id: `e-${eCounter++}`, options };
- }
- function parseE() {
- if (peek('T_SEL_OPEN')) return parseDropdown();
- if (peek('T_EDIT')) return parseEdit();
- if (peek('T_TEXT')) return parseText();
- throw new Error(`Unexpected token at position ${pos}: ${tokens[pos]?.type}`);
- }
- function parseExpr() {
- const elements = [];
- while (
- peek('T_TEXT') ||
- peek('T_EDIT') ||
- peek('T_SEL_OPEN')
- ) {
- elements.push(parseE());
- }
- return elements;
- }
- function parseCl() {
- consume('T_CK');
- const items = parseExpr();
- return {
- id: `cl-${clCounter++}`,
- checked: false,
- items
- };
- }
- function parseProgram() {
- const result = [];
- while (peek('T_CK')) {
- result.push(parseCl());
- }
- return result;
- }
- return parseProgram();
- }
- // === EXAMPLE INPUT (Diary-style, bilingual) ===
- const input = `
- *
- 今天我感覺 @sel(開心/普通/沮喪),也許是因為我終於完成了那個專案 @ed(),雖然還有一些小地方要修正 @ed()。
- Today I felt @sel(happy/neutral/down), maybe because I finally finished that project @ed(), although a few things still need fixing @ed()。
- *
- 我選擇了一本新書 @sel(小說/歷史/自我成長) 開始閱讀,名字叫 @ed(),它讓我重新思考一些事情 @ed()。
- I picked up a new book @sel(fiction/history/self-help) to read, titled @ed(), and it made me reflect on a few things @ed()。
- *
- 晚餐我做了 @sel(義大利麵/壽司/沙拉),因為我最近在嘗試更健康的飲食 @ed(),也開始記錄每天的熱量攝取 @ed()。
- For dinner, I made @sel(pasta/sushi/salad), since I’m trying to eat healthier lately @ed(), and started tracking my daily calories @ed()。
- *
- 今天我和 @ed() 一起散步,天氣是 @sel(晴天/陰天/下雨),我們聊了很多最近的煩惱 @ed()。
- I went for a walk with @ed() today — the weather was @sel(sunny/cloudy/rainy), and we talked about a lot of recent worries @ed()。
- *
- 睡前我花了一點時間做計畫,明天我打算完成 @ed(),而且希望心情是 @sel(積極/平穩/專注)。
- Before bed, I spent some time planning — tomorrow I hope to finish @ed(), and be in a @sel(positive/calm/focused) mindset。
- `;
- // === USAGE ===
- const tokens = tokenize(input);
- const result = parse(tokens);
- console.log(JSON.stringify(result, null, 2));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement