Feature: Array in NeoLang

This commit is contained in:
zt515 2017-08-08 16:04:22 +08:00
parent 4b9bf83181
commit 44947f05aa
24 changed files with 254 additions and 56 deletions

View File

@ -1,3 +1,13 @@
extra-key: {
program: { "vim", "vi" }
program: [ "vim", "vi" ]
key: [
{
display: "Ctrl"
code: "<CTRL>"
},
{
display: "Esc"
code: "<ESC>"
}
]
}

View File

@ -10,7 +10,10 @@ enum class NeoLangTokenType {
STRING,
BRACKET_START,
BRACKET_END,
ARRAY_START,
ARRAY_END,
COLON,
COMMA,
EOL,
EOF,
}

View File

@ -13,17 +13,24 @@ class NeoLangTokenValue(val value: NeoLangValue) {
companion object {
val COLON = NeoLangTokenValue(NeoLangValue(":"))
val BRACKET_START = NeoLangTokenValue(NeoLangValue("{"))
val BRACKET_END = NeoLangTokenValue(NeoLangValue("}"))
val COMMA = NeoLangTokenValue(NeoLangValue(","))
val QUOTE = NeoLangTokenValue(NeoLangValue("\""))
val EOF = NeoLangTokenValue(NeoLangValue("<EOF>"))
val BRACKET_START = NeoLangTokenValue(NeoLangValue("{"))
val BRACKET_END = NeoLangTokenValue(NeoLangValue("}"))
val ARRAY_START = NeoLangTokenValue(NeoLangValue("["))
val ARRAY_END = NeoLangTokenValue(NeoLangValue("]"))
fun wrap(tokenText: String): NeoLangTokenValue {
return when (tokenText) {
COLON.value.asString() -> COLON
COMMA.value.asString() -> COMMA
QUOTE.value.asString() -> QUOTE
BRACKET_START.value.asString() -> BRACKET_START
BRACKET_END.value.asString() -> BRACKET_END
QUOTE.value.asString() -> QUOTE
ARRAY_START.value.asString() -> ARRAY_START
ARRAY_END.value.asString() -> ARRAY_END
else -> NeoLangTokenValue(NeoLangValue(tokenText))
}
}

View File

@ -1,3 +0,0 @@
package io.neolang.ast.base
open class NeoLangAstBaseNode : NeoLangAst()

View File

@ -0,0 +1,3 @@
package io.neolang.ast.base
open class NeoLangBaseNode : NeoLangAst()

View File

@ -0,0 +1,9 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangBaseNode
import io.neolang.runtime.type.NeoLangArrayElement
/**
* @author kiva
*/
class NeoLangArrayNode(val arrayNameNode: NeoLangStringNode, val elements: Array<NeoLangArrayElement>) : NeoLangBaseNode()

View File

@ -1,11 +1,11 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.base.NeoLangBaseNode
/**
* @author kiva
*/
open class NeoLangAstBasedNode(val ast: NeoLangAstBaseNode) : NeoLangAstBaseNode() {
open class NeoLangAstBasedNode(val ast: NeoLangBaseNode) : NeoLangBaseNode() {
override fun toString(): String {
return "${javaClass.simpleName} { ast: $ast }"
}

View File

@ -1,11 +1,11 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.base.NeoLangBaseNode
/**
* @author kiva
*/
class NeoLangAttributeNode(val stringNode: NeoLangStringNode, val blockNode: NeoLangBlockNode) : NeoLangAstBaseNode() {
class NeoLangAttributeNode(val stringNode: NeoLangStringNode, val blockNode: NeoLangBlockNode) : NeoLangBaseNode() {
override fun toString(): String {
return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }"

View File

@ -1,11 +1,11 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.base.NeoLangBaseNode
/**
* @author kiva
*/
class NeoLangBlockNode(blockElement: NeoLangAstBaseNode) : NeoLangAstBasedNode(blockElement) {
class NeoLangBlockNode(blockElement: NeoLangBaseNode) : NeoLangAstBasedNode(blockElement) {
companion object {
fun emptyNode(): NeoLangBlockNode {
return NeoLangBlockNode(NeoLangDummyNode())

View File

@ -1,8 +1,8 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.base.NeoLangBaseNode
/**
* @author kiva
*/
class NeoLangDummyNode : NeoLangAstBaseNode()
class NeoLangDummyNode : NeoLangBaseNode()

View File

@ -1,11 +1,11 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.base.NeoLangBaseNode
/**
* @author kiva
*/
class NeoLangGroupNode(val attributes: List<NeoLangAttributeNode>) : NeoLangAstBaseNode() {
class NeoLangGroupNode(val attributes: Array<NeoLangAttributeNode>) : NeoLangBaseNode() {
override fun toString(): String {
return "NeoLangGroupNode { attrs: $attributes }"

View File

@ -1,12 +1,12 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.base.NeoLangBaseNode
/**
* @author kiva
*/
class NeoLangProgramNode(val groups: List<NeoLangGroupNode>) : NeoLangAstBaseNode() {
class NeoLangProgramNode(val groups: List<NeoLangGroupNode>) : NeoLangBaseNode() {
override fun toString(): String {
return "NeoLangProgramNode { groups: $groups }"

View File

@ -1,13 +1,13 @@
package io.neolang.ast.node
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.NeoLangToken
import io.neolang.ast.base.NeoLangBaseNode
import io.neolang.runtime.type.NeoLangValue
/**
* @author kiva
*/
open class NeoLangTokenBasedNode(val token: NeoLangToken) : NeoLangAstBaseNode() {
open class NeoLangTokenBasedNode(val token: NeoLangToken) : NeoLangBaseNode() {
override fun toString(): String {
return "${javaClass.simpleName} { token: $token }"
}

View File

@ -3,12 +3,13 @@ package io.neolang.ast.visitor
import io.neolang.ast.base.NeoLangAst
import io.neolang.ast.node.*
/**
* grammar: [
* program: group (group)*
* group: attribute (attribute)*
* attribute: ID COLON block
* block: STRING | NUMBER | (BRACKET_START group BRACKET_END)
* block: STRING | NUMBER | (BRACKET_START [group|] BRACKET_END) | (ARRAY_START [block(<,block>)+|] ARRAY_END)
* ]
*/
@ -32,22 +33,38 @@ internal object AstVisitorImpl {
visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback)
}
fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) {
val arrayName = ast.arrayNameNode.eval().asString()
visitorCallback.onEnterContext(arrayName)
ast.elements.forEach {
AstVisitorImpl.visitBlock(it.block, it.index.toString(), visitorCallback)
}
visitorCallback.onExitContext()
}
fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) {
val visitingNode = ast.ast
when (visitingNode) {
is NeoLangGroupNode -> {
// is a sub block, e.g.
// block: { sub-block: {} }
// block: { $blockName: {} }
// FIXME: Block in Array
visitorCallback.onEnterContext(blockName)
AstVisitorImpl.visitGroup(visitingNode, visitorCallback)
visitorCallback.onExitContext()
}
is NeoLangArrayNode -> {
// array: [ "a", "b", "c", 1, 2, 3 ]
AstVisitorImpl.visitArray(visitingNode, visitorCallback)
}
is NeoLangStringNode -> {
// block: { node: "hello" }
// block: { $blockName: "hello" }
visitorCallback.getCurrentContext().defineAttribute(blockName, visitingNode.eval())
}
is NeoLangNumberNode -> {
// block: { node: 123.456 }
// block: { $blockName: 123.456 }
visitorCallback.getCurrentContext().defineAttribute(blockName, visitingNode.eval())
}
}
@ -57,6 +74,7 @@ internal object AstVisitorImpl {
when (ast) {
is NeoLangProgramNode -> AstVisitorImpl.visitProgram(ast, visitorCallback)
is NeoLangGroupNode -> AstVisitorImpl.visitGroup(ast, visitorCallback)
is NeoLangArrayNode -> AstVisitorImpl.visitArray(ast, visitorCallback)
}
}
}

View File

@ -0,0 +1,24 @@
package io.neolang.ast.visitor
import io.neolang.runtime.context.NeoLangContext
/**
* @author kiva
*/
open class IVisitorCallbackAdapter : IVisitorCallback {
override fun onStart() {
}
override fun onFinish() {
}
override fun onEnterContext(contextName: String) {
}
override fun onExitContext() {
}
override fun getCurrentContext(): NeoLangContext {
throw RuntimeException("getCurrentContext() not supported in this IVisitorCallback!")
}
}

View File

@ -1,13 +1,13 @@
package io.neolang.main
import io.neolang.ast.visitor.IVisitorCallback
import io.neolang.ast.visitor.IVisitorCallbackAdapter
import io.neolang.runtime.context.NeoLangContext
import java.util.*
/**
* @author kiva
*/
class DisplayProcessVisitor : IVisitorCallback {
class DisplayProcessVisitor : IVisitorCallbackAdapter() {
private val contextStack = Stack<NeoLangContext>()
override fun onStart() {
@ -30,7 +30,10 @@ class DisplayProcessVisitor : IVisitorCallback {
override fun onExitContext() {
val context = contextStack.pop()
println(">>> Exiting Context ${context.contextName}")
println(">>> Exiting & Dumping Context ${context.contextName}")
context.getAttributes().entries.forEach {
println(" > [${it.key}]: ${it.value.asString()}")
}
}
override fun getCurrentContext(): NeoLangContext {

View File

@ -11,7 +11,7 @@ import java.util.*
* program: group (group)*
* group: attribute (attribute)*
* attribute: ID COLON block
* block: STRING | NUMBER | (BRACKET_START group BRACKET_END)
* block: STRING | NUMBER | (BRACKET_START [group|] BRACKET_END) | (ARRAY_START [block(<,block>)+|] ARRAY_END)
* ]
*/
@ -97,6 +97,18 @@ class NeoLangLexer {
moveToNextChar()
NeoLangToken(NeoLangTokenType.BRACKET_END, currentToken)
}
NeoLangTokenValue.ARRAY_START -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.ARRAY_START, currentToken)
}
NeoLangTokenValue.ARRAY_END -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.ARRAY_END, currentToken)
}
NeoLangTokenValue.COMMA -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.COMMA, currentToken)
}
NeoLangTokenValue.QUOTE -> {
NeoLangToken(NeoLangTokenType.STRING, NeoLangTokenValue.wrap(getNextTokenAsString()))
}
@ -106,7 +118,7 @@ class NeoLangLexer {
} else if (isIdentifier(currentChar, true)) {
NeoLangToken(NeoLangTokenType.ID, NeoLangTokenValue.wrap(getNextTokenAsId()))
} else {
throw InvalidTokenException("Unexpected character: " + currentChar)
throw InvalidTokenException("Unexpected character near line $lineNumber: $currentChar")
}
}
}

View File

@ -5,6 +5,7 @@ import io.neolang.ast.NeoLangTokenType
import io.neolang.ast.NeoLangTokenValue
import io.neolang.ast.base.NeoLangAst
import io.neolang.ast.node.*
import io.neolang.runtime.type.NeoLangArrayElement
/**
* @author kiva
@ -84,14 +85,15 @@ class NeoLangParser {
val attributes = mutableListOf(attr)
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.BRACKET_END) {
&& token.tokenType !== NeoLangTokenType.BRACKET_END
&& token.tokenType !== NeoLangTokenType.ARRAY_END) {
attr = attribute()
if (attr == null) {
break
}
attributes.add(attr)
}
return NeoLangGroupNode(attributes)
return NeoLangGroupNode(attributes.toTypedArray())
}
return null
@ -101,15 +103,79 @@ class NeoLangParser {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
if (match(NeoLangTokenType.ID)) {
match(NeoLangTokenType.COLON, errorThrow = true)
val block = block() ?: NeoLangBlockNode.emptyNode()
return NeoLangAttributeNode(NeoLangStringNode(token), block)
val attrName = NeoLangStringNode(token)
val block = block(attrName) ?: NeoLangBlockNode.emptyNode()
return NeoLangAttributeNode(attrName, block)
}
return null
}
private fun block(): NeoLangBlockNode? {
private fun array(arrayName: NeoLangStringNode): NeoLangArrayNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
// TODO: Multiple Array
var block = blockNonArrayElement(arrayName)
var index = 0
if (block != null) {
val elements = mutableListOf(NeoLangArrayElement(index++, block))
if (match(NeoLangTokenType.COMMA)) {
// More than one elements
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.ARRAY_END) {
block = blockNonArrayElement(arrayName)
if (block == null) {
break
}
elements.add(NeoLangArrayElement(index++, block))
// Meet the last element
if (!match(NeoLangTokenType.COMMA)) {
break
}
}
}
return NeoLangArrayNode(arrayName, elements.toTypedArray())
}
return null
}
/**
* @attrName The block holder's name
*/
private fun block(attrName: NeoLangStringNode): NeoLangBlockNode? {
val block = blockNonArrayElement(attrName)
if (block != null) {
return block
}
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
when (token.tokenType) {
NeoLangTokenType.ARRAY_START -> {
match(NeoLangTokenType.ARRAY_START, errorThrow = true)
val array = array(attrName)
match(NeoLangTokenType.ARRAY_END, errorThrow = true)
// Allow empty arrays
return if (array != null) NeoLangBlockNode(array) else NeoLangBlockNode.emptyNode()
}
else -> throw InvalidTokenException("Unexpected token `${token.tokenValue}' typed `${token.tokenType}' for block")
}
}
private fun blockNonArrayElement(attrName: NeoLangStringNode): NeoLangBlockNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
return when (token.tokenType) {
NeoLangTokenType.NUMBER -> {
match(NeoLangTokenType.NUMBER, errorThrow = true)
return NeoLangBlockNode(NeoLangNumberNode(token))
@ -130,10 +196,7 @@ class NeoLangParser {
// Allow empty blocks
return if (group != null) NeoLangBlockNode(group) else NeoLangBlockNode.emptyNode()
}
else -> throw InvalidTokenException("Unexpected token `$token' for block, " +
"expected `${NeoLangTokenType.NUMBER}', `${NeoLangTokenType.ID}' or `${NeoLangTokenType.BRACKET_START}'")
else -> null
}
}
}

View File

@ -17,6 +17,10 @@ class NeoLangContext(val contextName: String) {
return attributes[attributeName] ?: NeoLangValue.UNDEFINED
}
fun defineArray() {
}
fun getAttributes(): Map<String, NeoLangValue> {
return attributes
}

View File

@ -0,0 +1,8 @@
package io.neolang.runtime.type
import io.neolang.ast.node.NeoLangBlockNode
/**
* @author kiva
*/
class NeoLangArrayElement(val index: Int, val block: NeoLangBlockNode)

View File

@ -1,21 +1,53 @@
package io.neolang.runtime.type
import io.neolang.ast.node.NeoLangBlockNode
/**
* @author kiva
*/
class NeoLangValue(private val rawValue: Any) {
fun asString(): String {
if (rawValue is Array<*>) {
val array = asArray()
return buildString {
append("Array [ ")
array.forEachIndexed { index, value ->
append(value.asString())
if (index != array.size - 1) {
append(", ")
}
}
append(" ]")
}
}
return rawValue.toString()
}
fun asNumber(): Double {
if (rawValue is Array<*>) {
return 0.0
}
try {
return asString().toDouble()
return rawValue.toString().toDouble()
} catch (e: NumberFormatException) {
return 0.0
}
}
fun asArray(): Array<NeoLangValue> {
return castArrayOrNull(rawValue) ?: arrayOf()
}
@Suppress("UNCHECKED_CAST")
private fun castArrayOrNull(rawValue: Any): Array<NeoLangValue>? {
return if (rawValue is Array<*> && rawValue.isNotEmpty() && rawValue[0] is NeoLangValue)
rawValue as Array<NeoLangValue>
else null
}
fun isValid(): Boolean {
return this != UNDEFINED
}

View File

@ -10,8 +10,8 @@ import org.junit.Test
*/
class NeoLangTest {
@Test
fun testNeoLangParser() {
Main.main(arrayOf("NeoLang/example/color-scheme.nl"))
fun arrayTest() {
Main.main(arrayOf("NeoLang/example/extra-key.nl"))
}
}

View File

@ -56,6 +56,6 @@ dependencies {
compile 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
// compile 'com.ramotion.cardslider:card-slider:0.1.0'
// compile 'com.github.igalata:Bubble-Picker:v0.2.4'
compile project(path: ':NeoLang')
testCompile project(path: ':NeoLang')
implementation project(path: ':NeoLang')
androidTestImplementation project(path: ':NeoLang')
}

View File

@ -3,6 +3,7 @@ package io.neoterm
import io.neoterm.frontend.config.ConfigVisitor
import io.neoterm.frontend.config.NeoConfigureFile
import org.junit.Test
import java.io.File
/**
* @author kiva
@ -12,13 +13,17 @@ class ConfigureFileTest {
println("attr [$contextName->$attrName]: ${visitor.getAttribute(contextName, attrName).asString()}")
}
private fun parseConfigure(filePath: String, contextName: String, attrName: String) {
val config = NeoConfigureFile(File(filePath))
if (config.parseConfigure()) {
val visitor = config.getVisitor()
printAttr(visitor, contextName, attrName)
}
}
@Test
fun configureFileTest() {
val config = NeoConfigureFile("NeoLang/example/color-scheme.nl")
if (config.parseConfigure()) {
println("Parsed!")
val visitor = config.getVisitor()
printAttr(visitor, "colors", "foreground")
}
// parseConfigure("NeoLang/example/color-scheme.nl", "colors", "foreground")
parseConfigure("NeoLang/example/extra-key.nl", "key", "0")
}
}