Skip to content

Commit

Permalink
Merge pull request #328 from alephium/soft_parser
Browse files Browse the repository at this point in the history
Soft Parser: Initial implementation of `SoftParser`
  • Loading branch information
simerplaha authored Nov 28, 2024
2 parents a581676 + 3d74671 commit 4b91123
Show file tree
Hide file tree
Showing 26 changed files with 2,491 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package org.alephium.ralph.lsp.access.compiler
import org.alephium.ralph._
import org.alephium.ralph.lsp.access.compiler.ast.Tree
import org.alephium.ralph.lsp.access.compiler.message.CompilerMessage
import org.alephium.ralph.lsp.access.compiler.message.error.FastParseError
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST
import org.alephium.ralph.lsp.utils.log.ClientLogger
import org.alephium.ralph.lsp.utils.URIUtil

Expand Down Expand Up @@ -46,6 +48,14 @@ object CompilerAccess {
*/
trait CompilerAccess {

/**
* Runs the soft parser.
*
* @param code Code to parse.
* @return Parsing error or successfully parsed [[SoftAST]].
*/
def parseSoft(code: String): Either[FastParseError, SoftAST.BlockBody]

/**
* Runs the parser phase.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import org.alephium.protocol.vm.StatefulContext
import org.alephium.ralph._
import org.alephium.ralph.lsp.access.compiler.ast.Tree
import org.alephium.ralph.lsp.access.compiler.message.CompilerMessage
import org.alephium.ralph.lsp.access.compiler.message.error._
import org.alephium.ralph.lsp.access.compiler.message.error.FastParseError
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST
import org.alephium.ralph.lsp.access.compiler.parser.soft.SoftParser
import org.alephium.ralph.lsp.access.util.TryUtil
import org.alephium.ralph.lsp.utils.log.{ClientLogger, StrictImplicitLogging}

Expand All @@ -36,6 +38,16 @@ import java.net.URI

private object RalphCompilerAccess extends CompilerAccess with StrictImplicitLogging {

/** @inheritdoc */
override def parseSoft(code: String): Either[FastParseError, SoftAST.BlockBody] =
fastparse.parse(code, SoftParser.parse(_)) match {
case Parsed.Success(ast: SoftAST.BlockBody, _) =>
Right(ast)

case failure: Parsed.Failure =>
Left(FastParseError(failure))
}

/** @inheritdoc */
def parseContracts(
fileURI: URI,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ object SourceIndexExtra {
fileURI = Some(fileURI)
)

/**
* Creates a [[SourceIndex]] when the start and end indexes are known.
*
* @param from the starting index of the range (inclusive)
* @param to the ending index of the range (exclusive)
*/
@inline def range(
from: Int,
to: Int): SourceIndex =
SourceIndex(
index = from,
width = to - from,
fileURI = None
)

/**
* Sending negative index to the client would be incorrect.
* This set the index to be an empty range.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.CommonParser._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token}

private object BlockParser {

def clause[Unknown: P](mandatory: Boolean): P[SoftAST.BlockClause] =
P(Index ~ TokenParser.openCurly(mandatory) ~ spaceOrFail.? ~ body(Some(Token.CloseCurly.lexeme.head)) ~ spaceOrFail.? ~ TokenParser.closeCurly ~ Index) map {
case (from, openCurly, preBodySpace, body, postBodySpace, closeCurly, to) =>
SoftAST.BlockClause(
index = range(from, to),
openCurly = openCurly,
preBodySpace = preBodySpace,
body = body,
postBodySpace = postBodySpace,
closeCurly = closeCurly
)
}

def body[Unknown: P]: P[SoftAST.BlockBody] =
body(stopChar = None)

private def body[Unknown: P](stopChar: Option[Char]): P[SoftAST.BlockBody] =
P(Index ~ spaceOrFail.? ~ bodyPart(stopChar).rep ~ Index) map {
case (from, headSpace, parts, to) =>
SoftAST.BlockBody(
index = range(from, to),
prePartsSpace = headSpace,
parts = parts
)
}

private def bodyPart[Unknown: P](stopChar: Option[Char]): P[SoftAST.BlockBodyPart] =
P(Index ~ part(stopChar) ~ spaceOrFail.? ~ Index) map {
case (from, ast, space, to) =>
SoftAST.BlockBodyPart(
index = range(from, to),
part = ast,
postPartSpace = space
)
}

private def part[Unknown: P](stopChar: Option[Char]): P[SoftAST.BodyPartAST] =
P {
TemplateParser.parse |
FunctionParser.parse |
CommentParser.parse |
unresolved(stopChar)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.CommonParser._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST

private object CommentParser {

def parse[Unknown: P]: P[SoftAST.Comment] =
P(Index ~ TokenParser.doubleForwardSlash ~ spaceOrFail.? ~ text.? ~ Index) map {
case (from, doubleForwardSlash, space, text, to) =>
SoftAST.Comment(
index = range(from, to),
doubleForwardSlash = doubleForwardSlash,
space = space,
text = text
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 CommonParser {

def spaceOrFail[Unknown: P]: P[SoftAST.Space] =
P(Index ~ CharsWhileIn(" \t\r\n").! ~ Index) map {
case (from, text, to) =>
SoftAST.Space(
code = text,
index = range(from, to)
)
}

def space[Unknown: P]: P[SoftAST.SpaceAST] =
P(Index ~ spaceOrFail.?) map {
case (_, Some(space)) =>
space

case (from, None) =>
SoftAST.SpaceExpected(range(from, from))
}

def text[Unknown: P]: P[SoftAST.Text] =
P(Index ~ CharsWhileNot(Token.Newline.lexeme).! ~ Index) map {
case (from, text, to) =>
SoftAST.Text(
code = text,
index = range(from, to)
)
}

def unresolved[Unknown: P](stopChar: Option[Char]): P[SoftAST.Unresolved] =
P(Index ~ CharsWhileNot(Token.Space.lexeme ++ Token.Newline.lexeme ++ stopChar).! ~ Index) map {
case (from, text, to) =>
SoftAST.Unresolved(
code = text,
index = range(from, to)
)
}

def identifier[Unknown: P]: P[SoftAST.IdentifierAST] =
P(Index ~ isLetterDigitOrUnderscore.!.? ~ Index) map {
case (from, Some(identifier), to) =>
SoftAST.Identifier(
code = identifier,
index = range(from, to)
)

case (from, None, to) =>
SoftAST.IdentifierExpected(range(from, to))
}

def typeName[Unknown: P]: P[SoftAST.SingleTypeAST] =
P(Index ~ isLetterDigitOrUnderscore.!.? ~ Index) map {
case (from, Some(typeName), to) =>
SoftAST.Type(
code = typeName,
index = range(from, to)
)

case (from, None, to) =>
SoftAST.TypeExpected(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 || char == Token.Underscore.lexeme.head
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.alephium.ralph.lsp.access.compiler.parser.soft

import org.alephium.ralph.lsp.access.compiler.CompilerAccess

object Demo extends App {

val compiler =
CompilerAccess.ralphc

val ast =
compiler.parseSoft {
"""
|Contract HelloWorld(type: SomeType, tuple: (A, B)) {
|
| fn function(nested_tuple: (A, (B, C))) -> ABC {
| // comment
| }
|
| 🚀
|}
|""".stripMargin
} match {
case Right(softAST) =>
softAST

case Left(parseError) =>
// Format and print the FastParse error.
// The goal here is to ensure that the parser always succeeds, regardless of the input.
// Therefore, this error should never occur. If an error does occur, the parser should be updated to handle those cases.
println(parseError.error.toFormatter().format(Some(Console.RED)))
throw parseError.error
}

// Emit code generated from AST
println("Parsed code:")
println(ast.toCode())

// Emit parsed AST
println("SoftAST:")
println(ast.toStringTree())

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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, SourceIndexExtension}
import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST

private object FunctionParser {

def parse[Unknown: P]: P[SoftAST.Function] =
P(TokenParser.fn ~ space ~ signature ~ spaceOrFail.? ~ BlockParser.clause(mandatory = false).?) map {
case (fnDeceleration, headSpace, signature, tailSpace, block) =>
SoftAST.Function(
index = range(fnDeceleration.index.from, signature.index.to),
fn = fnDeceleration,
preSignatureSpace = headSpace,
signature = signature,
postSignatureSpace = tailSpace,
block = block
)
}

private def signature[Unknown: P]: P[SoftAST.FunctionSignature] =
P(Index ~ identifier ~ spaceOrFail.? ~ ParameterParser.parse ~ spaceOrFail.? ~ returnSignature ~ Index) map {
case (from, fnName, headSpace, params, tailSpace, returns, to) =>
SoftAST.FunctionSignature(
index = range(from, to),
fnName = fnName,
preParamSpace = headSpace,
params = params,
postParamSpace = tailSpace,
returned = returns
)
}

private def returnSignature[Unknown: P]: P[SoftAST.FunctionReturnAST] =
P(Index ~ (TokenParser.forwardArrow ~ spaceOrFail.? ~ TypeParser.parse).? ~ Index) map {
case (from, Some((forwardArrow, space, tpe)), to) =>
SoftAST.FunctionReturn(
index = range(from, to),
forwardArrow = forwardArrow,
space = space,
tpe = tpe
)

case (from, None, to) =>
SoftAST.FunctionReturnExpected(range(from, to))
}

}
Loading

0 comments on commit 4b91123

Please sign in to comment.