Skip to content

Commit 14b461c

Browse files
Merge pull request #15 from samtkit/samt-wrapper-cli
2 parents cfc6564 + d73bf13 commit 14b461c

File tree

20 files changed

+456
-154
lines changed

20 files changed

+456
-154
lines changed

README.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Get started with SAMT, learn fundamental concepts or extend SAMT with a custom g
2222

2323
- [Getting Started](https://github.com/samtkit/core/wiki/Getting-Started)
2424
- [Modeling Concepts](https://github.com/samtkit/core/wiki/Modeling-Concepts)
25+
- [SAMT Template](https://github.com/samtkit/template)
2526
- [Visual Studio Code Plugin](https://marketplace.visualstudio.com/items?itemName=samt.samt)
2627

2728
### Advanced
@@ -31,31 +32,24 @@ Get started with SAMT, learn fundamental concepts or extend SAMT with a custom g
3132

3233
## Development Setup
3334

34-
SAMT is written in [Kotlin](https://kotlinlang.org/) and uses [Gradle](https://gradle.org/) as a build tool, for the best developer experience we recommend using [IntelliJ](https://www.jetbrains.com/idea/).
35+
SAMT is written in [Kotlin](https://kotlinlang.org/) and uses [Gradle](https://gradle.org/) as a build tool,
36+
for the best developer experience we recommend using [IntelliJ IDEA](https://www.jetbrains.com/idea/).
3537

3638
If you want to start SAMT locally, simply clone the repository and compile it using Gradle:
3739

3840
```shell
3941
./gradlew assemble
4042
```
4143

42-
You can also compile the CLI module locally:
44+
And then use this locally compiled SAMT to compile your SAMT files:
4345

4446
```shell
45-
./gradlew :cli:shadowJar
47+
java -jar ./cli/build/libs/samt-cli.jar compile ./specification/examples/todo-service/*.samt
4648
```
4749

48-
And then compile SAMT files using this locally compiled version:
49-
50-
```shell
51-
java -jar ./cli/build/libs/samt-cli.jar ./specification/examples/todo-service/*.samt
52-
```
53-
54-
If you're more interested in the [SAMT Visual Studio Code plugin](https://github.com/samtkit/vscode) or the related language server, you can also compile it locally as well:
55-
56-
```shell
57-
./gradlew :language-server:shadowJar
58-
```
50+
If you are interested in learning about the functionality and operation of the [SAMT Visual Studio Code Plugin](https://github.com/samtkit/vscode)
51+
or methods for running and debugging the related language server on your local machine,
52+
related documentation is available in the [SAMT VS Code Wiki](https://github.com/samtkit/vscode/wiki).
5953

6054
## Contributing
6155

buildSrc/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ kotlin {
77
}
88

99
dependencies {
10-
implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.8.21")
10+
val kotlin = "1.8.21"
11+
implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:$kotlin")
12+
implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:$kotlin")
1113
}
1214

1315
repositories {

buildSrc/src/main/kotlin/samt-core.kotlin-conventions.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
kotlin("jvm")
3+
kotlin("plugin.serialization") apply false
34
}
45

56
apply(plugin = "kover")

cli/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ plugins {
22
application
33
id("samt-core.kotlin-conventions")
44
alias(libs.plugins.shadow)
5+
kotlin("plugin.serialization")
56
}
67

78
dependencies {
89
implementation(libs.jCommander)
910
implementation(libs.mordant)
11+
implementation(libs.kotlinx.serialization.json)
1012
implementation(project(":common"))
1113
implementation(project(":lexer"))
1214
implementation(project(":parser"))

cli/src/main/kotlin/tools/samt/cli/ASTPrinter.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.github.ajalt.mordant.rendering.TextStyles.bold
55
import com.github.ajalt.mordant.rendering.TextStyles.underline
66
import tools.samt.parser.*
77

8-
object ASTPrinter {
8+
internal object ASTPrinter {
99
fun dump(node: Node): String = buildString {
1010
val topLevelStyle = (red + bold + underline)
1111
val expressionStyle = (blue + bold)
@@ -53,8 +53,6 @@ object ASTPrinter {
5353
firstLine = false
5454
}
5555
}
56-
57-
appendLine()
5856
}
5957

6058
private fun dumpInfo(node: Node): String? = when (node) {
Lines changed: 25 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package tools.samt.cli
22

33
import com.beust.jcommander.JCommander
4-
54
import com.github.ajalt.mordant.terminal.Terminal
65
import tools.samt.common.DiagnosticController
7-
import tools.samt.common.SourceFile
8-
import tools.samt.semantic.SemanticModelBuilder
9-
import java.io.File
106

117
fun main(args: Array<String>) {
128
val cliArgs = CliArgs()
9+
val compileCommand = CompileCommand()
10+
val dumpCommand = DumpCommand()
11+
val wrapperCommand = WrapperCommand()
1312
val jCommander = JCommander.newBuilder()
1413
.addObject(cliArgs)
14+
.addCommand("compile", compileCommand)
15+
.addCommand("dump", dumpCommand)
16+
.addCommand("wrapper", wrapperCommand)
1517
.programName("./samtw")
1618
.build()
1719
jCommander.parse(*args)
@@ -20,77 +22,30 @@ fun main(args: Array<String>) {
2022
return
2123
}
2224

23-
val t = Terminal()
25+
val terminal = Terminal()
2426

2527
val workingDirectory = System.getProperty("user.dir")
26-
val filePaths = cliArgs.files
27-
28-
val startTimestamp = System.currentTimeMillis()
29-
val diagnosticController = DiagnosticController(workingDirectory).also { controller ->
30-
31-
// must specify at least one SAMT file
32-
if (filePaths.isEmpty()) {
33-
controller.reportGlobalError("No files specified")
34-
return@also
35-
}
36-
37-
// attempt to load each source file
38-
val sourceFiles = buildList {
39-
for (path in filePaths) {
40-
val file = File(path)
41-
42-
if (!file.exists()) {
43-
controller.reportGlobalError("File '$path' does not exist")
44-
continue
45-
}
46-
47-
if (!file.canRead()) {
48-
controller.reportGlobalError("File '$path' cannot be read, bad file permissions?")
49-
continue
50-
}
51-
52-
if (file.extension != "samt") {
53-
controller.reportGlobalError("File '$path' must end in .samt")
54-
continue
55-
}
56-
57-
val source = file.readText()
58-
add(SourceFile(file.canonicalPath, source))
59-
}
60-
}
61-
62-
// if any source files failed to load, exit
63-
if (controller.hasErrors()) {
64-
return@also
65-
}
66-
67-
// attempt to parse each source file into an AST
68-
val fileNodes = buildList {
69-
for (source in sourceFiles) {
70-
val context = controller.createContext(source)
71-
val fileNode = parseSourceFile(source, context)
72-
if (fileNode != null) {
73-
add(fileNode)
74-
}
75-
}
76-
}
7728

78-
// if any source files failed to parse, exit
79-
if (controller.hasErrors()) {
80-
return@also
81-
}
29+
val controller = DiagnosticController(workingDirectory)
8230

83-
// if the user requested the AST to be dumped, do so
84-
if (cliArgs.dumpAst) {
85-
fileNodes.forEach {
86-
t.print(ASTPrinter.dump(it))
87-
}
31+
val startTimestamp = System.currentTimeMillis()
32+
when (jCommander.parsedCommand) {
33+
"compile" -> compile(compileCommand, controller)
34+
"dump" -> dump(dumpCommand, terminal, controller)
35+
"wrapper" -> wrapper(wrapperCommand, terminal, controller)
36+
else -> {
37+
jCommander.usage()
38+
return
8839
}
89-
90-
// build up the semantic model from the AST
91-
SemanticModelBuilder.build(fileNodes, controller)
9240
}
93-
9441
val currentTimestamp = System.currentTimeMillis()
95-
t.println(DiagnosticFormatter.format(diagnosticController, startTimestamp, currentTimestamp, terminalWidth = t.info.width))
42+
43+
terminal.println(
44+
DiagnosticFormatter.format(
45+
controller,
46+
startTimestamp,
47+
currentTimestamp,
48+
terminalWidth = terminal.info.width
49+
)
50+
)
9651
}
Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
package tools.samt.cli
22

33
import com.beust.jcommander.Parameter
4+
import com.beust.jcommander.Parameters
45

56
class CliArgs {
67
@Parameter(names = ["-h", "--help"], description = "Display help", help = true)
78
var help: Boolean = false
9+
}
10+
11+
@Parameters(commandDescription = "Compile SAMT files")
12+
class CompileCommand {
13+
@Parameter(description = "Files to compile, defaults to all .samt files in the current directory")
14+
var files: List<String> = mutableListOf()
15+
}
16+
17+
@Parameters(commandDescription = "Dump SAMT files in various formats for debugging purposes")
18+
class DumpCommand {
19+
@Parameter(names = ["--tokens"], description = "Dump a visual representation of the token stream")
20+
var dumpTokens: Boolean = false
821

9-
@Parameter(names = ["--dump-ast"], description = "Dump a visual representation of the AST", help = true)
22+
@Parameter(names = ["--ast"], description = "Dump a visual representation of the AST")
1023
var dumpAst: Boolean = false
1124

12-
@Parameter(description = "Files")
25+
@Parameter(names = ["--types"], description = "Dump a visual representation of the resolved types")
26+
var dumpTypes: Boolean = false
27+
28+
@Parameter(description = "Files to dump, defaults to all .samt files in the current directory")
1329
var files: List<String> = mutableListOf()
1430
}
31+
32+
@Parameters(commandDescription = "Initialize or update the SAMT wrapper")
33+
class WrapperCommand {
34+
@Parameter(names = ["--version"], description = "The SAMT version to use, defaults to the latest version published on GitHub")
35+
var version: String = "latest"
36+
37+
@Parameter(names = ["--version-source"], description = "The location from where the latest version will be fetched from, defaults to the GitHub API. The result must be a JSON object with a 'tag_name' field containing the version string")
38+
var latestVersionSource: String = "https://api.github.com/repos/samtkit/core/releases/latest"
39+
40+
@Parameter(names = ["--init"], description = "Downloads all required files and initializes the SAMT wrapper")
41+
var init: Boolean = false
42+
43+
@Parameter(names = ["--init-source"], description = "The location from where the initial 'samtw', 'samtw.bat' and 'samt-wrapper.properties' will be downloaded from")
44+
var initSource: String = "https://raw.githubusercontent.com/samtkit/core/main/wrapper"
45+
}
Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
11
package tools.samt.cli
22

3-
import tools.samt.common.DiagnosticContext
3+
import tools.samt.common.DiagnosticController
44
import tools.samt.common.DiagnosticException
5-
import tools.samt.common.SourceFile
65
import tools.samt.lexer.Lexer
7-
import tools.samt.parser.FileNode
86
import tools.samt.parser.Parser
7+
import tools.samt.semantic.SemanticModelBuilder
98

10-
fun parseSourceFile(source: SourceFile, context: DiagnosticContext): FileNode? {
11-
val tokenStream = Lexer.scan(source.content.reader(), context)
9+
internal fun compile(command: CompileCommand, controller: DiagnosticController) {
10+
val sourceFiles = command.files.readSamtSourceFiles(controller)
1211

13-
if (context.hasErrors()) {
14-
return null
12+
if (controller.hasErrors()) {
13+
return
1514
}
1615

17-
val fileNode = try {
18-
Parser.parse(source, tokenStream, context)
19-
} catch (e: DiagnosticException) {
20-
// error message is added to the diagnostic console, so it can be ignored here
21-
return null
16+
// attempt to parse each source file into an AST
17+
val fileNodes = buildList {
18+
for (source in sourceFiles) {
19+
val context = controller.createContext(source)
20+
val tokenStream = Lexer.scan(source.content.reader(), context)
21+
22+
if (context.hasErrors()) {
23+
continue
24+
}
25+
26+
val fileNode = try {
27+
Parser.parse(source, tokenStream, context)
28+
} catch (e: DiagnosticException) {
29+
// error message is added to the diagnostic console, so it can be ignored here
30+
continue
31+
}
32+
33+
add(fileNode)
34+
}
2235
}
2336

24-
return fileNode
25-
}
37+
// if any source files failed to parse, exit
38+
if (controller.hasErrors()) {
39+
return
40+
}
41+
42+
// build up the semantic model from the AST
43+
SemanticModelBuilder.build(fileNodes, controller)
44+
45+
// Code Generators will be called here
46+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package tools.samt.cli
2+
3+
import com.github.ajalt.mordant.terminal.Terminal
4+
import tools.samt.common.DiagnosticController
5+
import tools.samt.common.DiagnosticException
6+
import tools.samt.lexer.Lexer
7+
import tools.samt.parser.Parser
8+
import tools.samt.semantic.SemanticModelBuilder
9+
10+
internal fun dump(command: DumpCommand, terminal: Terminal, controller: DiagnosticController) {
11+
val sourceFiles = command.files.readSamtSourceFiles(controller)
12+
13+
if (controller.hasErrors()) {
14+
return
15+
}
16+
17+
val dumpAll = !command.dumpTokens && !command.dumpAst && !command.dumpTypes
18+
19+
// attempt to parse each source file into an AST
20+
val fileNodes = buildList {
21+
for (source in sourceFiles) {
22+
val context = controller.createContext(source)
23+
24+
if (dumpAll || command.dumpTokens) {
25+
// create duplicate scan because sequence can only be iterated once
26+
val tokenStream = Lexer.scan(source.content.reader(), context)
27+
terminal.println("Tokens for ${source.absolutePath}:")
28+
terminal.println(TokenPrinter.dump(tokenStream))
29+
// clear the diagnostic messages so that messages aren't duplicated
30+
context.messages.clear()
31+
}
32+
33+
val tokenStream = Lexer.scan(source.content.reader(), context)
34+
35+
if (context.hasErrors()) {
36+
continue
37+
}
38+
39+
val fileNode = try {
40+
Parser.parse(source, tokenStream, context)
41+
} catch (e: DiagnosticException) {
42+
// error message is added to the diagnostic console, so it can be ignored here
43+
continue
44+
}
45+
46+
if (dumpAll || command.dumpAst) {
47+
terminal.println(ASTPrinter.dump(fileNode))
48+
}
49+
50+
if (context.hasErrors()) {
51+
continue
52+
}
53+
54+
add(fileNode)
55+
}
56+
}
57+
58+
// if any source files failed to parse, exit
59+
if (controller.hasErrors()) {
60+
return
61+
}
62+
63+
// build up the semantic model from the AST
64+
SemanticModelBuilder.build(fileNodes, controller)
65+
66+
if (dumpAll || command.dumpTypes) {
67+
terminal.println("Types:")
68+
terminal.println("Not yet implemented")
69+
// Type dumper will be added here
70+
}
71+
}

0 commit comments

Comments
 (0)