Skip to content

Here is an example of parsing a MyBatis code snippet #89

@AEPKILL

Description

@AEPKILL

Below is the code sample to be parsed

SELECT * FROM SSLS_GUI.VW_TU
    <if test="( hasFilters == 'yes' ) and ( parameters != null )">
        <where>
            <foreach item="clause" collection="parameters" separator=" AND "
                open="(" close=")">
                UPPER(${clause.column}) ${clause.operator} #{clause.value}
            </foreach>
        </where>
    </if>
    <if test="sort == 'true'">
        ORDER BY ${sortField}
        <if test="sortOrder == 'DESC'"> DESC</if>
        <if test="sortOder == 'ASC'"> ASC</if>
    </if>

And its Lexer code:

import {
	type Options,
	type RegexRule,
	type Rule,
	type Rules,
	type StringRule,
	createLexer,
} from "leac";

const Keywords = [
	"ADD",
	"ADD CONSTRAINT",
	"ALTER",
	"ALTER COLUMN",
	"ALTER TABLE",
	"ALL",
	"AND",
	"ANY",
	"AS",
	"ASC",
	"BACKUP DATABASE",
	"BETWEEN",
	"CASE",
	"CHECK",
	"COLUMN",
	"COMMIT",
	"CONSTRAINT",
	"CREATE",
	"CREATE DATABASE",
	"CREATE INDEX",
	"CREATE OR REPLACE VIEW",
	"CREATE TABLE",
	"CREATE PROCEDURE",
	"CREATE UNIQUE INDEX",
	"CREATE VIEW",
	"DATABASE",
	"DEFAULT",
	"DELETE",
	"DESC",
	"DISTINCT",
	"DROP",
	"DROP COLUMN",
	"DROP CONSTRAINT",
	"DROP DATABASE",
	"DROP DEFAULT",
	"DROP INDEX",
	"DROP TABLE",
	"DROP VIEW",
	"EXEC",
	"EXISTS",
	"FOREIGN KEY",
	"FROM",
	"FULL OUTER JOIN",
	"GROUP BY",
	"HAVING",
	"ILIKE",
	"IN",
	"INDEX",
	"INNER JOIN",
	"INSERT INTO",
	"INSERT INTO SELECT",
	"IS NULL",
	"IS NOT NULL",
	"JOIN",
	"LEFT JOIN",
	"LIKE",
	"LIMIT",
	"NOT",
	"NOT NULL",
	"OR",
	"ORDER BY",
	"OUTER JOIN",
	"PRIMARY KEY",
	"PROCEDURE",
	"RETURNING",
	"RIGHT JOIN",
	"ROWNUM",
	"SELECT",
	"SELECT DISTINCT",
	"SELECT INTO",
	"SELECT TOP",
	"SET",
	"TABLE",
	"TOP",
	"TRUNCATE TABLE",
	"UNION",
	"UNION ALL",
	"UNIQUE",
	"UPDATE",
	"VALUES",
	"VIEW",
	"WHERE",
	"PRAGMA",
	"INTEGER",
	"PRIMARY",
	"CHAR",
	"DATETIME",
	"DECIMAL",
	"BINARY",
	"TIMESTAMP",
	"VARCHAR",
	"VARBINARY",
	"TINYBLOB",
	"TINYTEXT",
	"BLOB",
	"LONGTEXT",
	"NULL",
	"REFERENCES",
	"INDEX_LIST",
	"BY",
	"CURRENT_DATE",
	"CURRENT_TIME",
	"EACH",
	"ELSE",
	"ELSEIF",
	"FALSE",
	"FOR",
	"GROUP",
	"IF",
	"IFNULL",
	"INSERT",
	"INTERVAL",
	"INTO",
	"IS",
	"KEY",
	"KEYS",
	"LEFT",
	"MATCH",
	"ON",
	"OPTION",
	"ORDER",
	"OUT",
	"OUTER",
	"REPLACE",
	"TINYINT",
	"RIGHT",
	"LEADING",
	"TRAILING",
	"THEN",
	"TO",
	"TRUE",
	"WHEN",
	"WITH",
	"UNSIGNED",
	"CASCADE",
	"ENGINE",
	"TEXT",
	"AUTO_INCREMENT",
	"SHOW",
	"BEGIN",
	"END",
	"PRINT",
	"OVERLAPS",
];
const Operators = [
	"<>",
	"<=",
	">=",
	"<<",
	">>",
	"~",
	"&&",
	"=",
	"<",
	">",
	"+",
	"-",
	"*",
	"/",
	"%",
	"&",
	"|",
	"^",
	"||",
	"!",
  "?"
];
const Punctuations = ["(", ")", "{", "}", "[", "]", ".", ",", ";"];
const defaultOptions: Options = {
	lineNumbers: true,
};

