Pascalのparserを作る
code:pascal.ts
import {
all,
ok,
choice,
lazy,
match,
Parser,
text,
///////////////////////////////////////////////////////////////////////
code:pascal.ts
const whitespace = match(/\s*/m);
const comment = match(/\{^{*\}/); /** token separator */
const separator = comment.or(whitespace);
code:pascal.ts
const identifier = match(/\w\w\d*/).trim(separator); const token = <S extends string>(str: S) => text(str).trim(separator);
const colon = token(":");
const semicolon = token(";");
const nothing = text("");
const comma = token(",");
const lParen = token("(");
const rParen = token(")");
/** end of statement */
const eos = semicolon;
code:pascal.ts
const programHeading = token("program").chain(() =>
identifier.and(programFiles.or(ok([]as string[]))).skip(eos)
);
const programFiles = identifier.sepBy(comma, 1).wrap(lParen, rParen);
code:pascal.ts
const label = match(/(1-9\d{0,3}0-9)/).map(Number).trim(separator); code:pascal.ts
const labelDeclarationsPart = label.sepBy(comma, 1).wrap(
token("label"),
eos,
).or(ok([] as number[]));
code:pascal.ts
const integer = match(/+\-?(0|1-9\d*)/).map(parseInt).trim(separator); const real = match(/+\-?(0|1-9\d*)\.\d+(e+-?\d+)?/i).map(parseFloat).trim( separator
);
code:pascal.ts
const char = match(/^'/).or(text("''")); // char typeの定数
const character = char.trim(text("'"));
// 文字列型の定数
const literal = char.repeat().map((chars) => chars.join("")).trim(text("'"));
定数名は字句解析の段階でチェックしたい
未定義の定数名がでてきたらエラーを出す
code:pascal.ts
const literal = char.repeat().map((chars) => chars.join("")).trim(text("'"));
const constant = choice(
integer,
real,
// 定数名
identifier,
);
const constDeclaration = identifier.skip(token("=")).and(constant) ;
code:pascal.ts
const pascalTrue = token("true").map(() => true);
const pascalFalse = token("false").map(() => false);
const constant = choice(
integer,
real,
pascalTrue,
pascalFalse,
character,
literal,
);
const constantExpression: Parser<string | number | boolean> = lazy(() =>
choice(
constantExpression.skip(token("+")).and(constantExpression).map((a, b) => a + b
),
constantExpression.skip(token("-")).and(constantExpression).map((a, b) => a - b
),
constantExpression.skip(token("*")).and(constantExpression).map((a, b) => a * b
),
constantExpression.skip(token("/")).and(constantExpression).map((a, b) => a / b
),
constantExpression.skip(token("div")).and(constantExpression).map((
) => Math.floor(a / b)),
constantExpression.skip(token("mod")).and(constantExpression).map((
) => a % b),
constantExpression.skip(token("and")).and(constantExpression).map((
) => a & b),
constantExpression.skip(token("or")).and(constantExpression).map((a, b) => a | b
),
constantExpression.skip(token("not")).map((a) => !a),
constantExpression.skip(token("=")).and(constantExpression).map((a, b) => a === b
),
constantExpression.skip(token("<>")).and(constantExpression).map((a, b) => a !== b
),
constantExpression.skip(token("<")).and(constantExpression).map((a, b) => a < b
),
constantExpression.skip(token("<=")).and(constantExpression).map((a, b) => a <= b
),
constantExpression.skip(token(">")).and(constantExpression).map((a, b) => a > b
),
constantExpression.skip(token(">=")).and(constantExpression).map((a, b) => a >= b
),
constantExpression.wrap(lParen, rParen),
constant,
identifier,
)
);
const plus = token("+");
const minus = token("-");
const realType = real;
code:pascal.ts
const enumeratedType = identifier.sepBy(comma, 1).wrap(lParen, rParen);
code:pascal.ts
const ordinalType = choice(
booleanType,
integerType,
charType,
enumeratedType,
subrangeType,
);
code:pascal.ts
const SimpleType = choice(
ordinalType,
realType,
)
code:pascal.ts
// identifierは型名
const pointerType = token("^").next(identifier);
subrange typeを作れるのに使えるのは、ordinal typeのconstantのみ
identifierは列挙型のつもり
code:pascal.ts
const subrangeConstant = choice(
integer,
plus.next(integer.or(identifier)),
minus.next(integer.or(identifier)).map((n) => -n),
);
const subrangeType = choice(subrangeConstant, identifier).skip(token("..")).and(
choice(subrangeConstant, identifier),
);
code:pascal.ts
const indexType = subrangeType.or(identifier);
const arrayType = token("array").next(
indexType.wrap(token("token("")
).skip(token("of")).and(pascalType);
code:pascal.ts
const setType = token("packed").or(nothing).next(token("set")).next(token("of")).next(ordinalType);
code:pascal.ts
const variantField = lazy(
()=> constant.sepBy(comma, 1).skip(colon).and(fieldList.wrap(lParen, rParen))
);
const variantRecord =
token("case").next(identifier.skip(colon)).or(nothing).and(pascalType).skip(token("of")).and(
variantField.sepBy(eos, 1),
),
);
code:pascal.ts
const fieldDeclaration = identifier.sepBy(comma, 1).skip(colon).and(pascalType);
const fieldList = fieldDeclaration.sepBy(eos).and(variantRecord.or(nothing));
const recordType = token("packed").or(nothing).next(token("record")).next(
fieldList,
).skip(token("end"));
code:pascal.ts
const fileType = token("packed").or(nothing).next(token("file")).next(
token("of"),
)
.next(pascalType);
code:pascal.ts
const structuredType = choice(
arrayType,
recordType,
setType,
fileType,
)
const pascalType = choice(SimpleType, structuredType, pointerType);
code:pascal.ts
const typeDefinition = identifier.skip(token("=")).and(pascalType)
const typeDefinitionPart = token("type").next(typeDefinition.sepBy(eos, 1))
.or(
);
code:pascal.ts
const variableDefinitionPart = token("var").next(fieldDeclaration.sepBy(eos, 1)).map(v=>v.flat()).or(ok([]as string,unknown[])); code:pascal.ts
const caseLabel = integer.or(token("others"));
const unsignedConstant = choice(
unsignedNumber,
literal,
constantName,
nil,
);
const functionCalling=lazy(()=>identifier.and(actualParameterList));
const setItem=expression.or(expression.skip(token("..")).and(expression));
const setConstructors=setItem.sepBy(comma,1).wrap(token(""),token("")); const factor = lazy(()=>choice(
unsignedConstant,
limitName,
variable,
setConstructors,
functionCalling,
token("not").next(factor),
expression.wrap(lParen, rParen),
));
const term=factor.or(factor.sepBy(choice(token("*"), token("/"), token("div"), token("mod"), token("and"))));
const simpleExpression = plus.or(minus).or(nothing).and(term)
.and(plus.or(minus).or(token("in")).and(term).sepBy(separator));
const expression: Parser<string | number | boolean> =
choice(
simpleExpression.skip(token("=")).and(simpleExpression).map((a, b) => a === b), simpleExpression.skip(token("<>")).and(simpleExpression).map((a, b) => a !== b), simpleExpression.skip(token("<")).and(simpleExpression).map((a, b) => a < b), simpleExpression.skip(token("<=")).and(simpleExpression).map((a, b) => a <= b), simpleExpression.skip(token(">")).and(simpleExpression).map((a, b) => a > b), simpleExpression.skip(token(">=")).and(simpleExpression).map((a, b) => a >= b), simpleExpression.skip(token("in")).and(simpleExpression).map((a, b) => a >= b), );
const variable: Parser<unknown> = lazy(() =>
choice(
identifier,
variable.and(expression.wrap(lParen, rParen)),
variable.skip(token(".")).and(identifier),
variable.skip(token("^")),
)
);
code:pascal.ts
const whileStatement = token("while").next(judgeExpression).skip(token("do")).and(statement);
const repeatStatement = token("repeat").next(statement.sepBy(eos,1)).skip(token("until")).and(judgeExpression);
// controlVariableにはordinalTypeが使える
const forStatement = token("for").next(controlVariable).skip(token(":=")).and(initialValue).and(token("to").or(token("downto"))).and(endValue).skip(token("do")).and(statement);
code:pascal.ts
const ifStatement = token("if").next(judgeExpression).skip(token("then")).and(statement).or(token("else").next(statement));
const caseStatement = token("case").next(judgeExpression).skip(token("of")).and(constant.sepBy(comma,1).skip(colon).and(statement).sepBy(eos,1)).skip(token("end"));
const gotoStatement = token("goto").next(label);
withの次にはrecord typeの変数を入れる
code:pascal.ts
const withStatement = token("with").next(identifier.sepBy(comma, 1)).skip(token("do")).and(statement);
code:pascal.ts
const statement:Parser<unknown> = lazy(() => label.skip(colon).or(nothing).and(simpleStatement.or(compoundStatement)));
code:pascal.ts
const compoundStatement = statement.sepBy(eos,1).skip(eos.or(nothing)).wrap(token("begin"), token("end"));
code:pascal.ts
const assignmentStatement = variable.skip(token(":=")).and(expression);
code:pascal.ts
/* "variable" here really means "identifier" */
const procedureStatement = choice(
variable,
variable.skip(nothing.wrap(lParen, rParen)),
variable.and(parameterList),
);
const simpleStatement=choice(
assignmentStatement,
procedureStatement,
whileStatement,
repeatStatement,
forStatement,
ifStatement,
caseStatement,
gotoStatement,
withStatement,
);
const block :Parser<unknown>= lazy(()=>all(
labelDeclarationsPart,
constDeclarationsPart,
typeDefinitionPart,
variableDefinitionPart,
procedureAndFunctionDefinitonPart,
compoundStatement,
));
const directives = token("forward");
code:pascal.ts
// identifierが型名
const parameter = identifier.sepBy(comma, 1).skip(colon).and(identifier);
const formalParameter = token("var").or(nothing).and(parameter);
const formalParameterList = formalParameter.sepBy(comma, 1).wrap(lParen, rParen);
const actualParameter = expression.or(variable);
const actualParameterList = actualParameter.sepBy(comma).wrap(lParen, rParen);
code:pascal.ts
const procedureHeading=token("procedure").next(identifier).and(formalParameterList);
result typeはsimple typeかpointer typeのみ
code:pascal.ts
// 最後のidentifierは型名
const functionHeading=token("function").next(identifier).and(formalParameterList).skip(colon).and(identifier);
code:pascal.ts
const procedureAndFunctionDefinitonPart = procedureHeading.or(functionHeading).skip(semicolon).and(block.or(directives)).skip(eos).sepby(separator);
code:pascal.ts
const pascal = all(
programHeading,
labelDeclarationsPart,
constDeclarationsPart,
typeDefinitionPart,
variableDefinitionPart,
procedureAndFunctionDefinitonPart,
compoundStatement,
).skip(token("."));
if (import.meta.main) {
console.log(pascal.tryParse(await res.text());
}