Skip to content
Open
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ Wirespec can read and convert OpenApiSpecification (OAS) files.

## Syntax

Wirespec knows four definitions: `refined`, `enum`, `type`, `endpoint`.
Wirespec knows four definitions: `import`, `refined`, `enum`, `type`, `endpoint`.

### Import

```wirespec
import { DEFINITION, DEFINITION, ... } "FILE"
```
Imports only work within the same folder

### Refined

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package community.flock.wirespec.compiler.core
import arrow.core.Either
import arrow.core.Nel
import arrow.core.NonEmptyList
import arrow.core.flatMap
import community.flock.wirespec.compiler.core.Stage.EMITTED
import community.flock.wirespec.compiler.core.Stage.PARSED
import community.flock.wirespec.compiler.core.Stage.TOKENIZED
Expand Down Expand Up @@ -32,7 +33,7 @@ fun ParseContext.parse(source: String): Either<NonEmptyList<WirespecException>,
.also(TOKENIZED::log)
.let(Parser(logger)::parse)
.also(PARSED::log)
.map { it.validate() }
.flatMap { it.validate() }
.also(VALIDATED::log)

private enum class Stage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import community.flock.wirespec.compiler.core.tokenize.FieldIdentifier
import community.flock.wirespec.compiler.core.tokenize.ForwardSlash
import community.flock.wirespec.compiler.core.tokenize.Hash
import community.flock.wirespec.compiler.core.tokenize.KebabCaseIdentifier
import community.flock.wirespec.compiler.core.tokenize.ImportDefinition
import community.flock.wirespec.compiler.core.tokenize.LeftCurly
import community.flock.wirespec.compiler.core.tokenize.Literal
import community.flock.wirespec.compiler.core.tokenize.Method
import community.flock.wirespec.compiler.core.tokenize.NewLine
import community.flock.wirespec.compiler.core.tokenize.PascalCaseIdentifier
Expand Down Expand Up @@ -57,12 +59,14 @@ object WirespecSpec : LanguageSpec {
override val typeIdentifier = WirespecType
override val fieldIdentifier = WirespecField
override val orderedMatchers = listOf(
Regex("^\\bimport\\b") to ImportDefinition,
Regex("^\\btype\\b") to TypeDefinition,
Regex("^\\benum\\b") to EnumTypeDefinition,
Regex("^\\bendpoint\\b") to EndpointDefinition,
Regex("^\\bchannel\\b") to ChannelDefinition,
Regex("^[^\\S\\r\\n]+") to WhiteSpaceExceptNewLine,
Regex("^[\\r\\n]") to NewLine,
Regex("^\".*\"") to Literal,
Regex("^\\{") to LeftCurly,
Regex("^\\}") to RightCurly,
Regex("^->") to Arrow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import community.flock.wirespec.compiler.core.parse.Enum
import community.flock.wirespec.compiler.core.parse.Field
import community.flock.wirespec.compiler.core.parse.FieldIdentifier
import community.flock.wirespec.compiler.core.parse.Identifier
import community.flock.wirespec.compiler.core.parse.Import
import community.flock.wirespec.compiler.core.parse.Reference
import community.flock.wirespec.compiler.core.parse.Refined
import community.flock.wirespec.compiler.core.parse.Type
Expand All @@ -33,13 +34,10 @@ abstract class Emitter(
open fun Definition.emitName(): String = notYetImplemented()

open fun emit(ast: AST): List<Emitted> = ast
.filterIsInstance<Definition>()
.map {
logger.info(
"Emitting Node ${
when (it) {
is Definition -> it.emitName()
}
}"
"Emitting Node ${it.emitName()}"
)
when (it) {
is Type -> Emitted(it.emitName(), emit(it, ast))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ sealed class WirespecException(message: String, val coordinates: Token.Coordinat
"Cannot find reference: $referenceName",
)

class EmptyImportReferenceException(token: Token) :
ParserException(
token.coordinates,
"List of imports cannot be empty",
)

class RelativeImportException(token: Token) :
ParserException(
token.coordinates,
"Can only import relative paths in the same directory as the source file",
)

sealed class NullTokenException(message: String, coordinates: Token.Coordinates) : ParserException(coordinates, "$message cannot be null") {
class NextException(coordinates: Token.Coordinates) : NullTokenException("Next Token", coordinates)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package community.flock.wirespec.compiler.core.parse

import arrow.core.Either
import arrow.core.raise.either
import arrow.core.toNonEmptyListOrNull
import community.flock.wirespec.compiler.core.WsCustomType
import community.flock.wirespec.compiler.core.exceptions.WirespecException
import community.flock.wirespec.compiler.core.exceptions.WirespecException.CompilerException.ParserException
import community.flock.wirespec.compiler.core.exceptions.WirespecException.CompilerException.ParserException.WrongTokenException
import community.flock.wirespec.compiler.core.tokenize.Comma
import community.flock.wirespec.compiler.core.tokenize.LeftCurly
import community.flock.wirespec.compiler.core.tokenize.Literal
import community.flock.wirespec.compiler.core.tokenize.RightCurly
import community.flock.wirespec.compiler.core.tokenize.WirespecType
import community.flock.wirespec.compiler.utils.Logger

class ImportParser(logger: Logger) : AbstractParser(logger) {

fun TokenProvider.parseImport(): Either<WirespecException, Import> = either {
eatToken().bind()
token.log()
val references = when (token.type) {
is LeftCurly -> parseReferences(listOf()).bind()
else -> raise(WrongTokenException<WirespecType>(token).also { eatToken().bind() })
}
val url = when (token.type) {
is Literal -> token.value.drop(1).dropLast(1).also { eatToken().bind() }
else -> raise(WrongTokenException<WirespecType>(token).also { eatToken().bind() })
}
when {
!Regex("^[\\w,\\s-]+\\.ws\$").matches(url) -> raise(ParserException.RelativeImportException(token))
else -> Import(
url = Import.Url(url),
references = references.toNonEmptyListOrNull() ?: raise(ParserException.EmptyImportReferenceException(token)),
)
}
}

private fun TokenProvider.parseReferences(list: List<Reference.Custom>): Either<WirespecException, List<Reference.Custom>> = either {
eatToken().bind()
token.log()
when (token.type) {
is WsCustomType -> parseReferences(list + Reference.Custom(token.value, false)).bind()
is Comma -> parseReferences(list).bind()
is RightCurly -> list.also { eatToken().bind() }
else -> raise(WrongTokenException<WirespecType>(token).also { eatToken().bind() })
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package community.flock.wirespec.compiler.core.parse

import arrow.core.NonEmptyList
import community.flock.wirespec.compiler.core.Value
import community.flock.wirespec.compiler.core.parse.Reference.Primitive.Type.Precision.P64
import community.flock.wirespec.compiler.core.removeBackticks
Expand All @@ -13,6 +14,14 @@ sealed interface Definition : Node {
val identifier: Identifier
}

data class Import(
val references: NonEmptyList<Reference.Custom>,
val url: Url,
) : Node {
@JvmInline
value class Url(override val value: String) : Value<String>
}

data class Type(
override val comment: Comment?,
override val identifier: DefinitionIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import community.flock.wirespec.compiler.core.tokenize.ChannelDefinition
import community.flock.wirespec.compiler.core.tokenize.Comment
import community.flock.wirespec.compiler.core.tokenize.EndpointDefinition
import community.flock.wirespec.compiler.core.tokenize.EnumTypeDefinition
import community.flock.wirespec.compiler.core.tokenize.ImportDefinition
import community.flock.wirespec.compiler.core.tokenize.Precision
import community.flock.wirespec.compiler.core.tokenize.Token
import community.flock.wirespec.compiler.core.tokenize.Tokens
Expand All @@ -26,6 +27,7 @@ abstract class AbstractParser(protected val logger: Logger) {

class Parser(logger: Logger) : AbstractParser(logger) {

private val importParser = ImportParser(logger)
private val typeParser = TypeParser(logger)
private val enumParser = EnumParser(logger)
private val endpointParser = EndpointParser(logger)
Expand All @@ -36,7 +38,7 @@ class Parser(logger: Logger) : AbstractParser(logger) {
.parse()

private fun TokenProvider.parse(): EitherNel<WirespecException, AST> = either {
mutableListOf<EitherNel<WirespecException, Definition>>()
mutableListOf<EitherNel<WirespecException, Node>>()
.apply { while (hasNext()) add(parseDefinition().mapLeft { it.nel() }) }
.map { it.bind() }
}
Expand All @@ -49,6 +51,7 @@ class Parser(logger: Logger) : AbstractParser(logger) {
}
when (token.type) {
is WirespecDefinition -> when (token.type as WirespecDefinition) {
is ImportDefinition -> with(importParser) { parseImport() }.bind()
is TypeDefinition -> with(typeParser) { parseType(comment) }.bind()
is EnumTypeDefinition -> with(enumParser) { parseEnum(comment) }.bind()
is EndpointDefinition -> with(endpointParser) { parseEndpoint(comment) }.bind()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package community.flock.wirespec.compiler.core.parse

import arrow.core.Either
import arrow.core.raise.either
import community.flock.wirespec.compiler.core.WsCustomType
import community.flock.wirespec.compiler.core.exceptions.WirespecException
import community.flock.wirespec.compiler.core.exceptions.WirespecException.CompilerException.ParserException
import community.flock.wirespec.compiler.core.exceptions.WirespecException.CompilerException.ParserException.NullTokenException.NextException
import community.flock.wirespec.compiler.core.tokenize.ImportDefinition
import community.flock.wirespec.compiler.core.tokenize.Keyword
import community.flock.wirespec.compiler.core.tokenize.Token
import community.flock.wirespec.compiler.core.tokenize.Tokens
import community.flock.wirespec.compiler.core.tokenize.WirespecDefinition
import community.flock.wirespec.compiler.core.tokenize.TypeDefinition
import community.flock.wirespec.compiler.utils.Logger

class TokenProvider(private val logger: Logger, tokens: Tokens) {
Expand All @@ -17,14 +20,21 @@ class TokenProvider(private val logger: Logger, tokens: Tokens) {
private val tokenIterator = tokens.tail.iterator()
private var nextToken = nextToken()

private val definitionNames: List<String> = tokens
.zipWithNext()
.mapNotNull { (first, second) ->
when (first.type) {
is WirespecDefinition -> second.value
else -> null
private val definitionNames = tokens
.fold(emptyList<MutableList<Token>>()) { acc, t ->
when {
t.type is Keyword -> acc + listOf(mutableListOf(t))
else -> acc.apply { lastOrNull()?.add(t) }
}
}
.flatMap {
when (it[0].type) {
is ImportDefinition -> it.filter { t -> t.type is WsCustomType }
is TypeDefinition -> listOf(it[1])
else -> emptyList()
}
}
.map { it.value }

fun Token.shouldBeDefined(): Either<WirespecException, Unit> = either {
if (value !in definitionNames) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package community.flock.wirespec.compiler.core.tokenize
fun TokenType.name(): String = this::class.simpleName!!

sealed interface TokenType
data object Literal : TokenType
data object RightCurly : TokenType
data object Colon : TokenType
data object Comma : TokenType
Expand Down Expand Up @@ -42,6 +43,7 @@ data object StartOfProgram : WhiteSpace

sealed interface Keyword : TokenType
sealed interface WirespecDefinition : Keyword
data object ImportDefinition : WirespecDefinition
data object TypeDefinition : WirespecDefinition
data object EnumTypeDefinition : WirespecDefinition
data object ChannelDefinition : WirespecDefinition
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
package community.flock.wirespec.compiler.core.validate

import arrow.core.Either
import arrow.core.Nel
import arrow.core.raise.either
import community.flock.wirespec.compiler.core.exceptions.WirespecException
import community.flock.wirespec.compiler.core.parse.AST
import community.flock.wirespec.compiler.core.parse.Channel
import community.flock.wirespec.compiler.core.parse.Endpoint
import community.flock.wirespec.compiler.core.parse.Enum
import community.flock.wirespec.compiler.core.parse.Import
import community.flock.wirespec.compiler.core.parse.Reference
import community.flock.wirespec.compiler.core.parse.Refined
import community.flock.wirespec.compiler.core.parse.Type
import community.flock.wirespec.compiler.core.parse.Union

fun AST.validate(): AST = map { node ->
when (node) {
is Channel -> node
is Endpoint -> node
is Enum -> node
is Refined -> node
is Type -> node.copy(
extends = filterIsInstance<Union>()
.filter { union ->
union.entries
.map {
when (it) {
is Reference.Custom -> it.value
else -> error("Any Unit of Primitive cannot be part of Union")
fun AST.validate(): Either<Nel<WirespecException>, AST> = either {
map { node ->
when (node) {
is Channel -> node
is Endpoint -> node
is Enum -> node
is Refined -> node
is Type -> node.copy(
extends = filterIsInstance<Union>()
.filter { union ->
union.entries
.map {
when (it) {
is Reference.Custom -> it.value
else -> error("Any Unit of Primitive cannot be part of Union")
}
}
}
.contains(node.identifier.value)
}
.map {
Reference.Custom(
value = it.identifier.value,
isNullable = false,
)
},
)
.contains(node.identifier.value)
}
.map {
Reference.Custom(
value = it.identifier.value,
isNullable = false,
)
},
)

is Union -> node
is Union -> node
is Import -> node
}
}
}
Loading