Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions cli/src/main/kotlin/tools/samt/cli/CliDumper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,9 @@ internal fun dump(command: DumpCommand, terminal: Terminal, controller: Diagnost
}

// build up the semantic model from the AST
SemanticModelBuilder.build(fileNodes, controller)
val samtPackage = SemanticModelBuilder.build(fileNodes, controller)

if (dumpAll || command.dumpTypes) {
terminal.println("Types:")
terminal.println("Not yet implemented")
// Type dumper will be added here
terminal.println(TypePrinter.dump(samtPackage))
}
}
56 changes: 56 additions & 0 deletions cli/src/main/kotlin/tools/samt/cli/TypePrinter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package tools.samt.cli

import com.github.ajalt.mordant.rendering.TextColors.*
import com.github.ajalt.mordant.rendering.TextStyles.bold
import tools.samt.semantic.Package

internal object TypePrinter {
fun dump(samtPackage: Package): String = buildString {
appendLine(blue(samtPackage.name.ifEmpty { "<root>" }))
for (enum in samtPackage.enums) {
appendLine(" ${bold("enum")} ${yellow(enum.name)}")
}
for (record in samtPackage.records) {
appendLine(" ${bold("record")} ${yellow(record.name)}")
}
for (alias in samtPackage.aliases) {
appendLine(" ${bold("alias")} ${yellow(alias.name)} = ${gray(alias.fullyResolvedType?.humanReadableName ?: "Unknown")}")
}
for (service in samtPackage.services) {
appendLine(" ${bold("service")} ${yellow(service.name)}")
}
for (provider in samtPackage.providers) {
appendLine(" ${bold("provider")} ${yellow(provider.name)}")
}
for (consumer in samtPackage.consumers) {
appendLine(" ${bold("consumer")} for ${yellow(consumer.provider.humanReadableName)}")
}

val childDumps: List<String> = samtPackage.subPackages.map { dump(it) }

childDumps.forEachIndexed { childIndex, child ->
var firstLine = true
child.lineSequence().forEach { line ->
if (line.isNotEmpty()) {
if (childIndex != childDumps.lastIndex) {
if (firstLine) {
append("${white("├─")}$line")
} else {
append("${white("│ ")}$line")
}
} else {
if (firstLine) {
append("${white("└─")}$line")
} else {
append(" $line")
}
}

appendLine()
}

firstLine = false
}
}
}
}
54 changes: 41 additions & 13 deletions cli/src/test/kotlin/tools/samt/cli/ASTPrinterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@ class ASTPrinterTest {

enum E { A, B, C }

alias B : E

@Description("This is a service")
service MyService {
testmethod(foo: A): B
}

provide MyEndpoint {
implements MyService
transport HTTP
}

consume MyEndpoint {
uses MyService
}
""".trimIndent())

val dump = ASTPrinter.dump(fileNode)
Expand Down Expand Up @@ -80,19 +91,36 @@ class ASTPrinterTest {
│ ├─IdentifierNode A <11:10>
│ ├─IdentifierNode B <11:13>
│ └─IdentifierNode C <11:16>
└─ServiceDeclarationNode <14:1>
├─IdentifierNode MyService <14:9>
├─RequestResponseOperationNode <15:3>
│ ├─IdentifierNode testmethod <15:3>
│ ├─OperationParameterNode <15:14>
│ │ ├─IdentifierNode foo <15:14>
│ │ └─BundleIdentifierNode A <15:19>
│ │ └─IdentifierNode A <15:19>
│ └─BundleIdentifierNode B <15:23>
│ └─IdentifierNode B <15:23>
└─AnnotationNode <13:1>
├─IdentifierNode Description <13:2>
└─StringNode "This is a service" <13:14>
├─TypeAliasNode <13:1>
│ ├─IdentifierNode B <13:7>
│ └─BundleIdentifierNode E <13:11>
│ └─IdentifierNode E <13:11>
├─ServiceDeclarationNode <16:1>
│ ├─IdentifierNode MyService <16:9>
│ ├─RequestResponseOperationNode <17:3>
│ │ ├─IdentifierNode testmethod <17:3>
│ │ ├─OperationParameterNode <17:14>
│ │ │ ├─IdentifierNode foo <17:14>
│ │ │ └─BundleIdentifierNode A <17:19>
│ │ │ └─IdentifierNode A <17:19>
│ │ └─BundleIdentifierNode B <17:23>
│ │ └─IdentifierNode B <17:23>
│ └─AnnotationNode <15:1>
│ ├─IdentifierNode Description <15:2>
│ └─StringNode "This is a service" <15:14>
├─ProviderDeclarationNode <20:1>
│ ├─IdentifierNode MyEndpoint <20:9>
│ ├─ProviderImplementsNode <21:3>
│ │ └─BundleIdentifierNode MyService <21:14>
│ │ └─IdentifierNode MyService <21:14>
│ └─ProviderTransportNode <22:3>
│ └─IdentifierNode HTTP <22:13>
└─ConsumerDeclarationNode <25:1>
├─BundleIdentifierNode MyEndpoint <25:9>
│ └─IdentifierNode MyEndpoint <25:9>
└─ConsumerUsesNode <26:3>
└─BundleIdentifierNode MyService <26:8>
└─IdentifierNode MyService <26:8>
""".trimIndent().trim(), dumpWithoutColorCodes.trimIndent().trim())
}

Expand Down
79 changes: 79 additions & 0 deletions cli/src/test/kotlin/tools/samt/cli/TypePrinterTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package tools.samt.cli

