parser.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. import type {
  2. TokenType,
  3. Token
  4. } from "./type";
  5. const keywordsByLanguage: Record<string, string[]> = {
  6. "csharp": [
  7. "protected",
  8. "namespace",
  9. "interface",
  10. "internal",
  11. "readonly",
  12. "override",
  13. "abstract",
  14. "continue",
  15. "private",
  16. "virtual",
  17. "decimal",
  18. "finally",
  19. "public",
  20. "static",
  21. "sealed",
  22. "double",
  23. "ushort",
  24. "string",
  25. "object",
  26. "switch",
  27. "return",
  28. "typeof",
  29. "using",
  30. "float",
  31. "sbyte",
  32. "short",
  33. "ulong",
  34. "class",
  35. "while",
  36. "break",
  37. "catch",
  38. "throw",
  39. "false",
  40. "bool",
  41. "byte",
  42. "char",
  43. "long",
  44. "uint",
  45. "this",
  46. "base",
  47. "else",
  48. "case",
  49. "null",
  50. "true",
  51. "void",
  52. "get",
  53. "set",
  54. "int",
  55. "new",
  56. "for",
  57. "try",
  58. "if",
  59. "do"
  60. ],
  61. "typescript": [
  62. "implements",
  63. "instanceof",
  64. "interface",
  65. "undefined",
  66. "namespace",
  67. "continue",
  68. "function",
  69. "extends",
  70. "default",
  71. "finally",
  72. "unknown",
  73. "boolean",
  74. "switch",
  75. "return",
  76. "import",
  77. "export",
  78. "typeof",
  79. "number",
  80. "string",
  81. "symbol",
  82. "object",
  83. "while",
  84. "break",
  85. "const",
  86. "class",
  87. "async",
  88. "await",
  89. "catch",
  90. "throw",
  91. "super",
  92. "false",
  93. "never",
  94. "else",
  95. "case",
  96. "type",
  97. "from",
  98. "this",
  99. "void",
  100. "null",
  101. "true",
  102. "enum",
  103. "for",
  104. "let",
  105. "var",
  106. "try",
  107. "new",
  108. "any",
  109. "if",
  110. "do"
  111. ],
  112. "tsx": [
  113. "implements",
  114. "instanceof",
  115. "interface",
  116. "undefined",
  117. "namespace",
  118. "continue",
  119. "function",
  120. "extends",
  121. "default",
  122. "finally",
  123. "unknown",
  124. "boolean",
  125. "switch",
  126. "return",
  127. "import",
  128. "export",
  129. "typeof",
  130. "number",
  131. "string",
  132. "symbol",
  133. "object",
  134. "while",
  135. "break",
  136. "const",
  137. "class",
  138. "async",
  139. "await",
  140. "catch",
  141. "throw",
  142. "super",
  143. "false",
  144. "never",
  145. "else",
  146. "case",
  147. "type",
  148. "from",
  149. "this",
  150. "void",
  151. "null",
  152. "true",
  153. "enum",
  154. "for",
  155. "let",
  156. "var",
  157. "try",
  158. "new",
  159. "any",
  160. "if",
  161. "do"
  162. ],
  163. "javascript": [
  164. "implements",
  165. "instanceof",
  166. "interface",
  167. "undefined",
  168. "continue",
  169. "function",
  170. "extends",
  171. "default",
  172. "finally",
  173. "switch",
  174. "return",
  175. "import",
  176. "export",
  177. "typeof",
  178. "while",
  179. "break",
  180. "const",
  181. "class",
  182. "async",
  183. "await",
  184. "catch",
  185. "throw",
  186. "super",
  187. "false",
  188. "else",
  189. "case",
  190. "type",
  191. "from",
  192. "this",
  193. "void",
  194. "null",
  195. "true",
  196. "for",
  197. "let",
  198. "var",
  199. "try",
  200. "new",
  201. "if",
  202. "do"
  203. ],
  204. "jsx": [
  205. "implements",
  206. "instanceof",
  207. "interface",
  208. "undefined",
  209. "continue",
  210. "function",
  211. "extends",
  212. "default",
  213. "finally",
  214. "switch",
  215. "return",
  216. "import",
  217. "export",
  218. "typeof",
  219. "while",
  220. "break",
  221. "const",
  222. "class",
  223. "async",
  224. "await",
  225. "catch",
  226. "throw",
  227. "super",
  228. "false",
  229. "else",
  230. "case",
  231. "type",
  232. "from",
  233. "this",
  234. "void",
  235. "null",
  236. "true",
  237. "for",
  238. "let",
  239. "var",
  240. "try",
  241. "new",
  242. "if",
  243. "do"
  244. ],
  245. "sql": [
  246. "REFERENCES",
  247. "PRIMARY",
  248. "FOREIGN",
  249. "BETWEEN",
  250. "SELECT",
  251. "INSERT",
  252. "VALUES",
  253. "UPDATE",
  254. "DELETE",
  255. "CREATE",
  256. "HAVING",
  257. "OFFSET",
  258. "EXISTS",
  259. "WHERE",
  260. "TABLE",
  261. "ALTER",
  262. "INDEX",
  263. "INNER",
  264. "RIGHT",
  265. "OUTER",
  266. "GROUP",
  267. "ORDER",
  268. "LIMIT",
  269. "FROM",
  270. "INTO",
  271. "DROP",
  272. "JOIN",
  273. "LEFT",
  274. "FULL",
  275. "NULL",
  276. "LIKE",
  277. "SET",
  278. "KEY",
  279. "AND",
  280. "NOT",
  281. "ON",
  282. "BY",
  283. "AS",
  284. "OR",
  285. "IS",
  286. "IN"
  287. ],
  288. "bash": [
  289. "install",
  290. "remove",
  291. "docker",
  292. "export",
  293. "source",
  294. "return",
  295. "start",
  296. "build",
  297. "while",
  298. "yarn",
  299. "pnpm",
  300. "test",
  301. "lint",
  302. "echo",
  303. "sudo",
  304. "brew",
  305. "bash",
  306. "then",
  307. "done",
  308. "esac",
  309. "else",
  310. "elif",
  311. "case",
  312. "npm",
  313. "npx",
  314. "bun",
  315. "add",
  316. "run",
  317. "dev",
  318. "apt",
  319. "git",
  320. "zsh",
  321. "for",
  322. "cd",
  323. "ls",
  324. "sh",
  325. "fi",
  326. "if",
  327. "do",
  328. "in"
  329. ],
  330. "python": [
  331. "nonlocal",
  332. "continue",
  333. "finally",
  334. "lambda",
  335. "global",
  336. "import",
  337. "except",
  338. "return",
  339. "assert",
  340. "False",
  341. "yield",
  342. "class",
  343. "raise",
  344. "while",
  345. "break",
  346. "async",
  347. "await",
  348. "elif",
  349. "pass",
  350. "None",
  351. "True",
  352. "with",
  353. "from",
  354. "else",
  355. "def",
  356. "and",
  357. "not",
  358. "del",
  359. "try",
  360. "for",
  361. "or",
  362. "is",
  363. "in",
  364. "as",
  365. "if"
  366. ]
  367. };
  368. const allKeywordsList = [
  369. "implements",
  370. "instanceof",
  371. "REFERENCES",
  372. "protected",
  373. "namespace",
  374. "interface",
  375. "undefined",
  376. "internal",
  377. "readonly",
  378. "override",
  379. "abstract",
  380. "continue",
  381. "function",
  382. "nonlocal",
  383. "private",
  384. "virtual",
  385. "decimal",
  386. "finally",
  387. "extends",
  388. "default",
  389. "unknown",
  390. "boolean",
  391. "PRIMARY",
  392. "FOREIGN",
  393. "BETWEEN",
  394. "install",
  395. "public",
  396. "static",
  397. "sealed",
  398. "double",
  399. "ushort",
  400. "string",
  401. "object",
  402. "switch",
  403. "return",
  404. "typeof",
  405. "import",
  406. "export",
  407. "number",
  408. "symbol",
  409. "SELECT",
  410. "INSERT",
  411. "VALUES",
  412. "UPDATE",
  413. "DELETE",
  414. "CREATE",
  415. "HAVING",
  416. "OFFSET",
  417. "EXISTS",
  418. "remove",
  419. "docker",
  420. "source",
  421. "lambda",
  422. "global",
  423. "except",
  424. "assert",
  425. "using",
  426. "float",
  427. "sbyte",
  428. "short",
  429. "ulong",
  430. "class",
  431. "while",
  432. "break",
  433. "catch",
  434. "throw",
  435. "false",
  436. "const",
  437. "async",
  438. "await",
  439. "super",
  440. "never",
  441. "WHERE",
  442. "TABLE",
  443. "ALTER",
  444. "INDEX",
  445. "INNER",
  446. "RIGHT",
  447. "OUTER",
  448. "GROUP",
  449. "ORDER",
  450. "LIMIT",
  451. "start",
  452. "build",
  453. "False",
  454. "yield",
  455. "raise",
  456. "bool",
  457. "byte",
  458. "char",
  459. "long",
  460. "uint",
  461. "this",
  462. "base",
  463. "else",
  464. "case",
  465. "null",
  466. "true",
  467. "void",
  468. "type",
  469. "from",
  470. "enum",
  471. "FROM",
  472. "INTO",
  473. "DROP",
  474. "JOIN",
  475. "LEFT",
  476. "FULL",
  477. "NULL",
  478. "LIKE",
  479. "yarn",
  480. "pnpm",
  481. "test",
  482. "lint",
  483. "echo",
  484. "sudo",
  485. "brew",
  486. "bash",
  487. "then",
  488. "done",
  489. "esac",
  490. "elif",
  491. "pass",
  492. "None",
  493. "True",
  494. "with",
  495. "get",
  496. "set",
  497. "int",
  498. "new",
  499. "for",
  500. "try",
  501. "let",
  502. "var",
  503. "any",
  504. "SET",
  505. "KEY",
  506. "AND",
  507. "NOT",
  508. "npm",
  509. "npx",
  510. "bun",
  511. "add",
  512. "run",
  513. "dev",
  514. "apt",
  515. "git",
  516. "zsh",
  517. "def",
  518. "and",
  519. "not",
  520. "del",
  521. "if",
  522. "do",
  523. "ON",
  524. "BY",
  525. "AS",
  526. "OR",
  527. "IS",
  528. "IN",
  529. "cd",
  530. "ls",
  531. "sh",
  532. "fi",
  533. "in",
  534. "or",
  535. "is",
  536. "as"
  537. ];
  538. const tokenRegexCache: Record<string, RegExp> = {};
  539. const getTokenRegex = (language?: string): RegExp => {
  540. const cacheKey = language || "default";
  541. const cachedRegex = tokenRegexCache[cacheKey];
  542. if (cachedRegex) {
  543. return cachedRegex;
  544. }
  545. let keywordList = allKeywordsList;
  546. if (language) {
  547. const langKeywords = keywordsByLanguage[language.toLowerCase()];
  548. if (langKeywords) {
  549. keywordList = langKeywords;
  550. }
  551. }
  552. const keywordsRegexStr = "(?<!\\.)\\b(?:" + keywordList.join("|") + ")\\b";
  553. const keywordControlRegexStr = "(?<!\\.)\\b(?:return|if|else|for|while|try|catch|finally|switch|case|break|continue|throw|yield)\\b";
  554. let commentRegexStr = "//.*|/\\*[\\s\\S]*?\\*/|#.*|<!--[\\s\\S]*?-->";
  555. if (language) {
  556. switch (language.toLowerCase()) {
  557. case "bash":
  558. case "python":
  559. commentRegexStr = "#.*";
  560. break;
  561. case "sql":
  562. commentRegexStr = "--.*|/\\*[\\s\\S]*?\\*/";
  563. break;
  564. case "html":
  565. case "xml":
  566. commentRegexStr = "<!--[\\s\\S]*?-->";
  567. break;
  568. case "javascript":
  569. case "typescript":
  570. case "jsx":
  571. case "tsx":
  572. case "csharp":
  573. commentRegexStr = "//.*|/\\*[\\s\\S]*?\\*/";
  574. break;
  575. }
  576. }
  577. let bashTextRegexStr = "";
  578. if (language && language.toLowerCase() === "bash") {
  579. bashTextRegexStr = "(?<text>(?:[a-zA-Z0-9_@]+[.:/-]+)+[a-zA-Z0-9_@]+)|";
  580. }
  581. const regexStr = "(?<comment>" + commentRegexStr + ")|" +
  582. '(?<string>(["\\\'`])(?:(?=(\\\\?))\\4.)*?\\3)|' +
  583. bashTextRegexStr +
  584. "(?<keywordControl>" + keywordControlRegexStr + ")|" +
  585. "(?<keyword>" + keywordsRegexStr + ")|" +
  586. "(?<htmlTag>(?<=<\\/?)[a-z][a-zA-Z0-9-]*\\b)|" +
  587. "(?<component>\\b[A-Z][a-zA-Z0-9_$]*\\b)|" +
  588. "(?<function>\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*\\()|\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*[:=]\\s*(?:async\\s+)?(?:\\([^)]*\\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\\s*=>)|\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*[:=]\\s*function\\b))|" +
  589. "(?<property>\\b[a-zA-Z_$][a-zA-Z0-9_$]*(?=\\s*[:=]))|" +
  590. "(?<variable>\\b[a-zA-Z_$][a-zA-Z0-9_$]*\\b)|" +
  591. "(?<number>\\b\\d+(?:\\.\\d+)?\\b)|" +
  592. "(?<arrow>=>)|" +
  593. "(?<punctuationTag>[<>/])|" +
  594. "(?<operator>[+\\-*=!&|%^~]+)|" +
  595. "(?<punctuation>[{}()\\[\\].,;:])";
  596. const regex = new RegExp(regexStr, "g");
  597. tokenRegexCache[cacheKey] = regex;
  598. return regex;
  599. };
  600. export const parseCode = (code: string, language?: string): Token[] => {
  601. const tokens: Token[] = [];
  602. let lastIndex = 0;
  603. const tokenRegex = getTokenRegex(language);
  604. tokenRegex.lastIndex = 0;
  605. let match: RegExpExecArray | null;
  606. while ((match = tokenRegex.exec(code)) !== null) {
  607. if (match.index > lastIndex) {
  608. tokens.push({
  609. type: "text",
  610. value: code.substring(lastIndex, match.index)
  611. });
  612. }
  613. const groups = match.groups || {};
  614. const type = Object.keys(groups).find(key => groups[key] !== undefined) as TokenType;
  615. if (type) {
  616. tokens.push({
  617. type,
  618. value: groups[type] as string
  619. });
  620. }
  621. lastIndex = match.index + match[0].length;
  622. }
  623. if (lastIndex < code.length) {
  624. tokens.push({
  625. type: "text",
  626. value: code.substring(lastIndex)
  627. });
  628. }
  629. return tokens;
  630. };
  631. export const splitTokensIntoLines = (tokens: Token[]): Token[][] => {
  632. const lines: Token[][] = [];
  633. let currentLine: Token[] = [];
  634. tokens.forEach((token) => {
  635. if (token.value.includes("\n")) {
  636. const parts = token.value.split("\n");
  637. parts.forEach((part, index) => {
  638. if (part.length > 0) {
  639. currentLine.push({
  640. ...token,
  641. value: part
  642. });
  643. }
  644. if (index < parts.length - 1) {
  645. lines.push(currentLine);
  646. currentLine = [];
  647. }
  648. });
  649. } else {
  650. currentLine.push(token);
  651. }
  652. });
  653. lines.push(currentLine);
  654. return lines;
  655. };
  656. export const getLineIndentation = (lineTokens: Token[]): number => {
  657. let indentation = 0;
  658. for (const token of lineTokens) {
  659. if (token.type === "text") {
  660. const match = token.value.match(/^(\s+)/);
  661. if(match) {
  662. const matchLen = (match[1] as string).length;
  663. indentation += matchLen;
  664. if(matchLen !== token.value.length) {
  665. break;
  666. }
  667. } else if (token.value.length > 0) {
  668. break;
  669. }
  670. } else {
  671. break;
  672. }
  673. }
  674. return indentation;
  675. };