UNPKG

ocat-lang

Version:

A programming language for the web design and development

453 lines (403 loc) 17.2 kB
import { Node, NodeType, RestMode } from "./types"; import { Token, TokenType } from "../lexer"; import { CustomError, OSyntaxError, Warning } from "../../error"; import { NodeAdapter } from "./adapters"; let tokens: Token[]; let currentIndex: number = 0; export const parse = (tokensK: Token[]): Node[] => { const nodes: Node[] = []; tokens = tokensK; let line = 1; currentIndex = 0; while (currentIndex < tokens.length) { let token = getToken(); let node: Node = { type: NodeType.ERR, params: { cause: "Unknown token error" }, base: token, line, }; try { switch (token.type) { case TokenType.IO: if (token.value === "print") { nextToken(); token = getToken(); if (token.type !== TokenType.Shape) { throw new OSyntaxError( `Expected '(' after 'print', but got: ${token.value} (Type: ${token.type})` ); } node.type = NodeType.OUTPUT; nextToken(); node.params = { content: collectString() }; nextToken(); } break; case TokenType.IERQ: node.type = NodeType.IMPORT; nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected value after 'import', but got: ${token.value} (Type: ${token.type})` ); } node.params = { name: sanitizeTokenValue(token.value) }; break; case TokenType.Component: nextToken(); token = getToken(); if (token.type !== TokenType.Identifier) { throw new OSyntaxError( `Expected identifier after 'component', but got: ${token.value} (Type: ${token.type})` ); } const name = sanitizeTokenValue(token.value); node.type = NodeType.CDECLARE; nextToken(); token = getToken(); if (token.type !== TokenType.PageRequest) { throw new OSyntaxError( `Expected page request after 'component', but got: ${token.value} (Type: ${token.type})` ); } nextToken(); token = getToken(); const params = collectTag().join(" "); node.params = { name, content: params }; break; case TokenType.PageRequest: node.type = NodeType.SHOW; nextToken(); const tags = collectTag(); const joinedTags = tags //.map((tag) => tag.trim()) .join(" ") .trim(); if (joinedTags.startsWith("%css-global%")) { node.type = NodeType.STYLE; node.params = { content: joinedTags.replace("%css-global%", ""), }; break; } nextToken(); if (!getToken() || getToken().value !== "as") { throw new OSyntaxError( `Expected 'as' after value, but got: ${ getToken() ? getToken().value : "\\eof" } (Type: ${ getToken() ? getToken().type : TokenType.EOF } at index ${currentIndex})` ); } nextToken(); node.params = { content: joinedTags, route: sanitizeTokenValue(getToken().value), }; break; case TokenType.EOF: return nodes.map(NodeAdapter); case TokenType.Comment: skipComment(); node.type = NodeType.NONE; break; case TokenType.Meta: node.type = NodeType.META; if (token.value === "title") { node.params.type = "\\title"; } else { nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected value after 'meta', but got: ${token.value} (Type: ${token.type})` ); } node.params.type = sanitizeTokenValue(collectString()); } nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected value after 'meta', but got: ${token.value} (Type: ${token.type})` ); } node.params.content = sanitizeTokenValue(collectString()); break; case TokenType.Datatype: const type = token.value; nextToken(); token = getToken(); const vname = token.value; nextToken(); token = getToken(); if (token.type !== TokenType.Assign) { throw new OSyntaxError( `Expected '=' after variable name, but got: ${token.value} (Type: ${token.type})` ); } nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected value after '=', but got: ${token.value} (Type: ${token.type})` ); } node.type = NodeType.DECLARE; node.params.name = vname; if (type === "string") { node.params.value = sanitizeTokenValue(collectString()); } else { node.params.value = sanitizeTokenValue(token.value); } node.params.type = type; break; case TokenType.ExportW: node.type = NodeType.EXPORTW; break; case TokenType.Conditional: node.params.condition = {}; switch (token.value) { case "if": node.type = NodeType.IF; nextToken(); token = getToken(); if (token.type !== TokenType.Shape) { throw new OSyntaxError( `Expected '(' after 'if', but got: ${token.value} (Type: ${token.type})` ); } nextToken(); token = getToken(); const params = collectParam().join(" "); nextToken(); token = getToken(); node.params.condition.cond = params; node.params.condition.body = collectBlock(); break; } break; case TokenType.Function: node.type = NodeType.Function; nextToken(); token = getToken(); if (token.type !== TokenType.Identifier) { throw new OSyntaxError( `Expected identifier after 'func', but got: ${token.value} (Type: ${token.type})` ); } node.params.name = sanitizeTokenValue(token.value); nextToken(); token = getToken(); if (token.type !== TokenType.Block) { throw new OSyntaxError( `Expected '{' after function name, but got: ${token.value} (Type: ${token.type})` ); } nextToken(); token = getToken(); const content = collectBlock(); node.params.content = content; break; case TokenType.FCall: node.type = NodeType.FCall; nextToken(); token = getToken(); if (token.type !== TokenType.Identifier) { throw new OSyntaxError( `Expected identifier after 'call', but got: ${token.value} (Type: ${token.type})` ); } node.params.name = sanitizeTokenValue(token.value); break; case TokenType.ORDER: const orderName = token.value.replace("@", ""); if (orderName === "strict") { node.type = NodeType.UseStrict; } else { node.type = NodeType.ORDER; nextToken(); token = getToken(); node.params.name = orderName; if (token.type !== TokenType.Shape) { node.params.content = "true"; currentIndex--; break; } nextToken(); token = getToken(); const params = token.value; nextToken(); token = getToken(); node.params.content = params; } break; case TokenType.SCS: node.type = NodeType.Create; nextToken(); token = getToken(); if (token.type !== TokenType.Identifier) { throw new OSyntaxError( `Expected identifier after 'call', but got: ${token.value} (Type: ${token.type})` ); } const dtype = sanitizeTokenValue(token.value); node.params.type = dtype; nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected value after 'call', but got: ${token.value} (Type: ${token.type})` ); } const dname = sanitizeTokenValue(collectString()); node.params.name = dname; if (dtype === "file") { nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected value after 'call', but got: ${token.value} (Type: ${token.type})` ); } const content = sanitizeTokenValue(collectString()); node.params.content = content; } break; case TokenType.Layout: node.type = NodeType.Layout; nextToken(); token = getToken(); if (token.type !== TokenType.PageRequest) { throw new OSyntaxError( `Expected page request after 'layout', but got: ${token.value} (Type: ${token.type})` ); } nextToken(); const tag = collectTag().join(" "); node.params = { content: tag }; break; case TokenType.Load: node.type = NodeType.LOAD; let _type = ""; switch (token.value) { case "loadComponent": _type = "component"; break; case "loadLayout": _type = "layout"; break; case "loadTemplate": _type = "layout"; break; } nextToken(); token = getToken(); if (token.type !== TokenType.Value) { throw new OSyntaxError( `Expected string after load, but got: ${token.value} (Type: ${token.type})` ); } const path = sanitizeTokenValue(collectString()); node.params = { route: path, type: _type }; break; default: throw new OSyntaxError( `Unexpected token: ${token.value} (Type: ${token.type})` ); } nextToken(); nodes.push(NodeAdapter(node)); } catch (e) { if (e instanceof Warning || e instanceof CustomError) { e.display(line); } } } return nodes; }; const getToken = (): Token => { return tokens[currentIndex]; }; const nextToken = (): void => { currentIndex++; }; const collectString = (): string => { let token = getToken(); let str: string = ""; while (token.type !== TokenType.Shape && !isStringEnd(token.value)) { str += sanitizeTokenValue(token.value) + " "; nextToken(); token = getToken(); } if (isStringEnd(token.value)) { str += sanitizeTokenValue(token.value); } return str.trim(); }; const collectBlock = (): string => { let token = getToken(); let str: string = ""; while (token.type !== TokenType.Block) { str += token.value + " "; nextToken(); token = getToken(); } str += token.value; return str.replace(/\{/g, "").replace(/\}/g, "").trim(); }; const collectParam = (): string[] => { let token = getToken(); let param: string[] = []; while (token.type !== TokenType.Shape) { param.push(token.value); nextToken(); token = getToken(); } nextToken(); token = getToken(); param.push(token.value); return param; }; const collectTag = (): string[] => { let token = getToken(); const param: string[] = []; while (token.type !== TokenType.PageRequest) { param.push(token.value.trim()); nextToken(); token = getToken(); } return param; }; const isStringEnd = (value: string): boolean => { return value.endsWith('"') || value.endsWith("'") || value.endsWith("`"); }; const sanitizeTokenValue = (value: string): string => { return value .replace(/["'`]/g, "") .replace(/\n/g, "") .replace(/\r/g, "") .replace("%", " "); }; function skipComment() {} function collectObject(): string { let token = getToken(); let str: string = ""; let iteration = 1; while (iteration !== 0) { if (token.type === TokenType.Block) { if (token.value === "{") { iteration++; } else if (token.value === "}") { iteration--; } } str += token.value + " "; nextToken(); token = getToken(); } return str.trim(); }