Skip to content

Commit

Permalink
feat(thrift): add Thrift grammar, analyser, and build setup
Browse files Browse the repository at this point in the history
This commit introduces a Thrift grammar definition, a Thrift parser listener for extracting code information, a Thrift analysis class, and the necessary Gradle build files to support the Thrift AST functionality within the project.
  • Loading branch information
phodal committed Oct 27, 2024
1 parent 7b69450 commit 4d63a51
Show file tree
Hide file tree
Showing 5 changed files with 385 additions and 0 deletions.
60 changes: 60 additions & 0 deletions chapi-ast-thrift/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
plugins {
id("antlr")
java
kotlin("jvm")
kotlin("plugin.serialization") version "1.6.10"

`jacoco-conventions`
}

repositories {
mavenCentral()
mavenLocal()
}

dependencies {
antlr("org.antlr:antlr4:4.13.1")

// project deps
implementation(project(":chapi-domain"))

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")

implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
// Kotlin reflection.
testImplementation(kotlin("test"))

// JUnit 5
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0")
testRuntimeOnly("org.junit.platform:junit-platform-console:1.6.0")

implementation("org.antlr:antlr4:4.13.1")
implementation("org.antlr:antlr4-runtime:4.13.1")
}

sourceSets.main {
java.srcDirs("${project.buildDir}/generated-src")
}

tasks.generateGrammarSource {
maxHeapSize = "64m"
arguments = arguments + listOf("-package", "chapi.ast.antlr") + listOf("-visitor", "-long-messages")
outputDirectory = file("${project.buildDir}/generated-src/chapi/ast/antlr")
}

tasks.withType<AntlrTask> {

}

tasks.named("compileKotlin") {
dependsOn(tasks.withType<AntlrTask>())
}

