diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AccessModifierParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AccessModifierParser.scala index cef9f5052..571af486b 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AccessModifierParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AccessModifierParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object AccessModifierParser { @@ -28,13 +27,13 @@ private object AccessModifierParser { P { Index ~ TokenParser.parseOrFail(Token.Pub) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ Index } map { - case (from, doubleForwardSlash, space, to) => + case (from, pub, space, to) => SoftAST.AccessModifier( index = range(from, to), - doubleForwardSlash, + pub = pub, postTokenSpace = space ) } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParser.scala index c7e4c71f6..4b21b5a33 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object AnnotationParser { @@ -40,11 +39,11 @@ private object AnnotationParser { P { Index ~ TokenParser.parseOrFail(Token.At) ~ - spaceOrFail.? ~ - identifier ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ + IdentifierParser.parse ~ + SpaceParser.parseOrFail.? ~ TupleParser.parseOrFail.? ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, at, preIdentifierSpace, identifier, postIdentifierSpace, tuple, postTupleSpace, to) => diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala index 6ddf2dbcf..7833e9f60 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object AssignmentAccessModifierParser { @@ -28,7 +27,7 @@ private object AssignmentAccessModifierParser { P { Index ~ (TokenParser.parseOrFail(Token.Let) | TokenParser.parseOrFail(Token.Mut)) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, dataAssignment, space, to) => diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala index fce74b9de..f863dcb12 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object AssignmentParser { @@ -12,10 +11,10 @@ private object AssignmentParser { P { Index ~ AssignmentAccessModifierParser.parseOrFail.rep ~ - identifierOrFail ~ - spaceOrFail.? ~ + IdentifierParser.parseOrFail ~ + SpaceParser.parseOrFail.? ~ TokenParser.parseOrFail(Token.Equal) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/BlockParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/BlockParser.scala index df07260a5..6e2b3b3db 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/BlockParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/BlockParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object BlockParser { @@ -28,9 +27,9 @@ private object BlockParser { P { Index ~ TokenParser.parse(required, Token.OpenCurly) ~ - spaceOrFail.? ~ - body(Some(Token.CloseCurly.lexeme)) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ + body(Token.CloseCurly) ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.CloseCurly) ~ Index } map { @@ -46,13 +45,13 @@ private object BlockParser { } def body[Unknown: P]: P[SoftAST.BlockBody] = - body(stopChars = None) + body() - private def body[Unknown: P](stopChars: Option[String]): P[SoftAST.BlockBody] = + private def body[Unknown: P](stop: Token*): P[SoftAST.BlockBody] = P { Index ~ - spaceOrFail.? ~ - bodyPart(stopChars).rep ~ + SpaceParser.parseOrFail.? ~ + bodyPart(stop).rep ~ Index } map { case (from, headSpace, parts, to) => @@ -63,11 +62,11 @@ private object BlockParser { ) } - private def bodyPart[Unknown: P](stopChars: Option[String]): P[SoftAST.BlockBodyPart] = + private def bodyPart[Unknown: P](stop: Seq[Token]): P[SoftAST.BlockBodyPart] = P { Index ~ - part(stopChars) ~ - spaceOrFail.? ~ + part(stop) ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, ast, space, to) => @@ -78,14 +77,14 @@ private object BlockParser { ) } - private def part[Unknown: P](stopChars: Option[String]): P[SoftAST.BodyPartAST] = + private def part[Unknown: P](stop: Seq[Token]): P[SoftAST.BodyPartAST] = P { TemplateParser.parseOrFail | DataTemplateParser.parseOrFail | FunctionParser.parseOrFail | ExpressionParser.parseOrFail | CommentParser.parseOrFail | - unresolved(stopChars) + UnresolvedParser.parseOrFail(stop: _*) } } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommentParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommentParser.scala index 066785499..ff5600d16 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommentParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommentParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object CommentParser { @@ -39,9 +38,9 @@ private object CommentParser { def parseOrFail[Unknown: P]: P[SoftAST.Comments] = P { Index ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ one.rep(1) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, preSpace, comments, postSpace, to) => @@ -64,9 +63,9 @@ private object CommentParser { P { Index ~ TokenParser.parseOrFailUndocumented(Token.DoubleForwardSlash) ~ - spaceOrFail.? ~ - text.? ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ + TextParser.parseOrFail(Token.Newline).? ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, doubleForwardSlash, preTextSpace, text, postTextSpace, to) => diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommonParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommonParser.scala deleted file mode 100644 index 1141d6373..000000000 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/CommonParser.scala +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2024 The Alephium Authors -// This file is part of the alephium project. -// -// The library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the library. If not, see http://www.gnu.org/licenses/. - -package org.alephium.ralph.lsp.access.compiler.parser.soft - -import fastparse._ -import fastparse.NoWhitespace.noWhitespaceImplicit -import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.{point, range} -import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} - -private object CommonParser { - - def space[Unknown: P]: P[SoftAST.SpaceAST] = - P(Index ~ spaceOrFail.?) map { - case (_, Some(space)) => - space - - case (from, None) => - SoftAST.SpaceExpected(range(from, from)) - } - - def spaceOrFail[Unknown: P]: P[SoftAST.Space] = - P(toCodeOrFail(CharsWhileIn(" \t\r\n").!)) map { - text => - SoftAST.Space(text) - } - - def text[Unknown: P]: P[SoftAST.CodeString] = - P(Index ~ CharsWhileNot(Token.Newline.lexeme).! ~ Index) map { - case (from, text, to) => - SoftAST.CodeString( - text = text, - index = range(from, to) - ) - } - - def unresolved[Unknown: P](stopChars: Option[String]): P[SoftAST.Unresolved] = - P { - Index ~ - toCodeOrFail(CharsWhileNot(Token.Space.lexeme ++ Token.Newline.lexeme ++ stopChars.getOrElse("")).!) ~ - CommentParser.parseOrFail.? ~ - Index - } map { - case (from, text, tailComment, to) => - SoftAST.Unresolved( - index = range(from, to), - code = text, - documentation = tailComment - ) - } - - def identifier[Unknown: P](required: Boolean): P[SoftAST.IdentifierAST] = - if (required) - identifier - else - identifierOrFail - - def identifier[Unknown: P]: P[SoftAST.IdentifierAST] = - P(Index ~ identifierOrFail.?) map { - case (_, Some(identifier)) => - identifier - - case (from, None) => - SoftAST.IdentifierExpected(point(from)) - } - - /** - * Parses identifiers as long as they are not reserved tokens [[Token.Reserved]]. - * - * For example, the following code will result in an [[SoftAST.IdentifierExpected]] error - * because `let` is a reserved token [[Token.Let]]: - * - * {{{ - * fn let() -> () - * }}} - * - * TODO: Handle cases such as `fn letter() -> ()` - * - * @return A successfully parsed identifier instance [[SoftAST.Identifier]] or a parser error. - */ - def identifierOrFail[Unknown: P]: P[SoftAST.Identifier] = - P { - Index ~ - CommentParser.parseOrFail.? ~ - !TokenParser.Reserved ~ - toCodeOrFail(isLetterDigitOrUnderscore.!) ~ - Index - } map { - case (from, documentation, code, to) => - SoftAST.Identifier( - index = range(from, to), - code = code, - documentation = documentation - ) - } - - def toCodeOrFail[Unknown: P](parser: => P[String]): P[SoftAST.CodeString] = - P(Index ~ parser ~ Index) map { - case (from, code, to) => - SoftAST.CodeString( - text = code, - index = range(from, to) - ) - } - - private def CharsWhileNot[Unknown: P](chars: String): P[Unit] = - CharsWhile { - char => - !(chars contains char) - } - - private def isLetterDigitOrUnderscore[Unknown: P]: P[Unit] = - CharsWhile { - char => - char.isLetterOrDigit || Token.Underscore.lexeme.contains(char) - } - -} diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/DataTemplateParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/DataTemplateParser.scala index 41af9b487..98f161e58 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/DataTemplateParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/DataTemplateParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object DataTemplateParser { @@ -12,9 +11,9 @@ private object DataTemplateParser { P { Index ~ (TokenParser.parseOrFail(Token.Struct) | TokenParser.parseOrFail(Token.Enum) | TokenParser.parseOrFail(Token.Event)) ~ - space ~ - identifier ~ - spaceOrFail.? ~ + SpaceParser.parse ~ + IdentifierParser.parse ~ + SpaceParser.parseOrFail.? ~ ParameterParser.parse ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala index 1d156e8b5..de19158ab 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST private object ExpressionParser { @@ -84,7 +83,7 @@ private object ExpressionParser { ReferenceCallParser.parseOrFail | AnnotationParser.parseOrFail | TupleParser.parseOrFail | - identifierOrFail + IdentifierParser.parseOrFail } } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ForLoopParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ForLoopParser.scala index e27fa3f46..2ca74a1dc 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ForLoopParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ForLoopParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object ForLoopParser { @@ -12,21 +11,21 @@ private object ForLoopParser { P { Index ~ TokenParser.parseOrFail(Token.For) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.OpenParen) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.Semicolon) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.Semicolon) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.CloseParen) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ BlockParser.clause(required = true) ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FunctionParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FunctionParser.scala index dfc7dcf64..848c13f92 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FunctionParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FunctionParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object FunctionParser { @@ -34,12 +33,12 @@ private object FunctionParser { P { Index ~ AnnotationParser.parseOrFail.rep ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ AccessModifierParser.parseOrFail.? ~ TokenParser.parseOrFail(Token.Fn) ~ - space ~ + SpaceParser.parse ~ signature ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ BlockParser.clause(required = false).? ~ Index } map { @@ -64,7 +63,15 @@ private object FunctionParser { * such as its name, parameters and return type. */ private def signature[Unknown: P]: P[SoftAST.FunctionSignature] = - P(Index ~ identifier ~ spaceOrFail.? ~ ParameterParser.parse ~ spaceOrFail.? ~ returnSignature ~ Index) map { + P { + Index ~ + IdentifierParser.parse ~ + SpaceParser.parseOrFail.? ~ + ParameterParser.parse ~ + SpaceParser.parseOrFail.? ~ + returnSignature ~ + Index + } map { case (from, fnName, headSpace, params, tailSpace, returns, to) => SoftAST.FunctionSignature( index = range(from, to), @@ -83,7 +90,11 @@ private object FunctionParser { * type or an error indicating that the return type is expected. */ private def returnSignature[Unknown: P]: P[SoftAST.FunctionReturnAST] = - P(Index ~ (TokenParser.parse(Token.ForwardArrow) ~ spaceOrFail.? ~ TypeParser.parse).? ~ Index) map { + P { + Index ~ + (TokenParser.parse(Token.ForwardArrow) ~ SpaceParser.parseOrFail.? ~ TypeParser.parse).? ~ + Index + } map { case (from, Some((forwardArrow, space, tpe)), to) => SoftAST.FunctionReturn( index = range(from, to), diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/IdentifierParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/IdentifierParser.scala new file mode 100644 index 000000000..99def2be8 --- /dev/null +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/IdentifierParser.scala @@ -0,0 +1,64 @@ +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import fastparse._ +import fastparse.NoWhitespace.noWhitespaceImplicit +import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra._ +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} + +object IdentifierParser { + + def parse[Unknown: P](required: Boolean): P[SoftAST.IdentifierAST] = + if (required) + parse + else + parseOrFail + + def parse[Unknown: P]: P[SoftAST.IdentifierAST] = + P(Index ~ parseOrFail.?) map { + case (_, Some(identifier)) => + identifier + + case (from, None) => + SoftAST.IdentifierExpected(point(from)) + } + + /** + * Parses identifiers as long as they are not reserved tokens [[Token.Reserved]]. + * + * For example, the following code will result in an [[SoftAST.IdentifierExpected]] error + * because `let` is a reserved token [[Token.Let]]: + * + * {{{ + * fn let() -> () + * }}} + * + * @return A successfully parsed identifier instance [[SoftAST.Identifier]] or a parser error. + */ + def parseOrFail[Unknown: P]: P[SoftAST.Identifier] = { + @inline def reserved() = + TokenParser.Reserved(Token.Hash) + + P { + Index ~ + CommentParser.parseOrFail.? ~ + !(reserved() ~ SpaceParser.parseOrFail) ~ // disallow reserved names such as `let mut = 1`. + !(reserved() ~ End) ~ // also handle cases where tail is the end of file `let mut`. + CodeParser.parseOrFail(isDevDefinedName.!) ~ + Index + } map { + case (from, documentation, code, to) => + SoftAST.Identifier( + index = range(from, to), + code = code, + documentation = documentation + ) + } + } + + private def isDevDefinedName[Unknown: P]: P[Unit] = + CharsWhile { + char => + char.isLetterOrDigit || Token.Underscore.lexeme.contains(char) + } + +} diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala index 782641381..8942fef75 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST case object InfixCallParser { @@ -12,9 +11,9 @@ case object InfixCallParser { P { Index ~ ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = true) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.InfixOperatorOrFail ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala index 77619f7e4..0b43fbcfe 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} case object MethodCallParser { @@ -12,7 +11,7 @@ case object MethodCallParser { P { Index ~ ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = false) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ dotCall.rep(1) ~ Index } map { @@ -29,7 +28,7 @@ case object MethodCallParser { P { Index ~ TokenParser.parseOrFail(Token.Dot) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ReferenceCallParser.parse ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReferenceCallParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReferenceCallParser.scala index d2cc4a546..6bc9f1d90 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReferenceCallParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReferenceCallParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST private object ReferenceCallParser { @@ -26,8 +25,8 @@ private object ReferenceCallParser { private def parse[Unknown: P](required: Boolean): P[SoftAST.ReferenceCall] = P { Index ~ - identifier(required) ~ - spaceOrFail.? ~ + IdentifierParser.parse(required) ~ + SpaceParser.parseOrFail.? ~ TupleParser.parse(required) ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReturnStatementParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReturnStatementParser.scala index bd6c20a1a..d7ee11aca 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReturnStatementParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ReturnStatementParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object ReturnStatementParser { @@ -12,7 +11,7 @@ private object ReturnStatementParser { P { Index ~ TokenParser.parseOrFail(Token.Return) ~ - space ~ + SpaceParser.parse ~ ExpressionParser.parse ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/SpaceParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/SpaceParser.scala new file mode 100644 index 000000000..54f2a8057 --- /dev/null +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/SpaceParser.scala @@ -0,0 +1,41 @@ +// Copyright 2024 The Alephium Authors +// This file is part of the alephium project. +// +// The library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the library. If not, see http://www.gnu.org/licenses/. + +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import fastparse._ +import fastparse.NoWhitespace.noWhitespaceImplicit +import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} + +object SpaceParser { + + def parse[Unknown: P]: P[SoftAST.SpaceAST] = + P(Index ~ parseOrFail.?) map { + case (_, Some(space)) => + space + + case (from, None) => + SoftAST.SpaceExpected(range(from, from)) + } + + def parseOrFail[Unknown: P]: P[SoftAST.Space] = + P(CodeParser.parseOrFail(TokenParser.WhileInOrFail(Token.Space, Token.Tab, Token.Newline).!)) map { + text => + SoftAST.Space(text) + } + +} diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TemplateParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TemplateParser.scala index 308ddceb3..34584e6fc 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TemplateParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TemplateParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object TemplateParser { @@ -12,11 +11,11 @@ private object TemplateParser { P { Index ~ (TokenParser.parseOrFail(Token.Contract) | TokenParser.parseOrFail(Token.TxScript) | TokenParser.parseOrFail(Token.AssetScript)) ~ - space ~ - identifier ~ - spaceOrFail.? ~ + SpaceParser.parse ~ + IdentifierParser.parse ~ + SpaceParser.parseOrFail.? ~ ParameterParser.parseOrFail.? ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ inheritance.rep ~ BlockParser.clause(required = true) ~ Index @@ -40,9 +39,9 @@ private object TemplateParser { P { Index ~ (TokenParser.parseOrFail(Token.Implements) | TokenParser.parseOrFail(Token.Extends)) ~ - space ~ - (ReferenceCallParser.parseOrFail | identifier) ~ - spaceOrFail.? ~ + SpaceParser.parse ~ + (ReferenceCallParser.parseOrFail | IdentifierParser.parse) ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, token, preConstructorCallSpace, constructorCall, postConstructorCallSpace, to) => diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TextParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TextParser.scala new file mode 100644 index 000000000..d9587b278 --- /dev/null +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TextParser.scala @@ -0,0 +1,19 @@ +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import fastparse._ +import fastparse.NoWhitespace.noWhitespaceImplicit +import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} + +private object TextParser { + + def parseOrFail[Unknown: P](stop: Token*): P[SoftAST.CodeString] = + P(Index ~ TokenParser.WhileNotOrFail(stop: _*).! ~ Index) map { + case (from, text, to) => + SoftAST.CodeString( + text = text, + index = range(from, to) + ) + } + +} diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TokenParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TokenParser.scala index 9fa173821..6a50b4167 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TokenParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TokenParser.scala @@ -57,8 +57,8 @@ private object TokenParser { /** * Parses all reserved tokens defined in [[Token.reserved]] and returns the first match. */ - def Reserved[Unknown: P]: P[Token.Reserved] = - ParserUtil.orTokenCombinator(Token.reserved.iterator) + def Reserved[Unknown: P](remove: Token.Reserved*): P[Token.Reserved] = + ParserUtil.orTokenCombinator(Token.reserved.diff(remove).iterator) /** * Parses all tokens of type [[Token.InfixOperator]] and also their comments. @@ -66,7 +66,7 @@ private object TokenParser { def InfixOperatorOrFail[Unknown: P]: P[SoftAST.TokenDocumented[Token.InfixOperator]] = { val infixOps = ParserUtil.orCombinator( - items = Token.infix.iterator.filter(_ != Token.ForwardSlash), // remove forward-slash + items = Token.infix.diff(Seq(Token.ForwardSlash)).iterator, // remove forward-slash parser = TokenParser.parseOrFail(_: Token.InfixOperator) ) @@ -78,4 +78,17 @@ private object TokenParser { infixOps | forwardSlashOperator } + /** + * Reads characters until at least one of the input tokens is matched. + * + * If none of the tokens are found, the parser fails. + * + * @param tokens the token to check for. + */ + def WhileNotOrFail[Unknown: P](tokens: Token*): P[Unit] = + P((!ParserUtil.orTokenCombinator(tokens.iterator) ~ AnyChar).rep(1)) + + def WhileInOrFail[Unknown: P](tokens: Token*): P[Unit] = + P(ParserUtil.orTokenCombinator(tokens.iterator).rep(1)) + } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TupleParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TupleParser.scala index cb340f2f8..23c108c98 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TupleParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TupleParser.scala @@ -19,7 +19,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.{point, range} -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object TupleParser { @@ -45,10 +44,10 @@ private object TupleParser { P { Index ~ TokenParser.parse(required, Token.OpenParen) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ Index ~ ExpressionParser.parseOrFail.? ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ tailParams.rep ~ TokenParser.parse(Token.CloseParen) ~ Index @@ -85,9 +84,9 @@ private object TupleParser { P { Index ~ TokenParser.parseOrFail(Token.Comma) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ Index } map { case (from, comma, preParamNameSpace, argumentName, postParamNameSpace, to) => diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeAssignmentParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeAssignmentParser.scala index 059353ef8..c1bd02039 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeAssignmentParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeAssignmentParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object TypeAssignmentParser { @@ -12,10 +11,10 @@ private object TypeAssignmentParser { P { Index ~ AssignmentAccessModifierParser.parseOrFail.rep ~ - identifierOrFail ~ - spaceOrFail.? ~ + IdentifierParser.parseOrFail ~ + SpaceParser.parseOrFail.? ~ TokenParser.parseOrFail(Token.Colon) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TypeParser.parse ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeParser.scala index 7014af840..0a5f4328b 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TypeParser.scala @@ -17,12 +17,11 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST private object TypeParser { def parse[Unknown: P]: P[SoftAST.TypeAST] = - P(TupleParser.parseOrFail | identifier) + P(TupleParser.parseOrFail | IdentifierParser.parse) } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala new file mode 100644 index 000000000..8021c4135 --- /dev/null +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala @@ -0,0 +1,41 @@ +// Copyright 2024 The Alephium Authors +// This file is part of the alephium project. +// +// The library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the library. If not, see http://www.gnu.org/licenses/. + +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import fastparse._ +import fastparse.NoWhitespace.noWhitespaceImplicit +import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} + +private object UnresolvedParser { + + def parseOrFail[Unknown: P](stop: Token*): P[SoftAST.Unresolved] = + P { + Index ~ + CodeParser.parseOrFail(TokenParser.WhileNotOrFail(stop :+ Token.Space :+ Token.Newline: _*).!) ~ + CommentParser.parseOrFail.? ~ + Index + } map { + case (from, text, tailComment, to) => + SoftAST.Unresolved( + index = range(from, to), + code = text, + documentation = tailComment + ) + } + +} diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/WhileLoopParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/WhileLoopParser.scala index 89d786364..e000326e5 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/WhileLoopParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/WhileLoopParser.scala @@ -3,7 +3,6 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft import fastparse._ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range -import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._ import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} private object WhileLoopParser { @@ -12,13 +11,13 @@ private object WhileLoopParser { P { Index ~ TokenParser.parseOrFail(Token.While) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.OpenParen) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ ExpressionParser.parse ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ TokenParser.parse(Token.CloseParen) ~ - spaceOrFail.? ~ + SpaceParser.parseOrFail.? ~ BlockParser.clause(required = true) ~ Index } map { diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/Token.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/Token.scala index b89af88f1..db370aaf2 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/Token.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/Token.scala @@ -52,6 +52,7 @@ object Token { sealed abstract class Operator(override val lexeme: String) extends Token case object Equality extends Operator("==") with Reserved with InfixOperator case object GreaterThanOrEqual extends Operator(">=") with Reserved with InfixOperator + case object PlusPlus extends Operator("++") with Reserved with InfixOperator case object PlusEquals extends Operator("+=") with Reserved with InfixOperator case object MinusEquals extends Operator("-=") with Reserved with InfixOperator case object LessThanOrEqual extends Operator("<=") with Reserved with InfixOperator @@ -85,6 +86,7 @@ object Token { case object Semicolon extends Delimiter(";") with Reserved case object Newline extends Delimiter(System.lineSeparator()) case object Space extends Delimiter(" ") + case object Tab extends Delimiter("\t") case object DoubleForwardSlash extends Delimiter("//") with Reserved sealed abstract class Punctuator(override val lexeme: String) extends Token diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/util/ParserUtil.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/util/ParserUtil.scala index c4b7b174d..84d3c80df 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/util/ParserUtil.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/util/ParserUtil.scala @@ -34,10 +34,10 @@ object ParserUtil { parser = createParser(_: T) ) - @inline private def createParser[Unknown: P, T <: Token](item: T): P[T] = - P(item.lexeme) map { + @inline private def createParser[Unknown: P, T <: Token](token: T): P[T] = + P(token.lexeme) map { _ => - item + token } } diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/IdentifierSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/IdentifierSpec.scala new file mode 100644 index 000000000..3eebf0fa3 --- /dev/null +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/IdentifierSpec.scala @@ -0,0 +1,34 @@ +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.Token +import org.alephium.ralph.lsp.access.compiler.parser.soft.TestParser._ +import org.alephium.ralph.lsp.access.util.TestFastParse._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +class IdentifierSpec extends AnyWordSpec with Matchers { + + "disallow reserved tokens to be used as identifier" when { + val reserved = + Token.reserved.diff(Seq(Token.Hash)) // Remove hash because `let hash = #` is valid + + "tail has space" in { + reserved foreach { + reserved => + assertIsFastParseError { + parseIdentifier(s"${reserved.lexeme} ") + } + } + } + + "tail is end-of-file" in { + reserved foreach { + reserved => + assertIsFastParseError { + parseIdentifier(reserved.lexeme) + } + } + } + } + +} diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala index a1087a13e..dd00a30fa 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala @@ -51,14 +51,17 @@ object TestParser { def parseType(code: String): SoftAST.TypeAST = runSoftParser(TypeParser.parse(_))(code) - def parseReservedToken(code: String): Token.Reserved = - runAnyParser(TokenParser.Reserved(_))(code) + def parseReservedToken(remove: Token.Reserved*)(code: String): Token.Reserved = + runAnyParser(TokenParser.Reserved(remove: _*)(_))(code) def parseInfixOperatorOrFail(code: String): SoftAST.TokenDocumented[Token.InfixOperator] = runAnyParser(TokenParser.InfixOperatorOrFail(_))(code) - def parseReservedTokenOrError(code: String): Either[Parsed.Failure, Token.Reserved] = - runAnyParserOrError(TokenParser.Reserved(_))(code) + def parseReservedTokenOrError(remove: Token.Reserved*)(code: String): Either[Parsed.Failure, Token.Reserved] = + runAnyParserOrError(TokenParser.Reserved(remove: _*)(_))(code) + + def parseIdentifier(code: String): SoftAST.IdentifierAST = + runSoftParser(IdentifierParser.parseOrFail(_))(code) def findAnnotation(identifier: String)(code: String): Option[SoftAST.Annotation] = findAnnotation( @@ -96,7 +99,7 @@ object TestParser { // AST returned by the parser should ALWAYS emit code identical to the inputted code. // Always assert this for each test case. try - code shouldBe astToCode + astToCode shouldBe code catch { case throwable: Throwable => // Print debug info for cases where the emitted code differs from the input code. @@ -118,7 +121,8 @@ object TestParser { result match { case Left(error) => // Print a formatted error so it's easier to debug. - fail(CompilerError.FastParseError(error).toFormatter().format(Some(Console.RED))) + val throwable = CompilerError.FastParseError(error) + fail(throwable.toFormatter().format(Some(Console.RED)), throwable) case Right(ast) => ast diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/ReservedTokenSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/ReservedTokenSpec.scala index 8adc7ecdd..cb6af9f71 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/ReservedTokenSpec.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/ReservedTokenSpec.scala @@ -10,20 +10,31 @@ import scala.util.Random class ReservedTokenSpec extends AnyWordSpec with Matchers { - "parse reserved tokens" in { - val tokens = - Random.shuffle(Token.reserved.toList) + "succeed" when { + "tokens are reserved" in { + val tokens = + Random.shuffle(Token.reserved.toList) - tokens should not be empty + tokens should not be empty - tokens foreach { - token => - parseReservedToken(token.lexeme) shouldBe token + tokens foreach { + token => + parseReservedToken()(token.lexeme) shouldBe token + } } } - "parse reserved without spaces" in { - parseReservedTokenOrError("blah").left.value shouldBe a[Parsed.Failure] + "fail" when { + "token is not reserved" in { + parseReservedTokenOrError()("blah").left.value shouldBe a[Parsed.Failure] + } + + "a reserved token is removed" in { + // First parse without removing Hash. + parseReservedTokenOrError()(Token.Hash.lexeme).value shouldBe Token.Hash + // Then parse removing Hash. This should error because Hash is no longer a reserved token. + parseReservedTokenOrError(remove = Token.Hash)(Token.Hash.lexeme).left.value shouldBe a[Parsed.Failure] + } } } diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/util/TestFastParse.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/util/TestFastParse.scala new file mode 100644 index 000000000..36873e62f --- /dev/null +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/util/TestFastParse.scala @@ -0,0 +1,18 @@ +package org.alephium.ralph.lsp.access.util + +import org.alephium.ralph.error.CompilerError +import org.scalatest.TryValues._ +import org.scalatest.matchers.should.Matchers._ +import org.scalatest.Assertion + +import scala.util.Try + +object TestFastParse { + + def assertIsFastParseError[A](f: => A): Assertion = + Try(f) + .failure + .exception + .getCause shouldBe a[CompilerError.FastParseError] + +}