UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

927 lines 45.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemeParser = void 0; const token_type_1 = require("../types/tokens/token-type"); const location_1 = require("../types/location"); const scheme_node_types_1 = require("../types/nodes/scheme-node-types"); const ParserError = require("./parser-error"); const group_1 = require("../types/tokens/group"); const tokens_1 = require("../types/tokens"); /** * An enum representing the current quoting mode of the parser */ var QuoteMode; (function (QuoteMode) { QuoteMode[QuoteMode["NONE"] = 0] = "NONE"; QuoteMode[QuoteMode["QUOTE"] = 1] = "QUOTE"; QuoteMode[QuoteMode["QUASIQUOTE"] = 2] = "QUASIQUOTE"; })(QuoteMode || (QuoteMode = {})); class SchemeParser { constructor(source, tokens, chapter = Infinity) { this.current = 0; this.quoteMode = QuoteMode.NONE; // We can group syntactical elements by their chapter this.BASIC_CHAPTER = 1; this.QUOTING_CHAPTER = 2; this.VECTOR_CHAPTER = 3; this.MUTABLE_CHAPTER = 3; this.source = source; this.tokens = tokens; this.chapter = chapter; } advance() { if (!this.isAtEnd()) this.current++; return this.previous(); } isAtEnd() { return this.current >= this.tokens.length; } previous() { return this.tokens[this.current - 1]; } peek() { return this.tokens[this.current]; } validateChapter(c, chapter) { if (this.chapter < chapter) { throw new ParserError.DisallowedTokenError(this.source, c.pos, c, this.chapter); } } /** * Returns the location of a token. * @param token A token. * @returns The location of the token. */ toLocation(token) { return new location_1.Location(token.pos, token.endPos); } /** * Helper function used to destructure a list into its elements and terminator. * An optional verifier is used if there are restrictions on the elements of the list. */ destructureList(list, verifier = (_x) => { }) { // check if the list is an empty list if (list.length === 0) { return [[], undefined]; } // check if the list is a list of length 1 if (list.length === 1) { verifier(list[0]); return [[this.parseExpression(list[0])], undefined]; } // we now know that the list is at least of length 2 // check for a dotted list // it is if the second last element is a dot const potentialDot = list.at(-2); if ((0, tokens_1.isToken)(potentialDot) && potentialDot.type === token_type_1.TokenType.DOT) { const cdrElement = list.at(-1); const listElements = list.slice(0, -2); verifier(cdrElement); listElements.forEach(verifier); return [ listElements.map(this.parseExpression.bind(this)), this.parseExpression(cdrElement), ]; } // we now know that it is a proper list const listElements = list; listElements.forEach(verifier); return [listElements.map(this.parseExpression.bind(this)), undefined]; } /** * Returns a group of associated tokens. * Tokens are grouped by level of parentheses. * * @param openparen The opening parenthesis, if one exists. * @returns A group of tokens or groups of tokens. */ grouping(openparen) { const elements = []; let inList = false; if (openparen) { inList = true; elements.push(openparen); } do { let c = this.advance(); switch (c.type) { case token_type_1.TokenType.LEFT_PAREN: case token_type_1.TokenType.LEFT_BRACKET: // the next group is not empty, especially because it // has an open parenthesis const innerGroup = this.grouping(c); elements.push(innerGroup); break; case token_type_1.TokenType.RIGHT_PAREN: case token_type_1.TokenType.RIGHT_BRACKET: if (!inList) { throw new ParserError.UnexpectedFormError(this.source, c.pos, c); } // add the parenthesis to the current group elements.push(c); inList = false; break; case token_type_1.TokenType.APOSTROPHE: // Quoting syntax (short form) case token_type_1.TokenType.BACKTICK: case token_type_1.TokenType.COMMA: case token_type_1.TokenType.COMMA_AT: case token_type_1.TokenType.HASH_VECTOR: // Vector syntax // these cases modify only the next element // so we group up the next element and use this // token on it let nextGrouping; do { nextGrouping = this.grouping(); } while (!nextGrouping); elements.push(this.affect(c, nextGrouping)); break; case token_type_1.TokenType.QUOTE: // Quoting syntax case token_type_1.TokenType.QUASIQUOTE: case token_type_1.TokenType.UNQUOTE: case token_type_1.TokenType.UNQUOTE_SPLICING: case token_type_1.TokenType.IDENTIFIER: // Atomics case token_type_1.TokenType.NUMBER: case token_type_1.TokenType.BOOLEAN: case token_type_1.TokenType.STRING: case token_type_1.TokenType.DOT: case token_type_1.TokenType.DEFINE: // Chapter 1 case token_type_1.TokenType.IF: case token_type_1.TokenType.ELSE: case token_type_1.TokenType.COND: case token_type_1.TokenType.LAMBDA: case token_type_1.TokenType.LET: case token_type_1.TokenType.SET: // Chapter 3 case token_type_1.TokenType.BEGIN: case token_type_1.TokenType.DELAY: case token_type_1.TokenType.IMPORT: case token_type_1.TokenType.EXPORT: elements.push(c); break; case token_type_1.TokenType.HASH_SEMICOLON: // a datum comment // get the next NON-EMPTY grouping // and ignore it while (!this.grouping()) { } break; case token_type_1.TokenType.EOF: // We should be unable to reach this point at top level as parse() // should prevent the grouping of the singular EOF token. // However, with any element that ranges beyond the end of the // file without its corresponding delemiter, we can reach this point. throw new ParserError.UnexpectedEOFError(this.source, c.pos); default: throw new ParserError.UnexpectedFormError(this.source, c.pos, c); } } while (inList); if (elements.length === 0) { return; } try { return group_1.Group.build(elements); } catch (e) { if (e instanceof ParserError.ExpectedFormError) { throw new ParserError.ExpectedFormError(this.source, e.loc, e.form, e.expected); } throw e; } } /** * Groups an affector token with its target. */ affect(affector, target) { return group_1.Group.build([affector, target]); } /** * Parse an expression. * @param expr A token or a group of tokens. * @returns */ parseExpression(expr) { // Discern the type of expression if ((0, tokens_1.isToken)(expr)) { return this.parseToken(expr); } // We now know it is a group // Due to group invariants we can determine if it represents a // single token instead if (expr.isSingleIdentifier()) { return this.parseToken(expr.unwrap()[0]); } return this.parseGroup(expr); } parseToken(token) { switch (token.type) { case token_type_1.TokenType.IDENTIFIER: return this.quoteMode === QuoteMode.NONE ? new scheme_node_types_1.Atomic.Identifier(this.toLocation(token), token.lexeme) : new scheme_node_types_1.Atomic.Symbol(this.toLocation(token), token.lexeme); // all of these are self evaluating, and so can be left alone regardless of quote mode case token_type_1.TokenType.NUMBER: return new scheme_node_types_1.Atomic.NumericLiteral(this.toLocation(token), token.literal); case token_type_1.TokenType.BOOLEAN: return new scheme_node_types_1.Atomic.BooleanLiteral(this.toLocation(token), token.literal); case token_type_1.TokenType.STRING: return new scheme_node_types_1.Atomic.StringLiteral(this.toLocation(token), token.literal); default: // if in a quoting context, any keyword is instead treated as a symbol if (this.quoteMode !== QuoteMode.NONE) { return new scheme_node_types_1.Atomic.Symbol(this.toLocation(token), token.lexeme); } throw new ParserError.UnexpectedFormError(this.source, token.pos, token); } } parseGroup(group) { // No need to check if group represents a single token as well if (!group.isParenthesized()) { // The only case left is the unparenthesized case // of a single affector token and a target group // Form: <affector token> <group> return this.parseAffectorGroup(group); } // Now we have fallen through to the generic group // case - a parenthesized group of tokens. switch (this.quoteMode) { case QuoteMode.NONE: return this.parseNormalGroup(group); case QuoteMode.QUOTE: case QuoteMode.QUASIQUOTE: return this.parseQuotedGroup(group); } } /** * Parse a group of tokens affected by an affector. * Important case as affector changes quotation mode. * * @param group A group of tokens, verified to be an affector and a target. * @returns An expression. */ parseAffectorGroup(group) { const [affector, target] = group.unwrap(); // Safe to cast affector due to group invariants switch (affector.type) { case token_type_1.TokenType.APOSTROPHE: case token_type_1.TokenType.QUOTE: this.validateChapter(affector, this.QUOTING_CHAPTER); if (this.quoteMode !== QuoteMode.NONE) { const innerGroup = this.parseExpression(target); const newSymbol = new scheme_node_types_1.Atomic.Symbol(this.toLocation(affector), "quote"); const newLocation = newSymbol.location.merge(innerGroup.location); // wrap the entire expression in a list return new scheme_node_types_1.Extended.List(newLocation, [newSymbol, innerGroup]); } this.quoteMode = QuoteMode.QUOTE; const quotedExpression = this.parseExpression(target); this.quoteMode = QuoteMode.NONE; return quotedExpression; case token_type_1.TokenType.BACKTICK: case token_type_1.TokenType.QUASIQUOTE: this.validateChapter(affector, this.QUOTING_CHAPTER); if (this.quoteMode !== QuoteMode.NONE) { const innerGroup = this.parseExpression(target); const newSymbol = new scheme_node_types_1.Atomic.Symbol(this.toLocation(affector), "quasiquote"); const newLocation = newSymbol.location.merge(innerGroup.location); // wrap the entire expression in a list return new scheme_node_types_1.Extended.List(newLocation, [newSymbol, innerGroup]); } this.quoteMode = QuoteMode.QUASIQUOTE; const quasiquotedExpression = this.parseExpression(target); this.quoteMode = QuoteMode.NONE; return quasiquotedExpression; case token_type_1.TokenType.COMMA: case token_type_1.TokenType.UNQUOTE: this.validateChapter(affector, this.QUOTING_CHAPTER); let preUnquoteMode = this.quoteMode; if (preUnquoteMode === QuoteMode.NONE) { throw new ParserError.UnsupportedTokenError(this.source, affector.pos, affector); } if (preUnquoteMode === QuoteMode.QUOTE) { const innerGroup = this.parseExpression(target); const newSymbol = new scheme_node_types_1.Atomic.Symbol(this.toLocation(affector), "unquote"); const newLocation = newSymbol.location.merge(innerGroup.location); // wrap the entire expression in a list return new scheme_node_types_1.Extended.List(newLocation, [newSymbol, innerGroup]); } this.quoteMode = QuoteMode.NONE; const unquotedExpression = this.parseExpression(target); this.quoteMode = preUnquoteMode; return unquotedExpression; case token_type_1.TokenType.COMMA_AT: case token_type_1.TokenType.UNQUOTE_SPLICING: // Unquote-splicing will be evaluated at runtime, // Proper unquote splicing will be dealt with in semester 2. this.validateChapter(affector, this.QUOTING_CHAPTER); let preUnquoteSplicingMode = this.quoteMode; if (preUnquoteSplicingMode === QuoteMode.NONE) { throw new ParserError.UnexpectedFormError(this.source, affector.pos, affector); } if (preUnquoteSplicingMode === QuoteMode.QUOTE) { const innerGroup = this.parseExpression(target); const newSymbol = new scheme_node_types_1.Atomic.Symbol(this.toLocation(affector), "unquote-splicing"); const newLocation = newSymbol.location.merge(innerGroup.location); // wrap the entire expression in a list return new scheme_node_types_1.Extended.List(newLocation, [newSymbol, innerGroup]); } throw new ParserError.UnsupportedTokenError(this.source, affector.pos, affector); this.quoteMode = QuoteMode.NONE; const unquoteSplicedExpression = this.parseExpression(target); this.quoteMode = preUnquoteSplicingMode; const newLocation = this.toLocation(affector).merge(unquoteSplicedExpression.location); return new scheme_node_types_1.Atomic.SpliceMarker(newLocation, unquoteSplicedExpression); case token_type_1.TokenType.HASH_VECTOR: // vectors quote over all elements inside. this.validateChapter(affector, this.VECTOR_CHAPTER); let preVectorQuoteMode = this.quoteMode; this.quoteMode = QuoteMode.QUOTE; const vector = this.parseVector(group); this.quoteMode = preVectorQuoteMode; return vector; default: throw new ParserError.UnexpectedFormError(this.source, affector.pos, affector); } } parseNormalGroup(group) { // it is an error if the group is empty in a normal context if (group.length() === 0) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "non-empty group"); } // get the first element const firstElement = group.unwrap()[0]; // If the first element is a token, it may be a keyword or a procedure call if ((0, tokens_1.isToken)(firstElement)) { switch (firstElement.type) { // Scheme chapter 1 case token_type_1.TokenType.LAMBDA: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseLambda(group); case token_type_1.TokenType.DEFINE: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseDefinition(group); case token_type_1.TokenType.IF: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseConditional(group); case token_type_1.TokenType.LET: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseLet(group); case token_type_1.TokenType.COND: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseExtendedCond(group); // Scheme chapter 2 case token_type_1.TokenType.QUOTE: case token_type_1.TokenType.APOSTROPHE: case token_type_1.TokenType.QUASIQUOTE: case token_type_1.TokenType.BACKTICK: case token_type_1.TokenType.UNQUOTE: case token_type_1.TokenType.COMMA: case token_type_1.TokenType.UNQUOTE_SPLICING: case token_type_1.TokenType.COMMA_AT: this.validateChapter(firstElement, this.QUOTING_CHAPTER); // we can reuse the affector group method to control the quote mode return this.parseAffectorGroup(group); // Scheme chapter 3 case token_type_1.TokenType.BEGIN: this.validateChapter(firstElement, this.MUTABLE_CHAPTER); return this.parseBegin(group); case token_type_1.TokenType.DELAY: this.validateChapter(firstElement, this.MUTABLE_CHAPTER); return this.parseDelay(group); case token_type_1.TokenType.SET: this.validateChapter(firstElement, this.MUTABLE_CHAPTER); return this.parseSet(group); // Scm-slang misc case token_type_1.TokenType.IMPORT: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseImport(group); case token_type_1.TokenType.EXPORT: this.validateChapter(firstElement, this.BASIC_CHAPTER); return this.parseExport(group); case token_type_1.TokenType.VECTOR: this.validateChapter(firstElement, this.VECTOR_CHAPTER); // same as above, this is an affector group return this.parseAffectorGroup(group); default: // It's a procedure call return this.parseApplication(group); } } // Form: (<group> <expr>*) // It's a procedure call return this.parseApplication(group); } /** * We are parsing a list/dotted list. */ parseQuotedGroup(group) { // check if the group is an empty list if (group.length() === 0) { return new scheme_node_types_1.Atomic.Nil(group.location); } // check if the group is a list of length 1 if (group.length() === 1) { const elem = [this.parseExpression(group.unwrap()[0])]; return new scheme_node_types_1.Extended.List(group.location, elem); } // we now know that the group is at least of length 2 const groupElements = group.unwrap(); const [listElements, cdrElement] = this.destructureList(groupElements); return new scheme_node_types_1.Extended.List(group.location, listElements, cdrElement); } // _____________________CHAPTER 1_____________________ /** * Parse a lambda expression. * @param group * @returns */ parseLambda(group) { // Form: (lambda (<identifier>*) <body>+) // | (lambda (<identifier>* . <rest-identifier>) <body>+) // ensure that the group has at least 3 elements if (group.length() < 3) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(lambda (<identifier>* . <rest-identifier>?) <body>+) | (lambda <rest-identifer> <body>+)"); } const elements = group.unwrap(); const formals = elements[1]; const body = elements.slice(2); // Formals should be a group of identifiers or a single identifier let convertedFormals = []; // if a rest element is detected, let convertedRest = undefined; if ((0, tokens_1.isToken)(formals)) { if (formals.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, formals.pos, formals, "<rest-identifier>"); } convertedRest = new scheme_node_types_1.Atomic.Identifier(this.toLocation(formals), formals.lexeme); } else { // it is a group const formalsElements = formals.unwrap(); [convertedFormals, convertedRest] = this.destructureList(formalsElements, // pass in a verifier that checks if the elements are identifiers formal => { if (!(0, tokens_1.isToken)(formal)) { throw new ParserError.ExpectedFormError(this.source, formal.pos, formal, "<identifier>"); } if (formal.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, formal.pos, formal, "<identifier>"); } }); } // Body is treated as a group of expressions const convertedBody = body.map(this.parseExpression.bind(this)); // assert that body is not empty if (convertedBody.length < 1) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(lambda ... <body>+)"); } if (convertedBody.length === 1) { return new scheme_node_types_1.Atomic.Lambda(group.location, convertedBody[0], convertedFormals, convertedRest); } const newLocation = convertedBody .at(0) .location.merge(convertedBody.at(-1).location); const bodySequence = new scheme_node_types_1.Atomic.Sequence(newLocation, convertedBody); return new scheme_node_types_1.Atomic.Lambda(group.location, bodySequence, convertedFormals, convertedRest); } /** * Parse a define expression. * @param group * @returns */ parseDefinition(group) { // Form: (define <identifier> <expr>) // | (define (<identifier> <formals>) <body>) // | (define (<identifier> <formals>) <body> <body>*) // ensure that the group has at least 3 elements if (group.length() < 3) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(define <identifier> <expr>) | (define (<identifier> <formals>) <body>+)"); } const elements = group.unwrap(); const identifier = elements[1]; const expr = elements.slice(2); let convertedIdentifier; let convertedFormals = []; let convertedRest = undefined; let isFunctionDefinition = false; // Identifier may be a token or a group of identifiers if ((0, tokens_1.isGroup)(identifier)) { // its a function definition isFunctionDefinition = true; const identifierElements = identifier.unwrap(); const functionName = identifierElements[0]; const formals = identifierElements.splice(1); // verify that the first element is an identifier if (!(0, tokens_1.isToken)(functionName)) { throw new ParserError.ExpectedFormError(this.source, functionName.location.start, functionName, "<identifier>"); } if (functionName.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, functionName.pos, functionName, "<identifier>"); } // convert the first element to an identifier convertedIdentifier = new scheme_node_types_1.Atomic.Identifier(this.toLocation(functionName), functionName.lexeme); // Formals should be a group of identifiers [convertedFormals, convertedRest] = this.destructureList(formals, formal => { if (!(0, tokens_1.isToken)(formal)) { throw new ParserError.ExpectedFormError(this.source, formal.pos, formal, "<identifier>"); } if (formal.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, formal.pos, formal, "<identifier>"); } }); } else if (identifier.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, identifier.pos, identifier, "<identifier>"); } else { // its a normal definition convertedIdentifier = new scheme_node_types_1.Atomic.Identifier(this.toLocation(identifier), identifier.lexeme); isFunctionDefinition = false; } // expr cannot be empty if (expr.length < 1) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(define ... <body>+)"); } if (isFunctionDefinition) { // Body is treated as a group of expressions const convertedBody = expr.map(this.parseExpression.bind(this)); if (convertedBody.length === 1) { return new scheme_node_types_1.Extended.FunctionDefinition(group.location, convertedIdentifier, convertedBody[0], convertedFormals, convertedRest); } const newLocation = convertedBody .at(0) .location.merge(convertedBody.at(-1).location); const bodySequence = new scheme_node_types_1.Atomic.Sequence(newLocation, convertedBody); return new scheme_node_types_1.Extended.FunctionDefinition(group.location, convertedIdentifier, bodySequence, convertedFormals, convertedRest); } // its a normal definition if (expr.length > 1) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(define <identifier> <expr>)"); } // Expr is treated as a single expression const convertedExpr = this.parseExpression(expr[0]); return new scheme_node_types_1.Atomic.Definition(group.location, convertedIdentifier, convertedExpr); } /** * Parse a conditional expression. * @param group * @returns */ parseConditional(group) { // Form: (if <pred> <cons> <alt>) // | (if <pred> <cons>) // ensure that the group has 3 or 4 elements if (group.length() < 3 || group.length() > 4) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(if <pred> <cons> <alt>?)"); } const elements = group.unwrap(); const test = elements[1]; const consequent = elements[2]; const alternate = group.length() > 3 ? elements[3] : undefined; // Test is treated as a single expression const convertedTest = this.parseExpression(test); // Consequent is treated as a single expression const convertedConsequent = this.parseExpression(consequent); // Alternate is treated as a single expression const convertedAlternate = alternate ? this.parseExpression(alternate) : new scheme_node_types_1.Atomic.Identifier(group.location, "undefined"); return new scheme_node_types_1.Atomic.Conditional(group.location, convertedTest, convertedConsequent, convertedAlternate); } /** * Parse an application expression. */ parseApplication(group) { // Form: (<func> <args>*) // ensure that the group has at least 1 element if (group.length() < 1) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(<func> <args>*)"); } const elements = group.unwrap(); const operator = elements[0]; const operands = elements.splice(1); // Operator is treated as a single expression const convertedOperator = this.parseExpression(operator); // Operands are treated as a group of expressions const convertedOperands = []; for (const operand of operands) { convertedOperands.push(this.parseExpression(operand)); } return new scheme_node_types_1.Atomic.Application(group.location, convertedOperator, convertedOperands); } /** * Parse a let expression. * @param group * @returns */ parseLet(group) { // Form: (let ((<identifier> <value>)*) <body>+) // ensure that the group has at least 3 elements if (group.length() < 3) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(let ((<identifier> <value>)*) <body>+)"); } const elements = group.unwrap(); const bindings = elements[1]; const body = elements.slice(2); // Verify bindings is a group if (!(0, tokens_1.isGroup)(bindings)) { throw new ParserError.ExpectedFormError(this.source, bindings.pos, bindings, "((<identifier> <value>)*)"); } // Bindings are treated as a group of grouped identifiers and values const convertedIdentifiers = []; const convertedValues = []; const bindingElements = bindings.unwrap(); for (const bindingElement of bindingElements) { // Verify bindingElement is a group of size 2 if (!(0, tokens_1.isGroup)(bindingElement)) { throw new ParserError.ExpectedFormError(this.source, bindingElement.pos, bindingElement, "(<identifier> <value>)"); } if (bindingElement.length() !== 2) { throw new ParserError.ExpectedFormError(this.source, bindingElement.location.start, bindingElement, "(<identifier> <value>)"); } const [identifier, value] = bindingElement.unwrap(); // Verify identifier is a token and an identifier if (!(0, tokens_1.isToken)(identifier)) { throw new ParserError.ExpectedFormError(this.source, identifier.location.start, identifier, "<identifier>"); } if (identifier.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, identifier.pos, identifier, "<identifier>"); } convertedIdentifiers.push(new scheme_node_types_1.Atomic.Identifier(this.toLocation(identifier), identifier.lexeme)); convertedValues.push(this.parseExpression(value)); } // Body is treated as a group of expressions const convertedBody = body.map(this.parseExpression.bind(this)); // assert that body is not empty if (convertedBody.length < 1) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(let ... <body>+)"); } if (convertedBody.length === 1) { return new scheme_node_types_1.Extended.Let(group.location, convertedIdentifiers, convertedValues, convertedBody[0]); } const newLocation = convertedBody .at(0) .location.merge(convertedBody.at(-1).location); const bodySequence = new scheme_node_types_1.Atomic.Sequence(newLocation, convertedBody); return new scheme_node_types_1.Extended.Let(group.location, convertedIdentifiers, convertedValues, bodySequence); } /** * Parse an extended cond expression. * @param group * @returns */ parseExtendedCond(group) { // Form: (cond (<pred> <body>)*) // | (cond (<pred> <body>)* (else <val>)) // ensure that the group has at least 2 elements if (group.length() < 2) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(cond (<pred> <body>*)* (else <val>)?)"); } const elements = group.unwrap(); const clauses = elements.splice(1); // safe to cast because of the check above const lastClause = clauses.pop(); // Clauses are treated as a group of groups of expressions // Form: (<pred> <body>*) const convertedClauses = []; const convertedConsequents = []; for (const clause of clauses) { // Verify clause is a group with size no less than 1 if (!(0, tokens_1.isGroup)(clause)) { throw new ParserError.ExpectedFormError(this.source, clause.pos, clause, "(<pred> <body>*)"); } if (clause.length() < 1) { throw new ParserError.ExpectedFormError(this.source, clause.firstToken().pos, clause.firstToken(), "(<pred> <body>*)"); } const [test, ...consequent] = clause.unwrap(); // verify that test is NOT an else token if ((0, tokens_1.isToken)(test) && test.type === token_type_1.TokenType.ELSE) { throw new ParserError.ExpectedFormError(this.source, test.pos, test, "<predicate>"); } // Test is treated as a single expression const convertedTest = this.parseExpression(test); // Consequent is treated as a group of expressions const consequentExpressions = consequent.map(this.parseExpression.bind(this)); const consequentLocation = consequent.length < 1 ? convertedTest.location : consequentExpressions .at(0) .location.merge(consequentExpressions.at(-1).location); // if consequent is empty, the test itself is treated // as the value returned. // if consequent is more than length one, there is a sequence. const convertedConsequent = consequent.length < 1 ? convertedTest : consequent.length < 2 ? consequentExpressions[0] : new scheme_node_types_1.Atomic.Sequence(consequentLocation, consequentExpressions); convertedClauses.push(convertedTest); convertedConsequents.push(convertedConsequent); } // Check last clause // Verify lastClause is a group with size at least 2 if (!(0, tokens_1.isGroup)(lastClause)) { throw new ParserError.ExpectedFormError(this.source, lastClause.pos, lastClause, "(<pred> <body>+) | (else <val>)"); } if (lastClause.length() < 2) { throw new ParserError.ExpectedFormError(this.source, lastClause.firstToken().pos, lastClause.firstToken(), "(<pred> <body>+) | (else <val>)"); } const [test, ...consequent] = lastClause.unwrap(); let isElse = false; // verify that test is an else token if ((0, tokens_1.isToken)(test) && test.type === token_type_1.TokenType.ELSE) { isElse = true; // verify that consequent is of length 1 if (consequent.length !== 1) { throw new ParserError.ExpectedFormError(this.source, lastClause.location.start, lastClause, "(else <val>)"); } } // verify that consequent is at least 1 expression if (consequent.length < 1) { throw new ParserError.ExpectedFormError(this.source, lastClause.location.start, lastClause, "(<pred> <body>+)"); } // Consequent is treated as a group of expressions const consequentExpressions = consequent.map(this.parseExpression.bind(this)); const consequentLocation = consequentExpressions .at(0) .location.merge(consequentExpressions.at(-1).location); const lastConsequent = consequent.length === 1 ? consequentExpressions[0] : new scheme_node_types_1.Atomic.Sequence(consequentLocation, consequentExpressions); if (isElse) { return new scheme_node_types_1.Extended.Cond(group.location, convertedClauses, convertedConsequents, lastConsequent); } // If the last clause is not an else clause, we treat it as a normal cond clause instead const lastTest = this.parseExpression(test); // Test convertedClauses.push(lastTest); convertedConsequents.push(lastConsequent); return new scheme_node_types_1.Extended.Cond(group.location, convertedClauses, convertedConsequents); } // _____________________CHAPTER 3_____________________ /** * Parse a reassignment expression. * @param group * @returns */ parseSet(group) { // Form: (set! <identifier> <expr>) // ensure that the group has 3 elements if (group.length() !== 3) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(set! <identifier> <expr>)"); } const elements = group.unwrap(); const identifier = elements[1]; const expr = elements[2]; // Identifier is treated as a single identifier if ((0, tokens_1.isGroup)(identifier)) { throw new ParserError.ExpectedFormError(this.source, identifier.location.start, identifier, "<identifier>"); } if (identifier.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, identifier.pos, identifier, "<identifier>"); } const convertedIdentifier = new scheme_node_types_1.Atomic.Identifier(this.toLocation(identifier), identifier.lexeme); const convertedExpr = this.parseExpression(expr); return new scheme_node_types_1.Atomic.Reassignment(group.location, convertedIdentifier, convertedExpr); } /** * Parse a begin expression. * @param group * @returns */ parseBegin(group) { // Form: (begin <body>+) // ensure that the group has 2 or more elements if (group.length() < 2) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(begin <body>+)"); } const sequence = group.unwrap(); const sequenceElements = sequence.slice(1); const convertedExpressions = []; for (const sequenceElement of sequenceElements) { convertedExpressions.push(this.parseExpression(sequenceElement)); } return new scheme_node_types_1.Extended.Begin(group.location, convertedExpressions); } /** * Parse a delay expression. * @param group * @returns */ parseDelay(group) { // Form: (delay <expr>) // ensure that the group has 2 elements if (group.length() !== 2) { throw new ParserError.ExpectedFormError(this.source, group.location.start, group, "(delay <expr>)"); } const elements = group.unwrap(); const expr = elements[1]; // Expr is treated as a single expression const convertedExpr = this.parseExpression(expr); return new scheme_node_types_1.Extended.Delay(group.location, convertedExpr); } // ___________________MISCELLANEOUS___________________ /** * Parse an import expression. * @param group * @returns */ parseImport(group) { // Form: (import "<source>" (<identifier>*)) // ensure that the group has 3 elements if (group.length() !== 3) { throw new ParserError.ExpectedFormError(this.source, group.firstToken().pos, group.firstToken(), '(import "<source>" (<identifier>*))'); } const elements = group.unwrap(); const source = elements[1]; const identifiers = elements[2]; // source is treated as a single string if (!(0, tokens_1.isToken)(source)) { throw new ParserError.ExpectedFormError(this.source, source.location.start, source, '"<source>"'); } if (source.type !== token_type_1.TokenType.STRING) { throw new ParserError.ExpectedFormError(this.source, source.pos, source, '"<source>"'); } // Identifiers are treated as a group of identifiers if (!(0, tokens_1.isGroup)(identifiers)) { throw new ParserError.ExpectedFormError(this.source, identifiers.pos, identifiers, "(<identifier>*)"); } const identifierElements = identifiers.unwrap(); const convertedIdentifiers = []; for (const identifierElement of identifierElements) { if (!(0, tokens_1.isToken)(identifierElement)) { throw new ParserError.ExpectedFormError(this.source, identifierElement.location.start, identifierElement, "<identifier>"); } if (identifierElement.type !== token_type_1.TokenType.IDENTIFIER) { throw new ParserError.ExpectedFormError(this.source, identifierElement.pos, identifierElement, "<identifier>"); } convertedIdentifiers.push(new scheme_node_types_1.Atomic.Identifier(this.toLocation(identifierElement), identifierElement.lexeme)); } const convertedSource = new scheme_node_types_1.Atomic.StringLiteral(this.toLocation(source), source.literal); return new scheme_node_types_1.Atomic.Import(group.location, convertedSource, convertedIdentifiers); } /** * Parse an export expression. * @param group * @returns */ parseExport(group) { // Form: (export (<definition>)) // ensure that the group has 2 elements if (group.length() !== 2) { throw new ParserError.ExpectedFormError(this.source, group.firstToken().pos, group.firstToken(), "(export (<definition>))"); } const elements = group.unwrap(); const definition = elements[1]; // assert that definition is a group if (!(0, tokens_1.isGroup)(definition)) { throw new ParserError.ExpectedFormError(this.source, definition.pos, definition, "(<definition>)"); } const convertedDefinition = this.parseExpression(definition); // assert that convertedDefinition is a definition if (!(convertedDefinition instanceof scheme_node_types_1.Atomic.Definition || convertedDefinition instanceof scheme_node_types_1.Extended.FunctionDefinition)) { throw new ParserError.ExpectedFormError(this.source, definition.location.start, definition, "(<definition>)"); } return new scheme_node_types_1.Atomic.Export(group.location, convertedDefinition); } /** * Parses a vector expression */ parseVector(group) { // Because of the group invariants, we can safely assume that the group // is strictly of size 2. // Additionally, we can safely assume that the second element is a group // because token HASH_VECTOR expects a parenthesis as the next immediate // token. const elements = group.unwrap()[1]; // Vectors will be treated normally regardless of the quote mode. // but interior expressions will be affected by the mode. const convertedElements = elements .unwrap() .map(this.parseExpression.bind(this)); return new scheme_node_types_1.Atomic.Vector(group.location, convertedElements); } // ___________________________________________________ /** Parses a sequence of tokens into an AST. * * @param group A group of tokens. * @returns An AST. */ parse() { // collect all top-level elements const topElements = []; while (!this.isAtEnd()) { if (this.peek().type === token_type_1.TokenType.EOF) { break; } const currentElement = this.grouping(); if (!currentElement) { continue; } const convertedElement = this.parseExpression(currentElement); topElements.push(convertedElement); } return topElements; } } exports.SchemeParser = SchemeParser; //# sourceMappingURL=scheme-parser.js.map