From df1c5bcf7de3c1fbe804ed880d87eee5b074fc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Leuth=C3=A4user?= <1417198+max-leuthaeuser@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:43:47 +0100 Subject: [PATCH] Added operator folding --- Package.swift | 1 + Sources/SwiftAstGenLib/SyntaxParser.swift | 7 +- .../src/test/scala/SwiftNodeSyntaxTest.scala | 121 ++++++++++++------ 3 files changed, 91 insertions(+), 38 deletions(-) diff --git a/Package.swift b/Package.swift index 01a16d8..bd1b837 100644 --- a/Package.swift +++ b/Package.swift @@ -20,6 +20,7 @@ let package = Package( dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftOperators", package: "swift-syntax"), "CodeGeneration", ] ), diff --git a/Sources/SwiftAstGenLib/SyntaxParser.swift b/Sources/SwiftAstGenLib/SyntaxParser.swift index 56906cf..ee8fdda 100644 --- a/Sources/SwiftAstGenLib/SyntaxParser.swift +++ b/Sources/SwiftAstGenLib/SyntaxParser.swift @@ -1,5 +1,6 @@ import Foundation import SwiftParser +import SwiftOperators @_spi(RawSyntax) import SwiftSyntax extension SyntaxProtocol { @@ -56,10 +57,12 @@ struct SyntaxParser { prettyPrint: Bool ) throws -> String { let code = try String(contentsOf: fileUrl) + let opPrecedence = OperatorTable.standardOperators let ast = Parser.parse(source: code) + let folded = try opPrecedence.foldAll(ast) - let locationConverter = SourceLocationConverter(fileName: fileUrl.path, tree: ast) - let treeNode = ast.toJson(converter: locationConverter) + let locationConverter = SourceLocationConverter(fileName: fileUrl.path, tree: folded) + let treeNode = folded.toJson(converter: locationConverter) treeNode.projectFullPath = srcDir.standardized.resolvingSymlinksInPath().path treeNode.fullFilePath = fileUrl.standardized.resolvingSymlinksInPath().path diff --git a/Tests/ScalaSwiftNodeSyntaxTests/src/test/scala/SwiftNodeSyntaxTest.scala b/Tests/ScalaSwiftNodeSyntaxTests/src/test/scala/SwiftNodeSyntaxTest.scala index daceccc..d924a1d 100644 --- a/Tests/ScalaSwiftNodeSyntaxTests/src/test/scala/SwiftNodeSyntaxTest.scala +++ b/Tests/ScalaSwiftNodeSyntaxTests/src/test/scala/SwiftNodeSyntaxTest.scala @@ -3,14 +3,13 @@ import better.files.* import scala.sys.process.* import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import org.scalatest.BeforeAndAfterAll import SwiftNodeSyntax.SourceFileSyntax import SwiftNodeSyntax._ import java.util.concurrent.ConcurrentLinkedQueue import scala.jdk.CollectionConverters._ -class SwiftNodeSyntaxTest extends AnyWordSpec with Matchers with BeforeAndAfterAll { +class SwiftNodeSyntaxTest extends AnyWordSpec with Matchers { private val shellPrefix: Seq[String] = if (scala.util.Properties.isWin) "cmd" :: "/c" :: Nil else "sh" :: "-c" :: Nil @@ -34,49 +33,99 @@ class SwiftNodeSyntaxTest extends AnyWordSpec with Matchers with BeforeAndAfterA "SwiftAstGen-linux" } - private val testFiles: Map[String, String] = Map("main.swift" -> "var x = 1") - - private val projectUnderTest: File = { - val dir = File.newTemporaryDirectory("swiftastgentests") - testFiles.foreach { case (testFile, content) => - val file = dir / testFile - file.createIfNotExists(createParents = true) - file.write(content) - } - dir - } - - private def runSwiftAstGen(): Unit = { + private def runSwiftAstGen(projectUnderTest: File): Unit = { val path = (File(".").parent.parent / executableName).toJava.toPath.normalize.toAbsolutePath.toString println("Running: " + path) run(path, projectUnderTest.pathAsString) } - override def beforeAll(): Unit = runSwiftAstGen() - - override def afterAll(): Unit = projectUnderTest.delete(swallowIOExceptions = true) - "Using the SwiftNodeSyntax API" should { "allow to grab a SourceFileSyntax node and its content" in { - testFiles.foreach { case (testFile, _) => - val lines = (projectUnderTest / "ast_out" / s"$testFile.json").contentAsString - val json = ujson.read(lines) - val sourceFileSyntax = SwiftNodeSyntax.createSwiftNode(json).asInstanceOf[SourceFileSyntax] - val Seq(codeBlock) = sourceFileSyntax.statements.children - codeBlock.item match { - case v: VariableDeclSyntax => - v.bindings.children.head.pattern match { - case ident: IdentifierPatternSyntax => - ident.identifier match { - case identifier(json) => json("tokenKind").str shouldBe "identifier(\"x\")" - case other => fail("Should have a token identifier here but got: " + other) - } - case other => fail("Should have a IdentifierPatternSyntax here but got: " + other) - } - case other => fail("Should have a VariableDeclSyntax here but got: " + other) - } + val projectUnderTest: File = File.newTemporaryDirectory("swiftastgentests") + val testFile = projectUnderTest / "main.swift" + val testContent = "var x = 1" + testFile.createIfNotExists(createParents = true) + testFile.write(testContent) + runSwiftAstGen(projectUnderTest) + + val lines = (projectUnderTest / "ast_out" / s"${testFile.name}.json").contentAsString + val json = ujson.read(lines) + val sourceFileSyntax = SwiftNodeSyntax.createSwiftNode(json).asInstanceOf[SourceFileSyntax] + + val Seq(codeBlock) = sourceFileSyntax.statements.children + codeBlock.item match { + case v: VariableDeclSyntax => + v.bindings.children.head.pattern match { + case ident: IdentifierPatternSyntax => + ident.identifier match { + case identifier(json) => json("tokenKind").str shouldBe "identifier(\"x\")" + case other => fail("Should have a token identifier here but got: " + other) + } + case other => fail("Should have a IdentifierPatternSyntax here but got: " + other) + } + case other => fail("Should have a VariableDeclSyntax here but got: " + other) } + + projectUnderTest.delete(swallowIOExceptions = true) + } + + "allow to grab a binary expression with operator folding" in { + val projectUnderTest: File = File.newTemporaryDirectory("swiftastgentests") + val testFile = projectUnderTest / "main.swift" + val testContent = "1 + 2 * 3" + testFile.createIfNotExists(createParents = true) + testFile.write(testContent) + runSwiftAstGen(projectUnderTest) + + val lines = (projectUnderTest / "ast_out" / s"${testFile.name}.json").contentAsString + val json = ujson.read(lines) + val sourceFileSyntax = SwiftNodeSyntax.createSwiftNode(json).asInstanceOf[SourceFileSyntax] + + val Seq(codeBlock) = sourceFileSyntax.statements.children + codeBlock.item match { + case v: InfixOperatorExprSyntax => + val leftExpr = v.leftOperand + val op = v.operator + val rightExpr = v.rightOperand + + leftExpr shouldBe a[IntegerLiteralExprSyntax] + leftExpr.asInstanceOf[IntegerLiteralExprSyntax].literal match { + case integerLiteral(json) => json("tokenKind").str shouldBe """integerLiteral("1")""" + case other => fail("Should have a integerLiteral here but got: " + other) + } + op shouldBe a[BinaryOperatorExprSyntax] + op.asInstanceOf[BinaryOperatorExprSyntax].operator match { + case binaryOperator(json) => json("tokenKind").str shouldBe """binaryOperator("+")""" + case other => fail("Should have a binaryOperator here but got: " + other) + } + rightExpr match { + case v: InfixOperatorExprSyntax => + val leftExpr = v.leftOperand + val op = v.operator + val rightExpr = v.rightOperand + + leftExpr shouldBe a[IntegerLiteralExprSyntax] + leftExpr.asInstanceOf[IntegerLiteralExprSyntax].literal match { + case integerLiteral(json) => json("tokenKind").str shouldBe """integerLiteral("2")""" + case other => fail("Should have a integerLiteral here but got: " + other) + } + op shouldBe a[BinaryOperatorExprSyntax] + op.asInstanceOf[BinaryOperatorExprSyntax].operator match { + case binaryOperator(json) => json("tokenKind").str shouldBe """binaryOperator("*")""" + case other => fail("Should have a binaryOperator here but got: " + other) + } + rightExpr shouldBe a[IntegerLiteralExprSyntax] + rightExpr.asInstanceOf[IntegerLiteralExprSyntax].literal match { + case integerLiteral(json) => json("tokenKind").str shouldBe """integerLiteral("3")""" + case other => fail("Should have a integerLiteral here but got: " + other) + } + case other => fail("Should have a InfixOperatorExprSyntax here but got: " + other) + } + case other => fail("Should have a InfixOperatorExprSyntax here but got: " + other) + } + + projectUnderTest.delete(swallowIOExceptions = true) } }