tasks.withType<Test> {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
279 changes: 279 additions & 0 deletions chapi-ast-thrift/src/main/antlr/Thrift.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false
// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging

grammar Thrift;

document
: header* definition* EOF
;

header
: include_
| namespace_
| cpp_include
;

include_
: 'include' LITERAL
;

namespace_
: 'namespace' '*' (IDENTIFIER | LITERAL)
| 'namespace' IDENTIFIER (IDENTIFIER | LITERAL) type_annotations?
| 'cpp_namespace' IDENTIFIER
| 'php_namespace' IDENTIFIER
;

cpp_include
: 'cpp_include' LITERAL
;

definition
: const_rule
| typedef_
| enum_rule
| senum
| struct_
| union_
| exception
| service
;

const_rule
: 'const' field_type IDENTIFIER ('=' const_value)? list_separator?
;

typedef_
: 'typedef' field_type IDENTIFIER type_annotations?
;

enum_rule
: 'enum' IDENTIFIER '{' enum_field* '}' type_annotations?
;

enum_field
: IDENTIFIER ('=' integer)? type_annotations? list_separator?
;

senum
: 'senum' IDENTIFIER '{' (LITERAL list_separator?)* '}' type_annotations?
;

struct_
: 'struct' IDENTIFIER '{' field* '}' type_annotations?
;

union_
: 'union' IDENTIFIER '{' field* '}' type_annotations?
;

exception
: 'exception' IDENTIFIER '{' field* '}' type_annotations?
;

service
: 'service' IDENTIFIER ('extends' IDENTIFIER)? '{' function_* '}' type_annotations?
;

field
: field_id? field_req? field_type IDENTIFIER ('=' const_value)? type_annotations? list_separator?
;

field_id
: integer ':'
;

field_req
: 'required'
| 'optional'
;

function_
: oneway? function_type IDENTIFIER '(' field* ')' throws_list? type_annotations? list_separator?
;

oneway
: ('oneway' | 'async')
;

function_type
: field_type
| 'void'
;

throws_list
: 'throws' '(' field* ')'
;

type_annotations
: '(' type_annotation* ')'
;

type_annotation
: IDENTIFIER ('=' annotation_value)? list_separator?
;

annotation_value
: integer
| LITERAL
;

field_type
: base_type
| IDENTIFIER
| container_type
;

base_type
: real_base_type type_annotations?
;

container_type
: (map_type | set_type | list_type) type_annotations?
;

map_type
: 'map' cpp_type? '<' field_type COMMA field_type '>'
;

set_type
: 'set' cpp_type? '<' field_type '>'
;

list_type
: 'list' '<' field_type '>' cpp_type?
;

cpp_type
: 'cpp_type' LITERAL
;

const_value
: integer
| DOUBLE
| LITERAL
| IDENTIFIER
| const_list
| const_map
;

integer
: INTEGER
| HEX_INTEGER
;

INTEGER
: ('+' | '-')? DIGIT+
;

HEX_INTEGER
: '-'? '0x' HEX_DIGIT+
;

DOUBLE
: ('+' | '-')? (DIGIT+ ('.' DIGIT+)? | '.' DIGIT+) (('E' | 'e') INTEGER)?
;

const_list
: '[' (const_value list_separator?)* ']'
;

const_map_entry
: const_value ':' const_value list_separator?
;

const_map
: '{' const_map_entry* '}'
;

list_separator
: COMMA
| ';'
;

real_base_type
: TYPE_BOOL
| TYPE_BYTE
| TYPE_I16
| TYPE_I32
| TYPE_I64
| TYPE_DOUBLE
| TYPE_STRING
| TYPE_BINARY
;

TYPE_BOOL
: 'bool'
;

TYPE_BYTE
: 'byte'
;

TYPE_I16
: 'i16'
;

TYPE_I32
: 'i32'
;

TYPE_I64
: 'i64'
;

TYPE_DOUBLE
: 'double'
;

TYPE_STRING
: 'string'
;

TYPE_BINARY
: 'binary'
;

LITERAL
: '"' (ESC_SEQ | ~[\\"])* '"'
| '\'' ( ESC_SEQ | ~[\\'])* '\''
;

fragment ESC_SEQ
: '\\' [rnt"'\\]
;
IDENTIFIER
: (LETTER | '_') (LETTER | DIGIT | '.' | '_')*
;
COMMA
: ','
;
fragment LETTER
: 'A' ..'Z'
| 'a' ..'z'
;
fragment DIGIT
: '0' ..'9'
;
fragment HEX_DIGIT
: DIGIT
| 'A' ..'F'
| 'a' ..'f'
;
WS
: (' ' | '\t' | '\r' '\n' | '\n')+ -> channel(HIDDEN)
;
SL_COMMENT
: ('//' | '#') (~'\n')* ('\r')? '\n' -> channel(HIDDEN)
;
ML_COMMENT
: '/*' .*? '*/' -> channel(HIDDEN)
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package chapi.ast.thrift

import chapi.ast.antlr.ThriftLexer
import chapi.ast.antlr.ThriftParser
import chapi.domain.core.CodeContainer
import chapi.parser.Analyser
import org.antlr.v4.runtime.*
import org.antlr.v4.runtime.tree.ParseTreeWalker

class ThriftAnalyser : Analyser {
override fun analysis(code: String, filePath: String): CodeContainer {
val context = this.parse(code).document()
val listener = ThriftFullIdentListener(fileName = filePath)
ParseTreeWalker().walk(listener, context)

return listener.getNodeInfo()
}

private fun parse(str: String): ThriftParser {
val fromString = CharStreams.fromString(str)
val lexer = ThriftLexer(fromString)
val tokenStream = CommonTokenStream(lexer)
val parser = ThriftParser(tokenStream)
return parser
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package chapi.ast.thrift

import chapi.ast.antlr.ThriftBaseListener
import chapi.ast.antlr.ThriftParser
import chapi.domain.core.CodeContainer

class ThriftFullIdentListener(fileName: String) : ThriftBaseListener() {
private var codeContainer: CodeContainer = CodeContainer(FullName = fileName)

override fun enterNamespace_(ctx: ThriftParser.Namespace_Context?) {
val prefix = ctx!!.children[0].text
val namespace = ctx.text.removePrefix(prefix).trim()
codeContainer.PackageName = namespace
}

fun getNodeInfo(): CodeContainer {
return codeContainer
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ include(

// tier 1 model language
":chapi-ast-protobuf",
":chapi-ast-thrift",

// tier 2 languages
":chapi-ast-kotlin",
Expand Down

0 comments on commit 4d63a51

Please sign in to comment.