export enum TokenType {
	Keyword = "keyword",
	Whitespace = "whitespace",
	Break = "break",
	Operator = "operator",
	Symbol = "symbol",
	Punctuation = "punctuation",
	CdataBegin = "cdata-begin",
	CdataEnd = "cdata-end",

	VariableBegin = "variable-begin",
	Variable = "variable",
	VariableEnd = "variable-end",

	XmlBegin = "xml-begin",
	XmlTag = "xml-tag",
	XmlAttrName = "xml-attr-name",
	XmlEnd = "xml-end",

	XmlCommentBegin = "xml-comment-begin",
	XmlComment = "xml-comment",
	XmlCommentEnd = "xml-comment-end",

	StringBegin = "string-begin",
	String = "string-literal",
	StringEnd = "string-end",

	Number = "number",
}

// COMMON
const keywordRules = Keywords.map((keyword) => {
	return {
		name: TokenType.Keyword,
		regex: new RegExp(`\\b${keyword}\\b`, "i"),
	} satisfies RegexRule;
}) as Rules;
const operatorsRules = Operators.map((operator) => {
	return {
		name: TokenType.Operator,
		regex: new RegExp(`\\${operator}`, "i"),
	} satisfies RegexRule;
}) as Rules;
const punctuationsRules = Punctuations.map((punctuation) => {
	return {
		name: TokenType.Punctuation,
		regex: new RegExp(`\\${punctuation}`, "i"),
	} satisfies RegexRule;
}) as Rules;
const whitespaceRule = {
	name: TokenType.Whitespace,
	regex: /\s+/,
} satisfies RegexRule;
const breakRule = {
	name: TokenType.Break,
	regex: /[\n\r]/,
} satisfies RegexRule;
const symbolRule = {
	name: TokenType.Symbol,
	regex: /[a-zA-Z_][a-zA-Z_0-9]*/,
} satisfies RegexRule;

// CDATA
const cdataBeginRule = {
	name: TokenType.CdataBegin,
	regex: /<\!\s*\[CDATA\[/i,
} satisfies RegexRule;
const cdataEndRule = {
	name: TokenType.CdataEnd,
	regex: /\]\]\>/,
} satisfies RegexRule;
const cdataRule = {
	...cdataBeginRule,
	push: createLexer(
		[
			{
				...cdataEndRule,
				pop: true,
			},
			...operatorsRules,
			...punctuationsRules,
			breakRule,
			whitespaceRule,
		],
		defaultOptions,
	),
} satisfies RegexRule;

// Variable
const variableBeginRule = {
	name: TokenType.VariableBegin,
	regex: /[\#\$]\{/,
} satisfies RegexRule;
const variableEndRule = {
	name: TokenType.VariableEnd,
	regex: /\}/,
} satisfies RegexRule;
const variableRule = {
	...variableBeginRule,
	push: createLexer(
		[
			{
				...symbolRule,
				name: TokenType.Variable,
			},
			{
				...variableEndRule,
				pop: true,
			},
		],
		defaultOptions,
	),
} satisfies RegexRule;