import tools.samt.common.DiagnosticController
import tools.samt.common.SourceFile
import tools.samt.lexer.Lexer
import tools.samt.parser.FileNode
import tools.samt.parser.Parser
import tools.samt.semantic.SemanticModelBuilder
import java.net.URI
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse

class TypePrinterTest {
@Test
fun `correctly formats an AST dump`() {
val stuffPackage = parse("""
package test.stuff

record A {
name: String (10..20, pattern("hehe"))
age: Int (0..150)
}

enum E { A, B, C }

@Description("This is a service")
service MyService {
testmethod(foo: A): E
}

provide MyEndpoint {
implements MyService
transport HTTP
}
""".trimIndent())
val consumerPackage = parse("""
package test.other.company

record Empty

consume test.stuff.MyEndpoint {
uses test.stuff.MyService
}
""".trimIndent())

val controller = DiagnosticController(URI("file:///tmp"))
val samtPackage = SemanticModelBuilder.build(listOf(stuffPackage, consumerPackage), controller)
assertFalse(controller.hasErrors())

val dump = TypePrinter.dump(samtPackage)
val dumpWithoutColorCodes = dump.replace(Regex("\u001B\\[[;\\d]*m"), "")

assertEquals("""
<root>
└─test
├─stuff
│ enum E
│ record A
│ service MyService
│ provider MyEndpoint
└─other
└─company
record Empty
consumer for MyEndpoint
""".trimIndent().trim(), dumpWithoutColorCodes.trimIndent().trim())
}

private fun parse(source: String): FileNode {
val filePath = URI("file:///tmp/ASTPrinterTest.samt")
val sourceFile = SourceFile(filePath, source)
val diagnosticController = DiagnosticController(URI("file:///tmp"))
val diagnosticContext = diagnosticController.getOrCreateContext(sourceFile)
val stream = Lexer.scan(source.reader(), diagnosticContext)
val fileTree = Parser.parse(sourceFile, stream, diagnosticContext)
assertFalse(diagnosticContext.hasErrors(), "Expected no errors, but had errors")
return fileTree
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class SamtDeclarationLookup private constructor() : SamtSemanticLookup<Location,
this[operation.declaration.name.location] = operation
}

override fun markTypeAliasDeclaration(aliasType: AliasType) {
super.markTypeAliasDeclaration(aliasType)
this[aliasType.declaration.name.location] = aliasType
}

companion object {
fun analyze(fileNode: FileNode, samtPackage: Package) =
SamtDeclarationLookup().also { it.analyze(fileNode, samtPackage) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ abstract class SamtSemanticLookup<TKey, TValue> protected constructor() {
markTypeReference(type.valueType)
}

is AliasType,
is ConsumerType,
is EnumType,
is ProviderType,
Expand Down Expand Up @@ -61,12 +62,17 @@ abstract class SamtSemanticLookup<TKey, TValue> protected constructor() {
is EnumDeclarationNode -> markEnumDeclaration(samtPackage.getTypeByNode(statement))
is RecordDeclarationNode -> markRecordDeclaration(samtPackage.getTypeByNode(statement))
is ServiceDeclarationNode -> markServiceDeclaration(samtPackage.getTypeByNode(statement))
is TypeAliasNode -> Unit
is TypeAliasNode -> markTypeAliasDeclaration(samtPackage.getTypeByNode(statement))
is PackageDeclarationNode -> markPackageDeclaration(statement)
is ImportNode -> markImport(statement,samtPackage.typeByNode[statement] ?: UnknownType)
}
}

protected open fun markTypeAliasDeclaration(aliasType: AliasType) {
markAnnotations(aliasType.declaration.annotations)
markTypeReference(aliasType.aliasedType)
}

protected open fun markServiceDeclaration(serviceType: ServiceType) {
markAnnotations(serviceType.declaration.annotations)
for (operation in serviceType.operations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SamtSemanticTokens private constructor() : SamtSemanticLookup<Location, Sa
Metadata(TokenType.type, TokenModifier.defaultLibrary)
}

is AliasType -> this[location] = Metadata(TokenType.type)
is ProviderType -> this[location] = Metadata(TokenType.type)
is RecordType -> this[location] = Metadata(TokenType.`class`)
is ServiceType -> this[location] = Metadata(TokenType.`interface`)
Expand Down
10 changes: 8 additions & 2 deletions semantic/src/main/kotlin/tools/samt/semantic/Package.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ class Package(val name: String) {

val records: MutableList<RecordType> = mutableListOf()

@Suppress("MemberVisibilityCanBePrivate")
val enums: MutableList<EnumType> = mutableListOf() // Will be read in the future
val enums: MutableList<EnumType> = mutableListOf()
val services: MutableList<ServiceType> = mutableListOf()
val providers: MutableList<ProviderType> = mutableListOf()
val consumers: MutableList<ConsumerType> = mutableListOf()
val aliases: MutableList<AliasType> = mutableListOf()

val typeByNode: MutableMap<Node, Type> = mutableMapOf()

Expand Down Expand Up @@ -73,6 +73,12 @@ class Package(val name: String) {
typeByNode[consumer.declaration] = consumer
}

operator fun plusAssign(alias: AliasType) {
aliases.add(alias)
types[alias.name] = alias
typeByNode[alias.declaration] = alias
}

operator fun contains(identifier: IdentifierNode): Boolean =
types.containsKey(identifier.name)

Expand Down
Loading