131 lines
3.6 KiB
JavaScript
131 lines
3.6 KiB
JavaScript
|
// lexer.mjs
|
||
|
import { token } from './token.mjs';
|
||
|
|
||
|
export class Lexer {
|
||
|
constructor(string) {
|
||
|
this.string = string;
|
||
|
this.pos = 0;
|
||
|
}
|
||
|
|
||
|
advance() {
|
||
|
return this.string[this.pos++];
|
||
|
}
|
||
|
|
||
|
lookAhead() {
|
||
|
return this.string[this.pos];
|
||
|
}
|
||
|
|
||
|
trimQuotes(str) {
|
||
|
return str.replace(/^['"]|['"]$/g, ''); // Remove surrounding quotes
|
||
|
}
|
||
|
|
||
|
lex() {
|
||
|
let text = '';
|
||
|
while (true) {
|
||
|
let t = this.advance();
|
||
|
let tokenResult = ''; // Renamed to avoid conflict with outer token variable
|
||
|
switch (t) {
|
||
|
case '<':
|
||
|
if (this.lookAhead() === '/') {
|
||
|
tokenResult = this.handleEndTag();
|
||
|
} else {
|
||
|
tokenResult = this.handleStartTag();
|
||
|
}
|
||
|
break;
|
||
|
case '\n':
|
||
|
break;
|
||
|
case ' ':
|
||
|
if (text !== '') {
|
||
|
text += t;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case undefined:
|
||
|
if (this.pos >= this.string.length) {
|
||
|
tokenResult = [token.eof, 'eof', []];
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
text += t;
|
||
|
tokenResult = this.handleTextTag(text);
|
||
|
break;
|
||
|
}
|
||
|
this.string = this.string.slice(this.pos);
|
||
|
this.pos = 0;
|
||
|
if (tokenResult !== '') {
|
||
|
return tokenResult;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
handleStartTag() {
|
||
|
let idx = this.string.indexOf('>');
|
||
|
if (idx === -1) {
|
||
|
throw new Error('parse err! miss match ' + '>');
|
||
|
}
|
||
|
let str = this.string.slice(this.pos, idx);
|
||
|
let s = '';
|
||
|
if (str.includes(' ')) {
|
||
|
s = str.split(' ')[0]; // Fixed splitting logic
|
||
|
} else {
|
||
|
s = str;
|
||
|
}
|
||
|
let type = s.slice(1);
|
||
|
this.pos += type.length;
|
||
|
let props = this.handlePropTag();
|
||
|
this.advance();
|
||
|
return [token.startTag, type, props];
|
||
|
}
|
||
|
|
||
|
handlePropTag() {
|
||
|
let idx = this.string.indexOf('>');
|
||
|
if (idx == -1) {
|
||
|
throw new Error('parse err! miss match ' + '>');
|
||
|
}
|
||
|
let string = this.string.slice(this.pos, idx);
|
||
|
let pm = [];
|
||
|
if (string.trim() != '') { // Checking if string is not empty
|
||
|
let props = string.split(' ');
|
||
|
pm = props.filter((prop) => {
|
||
|
return prop != '';
|
||
|
}).map((prop) => {
|
||
|
let kv = prop.split('=');
|
||
|
let o = {};
|
||
|
// Ensure kv[1] exists before calling trimQuotes
|
||
|
if (kv[1]) {
|
||
|
o[kv[0]] = this.trimQuotes(kv[1]);
|
||
|
} else {
|
||
|
o[kv[0]] = ''; // Or assign a default value
|
||
|
}
|
||
|
return o;
|
||
|
});
|
||
|
this.pos += string.length;
|
||
|
}
|
||
|
|
||
|
return pm;
|
||
|
}
|
||
|
|
||
|
|
||
|
handleEndTag() {
|
||
|
this.advance();
|
||
|
let idx = this.string.indexOf('>');
|
||
|
let type = this.string.slice(this.pos, idx);
|
||
|
this.pos += type.length;
|
||
|
if (this.advance() !== '>') {
|
||
|
throw new Error('parse err! miss match ' + '>');
|
||
|
}
|
||
|
return [token.endTag, type, []];
|
||
|
}
|
||
|
|
||
|
handleTextTag(text) {
|
||
|
let t = text.trim();
|
||
|
if (this.lookAhead() === '<') {
|
||
|
return [token.text, t, []];
|
||
|
} else {
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
}
|