ocat-lang
Version:
A programming language for the web design and development
453 lines (403 loc) • 17.2 kB
text/typescript
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();
}