// Literal-String
function createStringLiteralRules(stringName: string) {
	const doubleStringQuoteRule = {
		name: "stringQuote",
		str: `"`,
	} satisfies StringRule;
	const singleStringQuoteRule = {
		name: "stringQuote",
		str: `'`,
	} satisfies StringRule;
	const templateStringQuoteRule = {
		name: "stringQuote",
		str: "`",
	} satisfies StringRule;

	return [
		{
			...singleStringQuoteRule,
			name: TokenType.StringBegin,
			push: createLexer(
				[
					{
						regex: new RegExp(`[^${singleStringQuoteRule.str}]*`),
						name: stringName,
					},
					{
						...singleStringQuoteRule,
						pop: true,
						name: TokenType.StringEnd,
					},
				],
				defaultOptions,
			),
		} satisfies StringRule,
		{
			...doubleStringQuoteRule,
			name: TokenType.StringBegin,
			push: createLexer(
				[
					{
						regex: new RegExp(`[^${doubleStringQuoteRule.str}]*`),
						name: stringName,
					},
					{
						...doubleStringQuoteRule,
						pop: true,
						name: TokenType.StringEnd,
					},
				],
				defaultOptions,
			),
		} satisfies StringRule,
		{
			...templateStringQuoteRule,
			name: TokenType.StringBegin,
			push: createLexer(
				[
					{
						regex: new RegExp(`[^${templateStringQuoteRule.str}]*`),
						name: stringName,
					},
					{
						...templateStringQuoteRule,
						pop: true,
						name: TokenType.StringEnd,
					},
				],
				defaultOptions,
			),
		} satisfies StringRule,
	];
}

const stringLiteralRules = createStringLiteralRules(TokenType.String);

// Literal-Number
const numberLiteralRule = {
	name: TokenType.Number,
	regex: /-?\d+(\.\d+)?/,
} satisfies RegexRule;

// XML
const xmlBeginRule = {
	name: TokenType.XmlBegin,
	regex: /<\s*\/?/,
} satisfies RegexRule;
const xmlEndRule = {
	name: TokenType.XmlEnd,
	regex: /\s*\/?>/,
} satisfies RegexRule;
const xmlRule = {
	...xmlBeginRule,
	push: createLexer(
		[
			{
				...symbolRule,
				name: TokenType.XmlTag,
				push: createLexer(
					[
						{
							...xmlEndRule,
							pop: true,
						},
						breakRule,
						whitespaceRule,
						...operatorsRules,
						{
							...symbolRule,
							name: TokenType.XmlAttrName,
						},
						...stringLiteralRules,
					],
					defaultOptions,
				),
			},
		],
		defaultOptions,
	),
} satisfies RegexRule;

// XML Comment
const xmlCommentBeginRule = {
	name: TokenType.XmlCommentBegin,
	str: "<!--",
} satisfies StringRule;
const xmlCommentEndRule = {
	name: TokenType.XmlCommentEnd,
	str: "-->",
} satisfies StringRule;
const xmlCommentRule = {
	...xmlCommentBeginRule,
	push: createLexer(
		[
			{
				...xmlCommentEndRule,
				pop: true,
			},
			{
				regex: new RegExp(`[^${xmlCommentEndRule.str}]*`),
				name: TokenType.XmlComment,
			},
		],
		defaultOptions,
	),
} satisfies Rule;

export const SQLlexer = createLexer(
	[
		cdataRule,
		xmlCommentRule,
		xmlRule,
		variableRule,
		...keywordRules,
		...operatorsRules,
		...punctuationsRules,
		...stringLiteralRules,
		numberLiteralRule,
		breakRule,
		whitespaceRule,
		symbolRule,
	],
	defaultOptions,
);

Maybe somebody need this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    exampleUser-provided demo

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions