Feature: A new DSL for ColorScheme and ExtraKeysView and even more in future

This commit is contained in:
zt515
2017-08-02 00:47:28 +08:00
parent 3f38df8911
commit f43c2dde2e
28 changed files with 522 additions and 240 deletions

View File

@ -1,10 +0,0 @@
package io.neolang
/**
* @author kiva
*/
object Version {
const val MAJOR = 0
const val MINOR = 1
const val PATCH = 0
}

View File

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

View File

@ -0,0 +1,15 @@
package io.neolang.ast
import io.neolang.ast.base.NeoLangAstBaseNode
import io.neolang.ast.typed.NeoLangAstTypedNode
/**
* @author kiva
*/
class NeoLangBlockNode(ast: NeoLangAstBaseNode) : NeoLangAstTypedNode(ast) {
companion object {
fun emptyNode() :NeoLangBlockNode {
return NeoLangBlockNode(NeoLangDummyNode())
}
}
}

View File

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

View File

@ -0,0 +1,6 @@
package io.neolang.ast
/**
* @author kiva
*/
class NeoLangEOFToken : NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF)

View File

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

View File

@ -0,0 +1,8 @@
package io.neolang.ast
import io.neolang.ast.typed.NeoLangTokenTypedNode
/**
* @author kiva
*/
class NeoLangNumberNode(token: NeoLangToken) : NeoLangTokenTypedNode(token)

View File

@ -0,0 +1,21 @@
package io.neolang.ast
import io.neolang.ast.base.NeoLangAstBaseNode
/**
* @author kiva
*/
class NeoLangProgramNode(private val groups: List<NeoLangGroupNode>) : NeoLangAstBaseNode() {
override fun toString(): String {
return "NeoLangProgramNode { groups: $groups }"
}
companion object {
fun emptyNode() : NeoLangProgramNode {
return NeoLangProgramNode(listOf())
}
}
}

View File

@ -0,0 +1,8 @@
package io.neolang.ast
import io.neolang.ast.typed.NeoLangTokenTypedNode
/**
* @author kiva
*/
class NeoLangStringNode(token: NeoLangToken) : NeoLangTokenTypedNode(token)

View File

@ -0,0 +1,13 @@
package io.neolang.ast
/**
* @author kiva
*/
open class NeoLangToken(val tokenType: NeoLangTokenType, val tokenValue: NeoLangTokenValue) {
var lineNumber = 0
override fun toString(): String {
return "Token { tokenType: $tokenType, tokenValue: $tokenValue };"
}
}

View File

@ -0,0 +1,16 @@
package io.neolang.ast
/**
* @author kiva
*/
enum class NeoLangTokenType {
NUMBER,
STRING,
BRACKET_START,
BRACKET_END,
COLON,
QUOTE,
EOL,
EOF,
}

View File

@ -0,0 +1,24 @@
package io.neolang.ast
/**
* @author kiva
*/
enum class NeoLangTokenValue(val value: String) {
COLON(":"),
BRACKET_START("{"),
BRACKET_END("}"),
QUOTE("\""),
EOF("");
companion object {
fun wrap(tokenText: String): NeoLangTokenValue {
return when (tokenText) {
COLON.value -> COLON
BRACKET_START.value -> BRACKET_START
BRACKET_END.value -> BRACKET_END
QUOTE.value -> QUOTE
else -> EOF
}
}
}
}

View File

@ -0,0 +1,12 @@
package io.neolang.ast.base
import io.neolang.ast.visitor.NeoLangAstVisitor
/**
* @author kiva
*/
open class NeoLangAst {
fun visit(): NeoLangAstVisitor {
return NeoLangAstVisitor(this)
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
package io.neolang.ast.visitor
import io.neolang.ast.base.NeoLangAst
/**
* @author kiva
*/
open class NeoLangAstVisitor(ast: NeoLangAst)

View File

@ -0,0 +1,6 @@
package io.neolang.parser
/**
* @author kiva
*/
open class InvalidTokenException(message: String) : ParseException(message)

View File

@ -0,0 +1,133 @@
package io.neolang.parser
import io.neolang.ast.NeoLangEOFToken
import io.neolang.ast.NeoLangToken
import io.neolang.ast.NeoLangTokenType
import io.neolang.ast.NeoLangTokenValue
import java.util.*
/**
* grammar: [
* prog: group (group)*
* group: attribute (attribute)*
* attribute: TEXT COLON block
* block: NUMBER | TEXT | (BRACKET_START group BRACKET_STOP)
* ]
*/
/**
* @author kiva
*/
class NeoLangLexer {
private var programCode: String? = null
private var currentPosition: Int = 0
private var currentChar: Char = ' '
private var lineNumber = 0
internal fun setInputSource(programCode: String?) {
this.programCode = programCode
}
internal fun lex(): List<NeoLangToken> {
val programCode = this.programCode ?: return listOf()
currentPosition = 0
lineNumber = 1
currentChar = programCode[currentPosition]
val tokens = ArrayList<NeoLangToken>()
while (currentPosition < programCode.length) {
val token = nextToken
if (token is NeoLangEOFToken) {
break
}
tokens.add(token)
}
return tokens
}
private fun moveToNextChar(): Boolean {
val programCode = this.programCode ?: return false
currentPosition++
if (currentPosition >= programCode.length) {
return false
} else {
currentChar = programCode[currentPosition]
return true
}
}
private val nextToken: NeoLangToken
get() {
val programCode = this.programCode ?: return NeoLangEOFToken()
while (currentChar == ' '
|| currentChar == '\t'
|| currentChar == '\n'
|| currentChar == '\r') {
if (currentChar == '\n') {
++lineNumber
}
// Skip white chars
moveToNextChar()
}
if (currentPosition >= programCode.length) {
return NeoLangEOFToken()
}
val currentToken = NeoLangTokenValue.wrap(currentChar.toString())
val token: NeoLangToken = when (currentToken) {
NeoLangTokenValue.COLON -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.COLON, currentToken)
}
NeoLangTokenValue.BRACKET_START -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.BRACKET_START, currentToken)
}
NeoLangTokenValue.BRACKET_END -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.BRACKET_END, currentToken)
}
NeoLangTokenValue.QUOTE -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.QUOTE, currentToken)
}
else -> {
if (Character.isDigit(currentChar)) {
NeoLangToken(NeoLangTokenType.NUMBER, NeoLangTokenValue.wrap(getNextTokenAsNumber()))
} else if (Character.isLetterOrDigit(currentChar)) {
NeoLangToken(NeoLangTokenType.STRING, NeoLangTokenValue.wrap(getNextTokenAsString()))
} else {
throw InvalidTokenException("Unexpected character: " + currentChar)
}
}
}
token.lineNumber = lineNumber
return token
}
private fun getNextTokenAsNumber(): String {
return buildString {
while (Character.isDigit(currentChar)) {
append(currentChar)
if (!moveToNextChar()) {
break
}
}
}
}
private fun getNextTokenAsString(): String {
return buildString {
while (Character.isLetterOrDigit(currentChar)) {
append(currentChar)
if (!moveToNextChar()) {
break
}
}
}
}
}

View File

@ -0,0 +1,134 @@
package io.neolang.parser
import io.neolang.ast.*
import io.neolang.ast.base.NeoLangAst
/**
* @author kiva
*/
class NeoLangParser {
var ast: NeoLangAst? = null
private set
private val lexer = NeoLangLexer()
private var tokens = mutableListOf<NeoLangToken>()
private var currentPosition: Int = 0
private var currentToken: NeoLangToken? = null
fun setInputSource(programCode: String?) {
lexer.setInputSource(programCode)
}
fun parse(): NeoLangAst {
updateParserStatus(lexer.lex())
return ast ?: throw ParseException("AST is null")
}
private fun updateParserStatus(tokens: List<NeoLangToken>) {
if (tokens.isEmpty()) {
throw ParseException("Input tokens must be non-empty")
}
this.tokens.clear()
this.tokens.addAll(tokens)
currentPosition = 0
currentToken = tokens[currentPosition]
ast = program()
}
private fun match(tokenType: NeoLangTokenType, errorThrow: Boolean = false): Boolean {
val currentToken = this.currentToken ?: throw InvalidTokenException("Unexpected token: null")
if (currentToken.tokenType === tokenType) {
currentPosition++
if (currentPosition >= tokens.size) {
this.currentToken = NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF)
} else {
this.currentToken = tokens[currentPosition]
}
return true
} else if (errorThrow) {
throw InvalidTokenException("Unexpected token type " +
"`${currentToken.tokenType}' near line ${currentToken.lineNumber}, " +
"expected $tokenType")
}
return false
}
private fun program(): NeoLangProgramNode {
val token = currentToken
var group = group()
if (group != null) {
val groups = mutableListOf(group)
while (token?.tokenType !== NeoLangTokenType.EOF) {
group = group()
if (group == null) {
break
}
groups.add(group)
}
return NeoLangProgramNode(groups)
}
return NeoLangProgramNode.emptyNode()
}
private fun group(): NeoLangGroupNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
var attr = attribute()
if (attr != null) {
val attributes = mutableListOf(attr)
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.BRACKET_END) {
attr = attribute()
if (attr == null) {
break
}
attributes.add(attr)
}
return NeoLangGroupNode(attributes)
}
return null
}
private fun attribute(): NeoLangAttributeNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
if (match(NeoLangTokenType.STRING)) {
match(NeoLangTokenType.COLON, errorThrow = true)
val block = block() ?: NeoLangBlockNode.emptyNode()
return NeoLangAttributeNode(NeoLangStringNode(token), block)
}
return null
}
private fun block(): NeoLangBlockNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
when (token.tokenType) {
NeoLangTokenType.NUMBER -> {
match(NeoLangTokenType.NUMBER, errorThrow = true)
return NeoLangBlockNode(NeoLangNumberNode(token))
}
NeoLangTokenType.STRING -> {
match(NeoLangTokenType.STRING, errorThrow = true)
return NeoLangBlockNode(NeoLangStringNode(token))
}
NeoLangTokenType.BRACKET_START -> {
match(NeoLangTokenType.BRACKET_START, errorThrow = true)
val group = group()
match(NeoLangTokenType.BRACKET_END, errorThrow = true)
// Allow empty blocks
return if (group != null) NeoLangBlockNode(group) else NeoLangBlockNode.emptyNode()
}
else -> throw InvalidTokenException("Unexpected token `$token' for block, " +
"expected `${NeoLangTokenType.NUMBER}', `${NeoLangTokenType.STRING}' or `${NeoLangTokenType.BRACKET_START}'")
}
}
}

View File

@ -0,0 +1,6 @@
package io.neolang.parser
/**
* @author kiva
*/
open class ParseException(message: String) : RuntimeException(message)

View File

@ -1,10 +0,0 @@
package io.neolang.token
/**
* @author kiva
*/
class Token {
lateinit var type: TokenType
lateinit var value: Any
lateinit var nextToken: Token
}

View File

@ -1,40 +0,0 @@
package io.neolang.token
/**
* @author kiva
*/
enum class TokenType {
TYPE_IDENTIFIER,
TYPE_STRING,
TYPE_INTEGER,
TYPE_BOOLEAN,
KEYWORD_DOLLAR, /* $ */
KEYWORD_USE /* @ */,
OPERATOR_BEGIN,
OPT_ADD, /* + */
OPT_SUB, /* - */
OPT_NAV, /* - (负号) */
OPT_MUL, /* * */
OPT_DIV, /* / */
OPT_MOD, /* % */
OPT_XOR, /* ^ */
OPT_AND, /* & */
OPT_OR, /* | */
LEFT_SHIFT, /* << */
RIGHT_SHIFT, /* >> */
OPERATOR_END,
LOGICAL_OPERATOR_BEGIN,
OPT_LAND, /* && */
OPT_LOR, /* || */
OPT_LE, /* <= */
OPT_LT, /* < */
OPT_GE, /* >= */
OPT_GT, /* > */
OPT_EQ, /* == */
OPT_NEQ, /* != */
OPT_NOT, /* ! */
LOGICAL_OPERATOR_END,
}

View File

@ -55,5 +55,6 @@ dependencies {
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.19'
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 'com.github.igalata:Bubble-Picker:v0.2.4'
implementation project(path: ':NeoLang')
}

View File

@ -1,88 +1,30 @@
package io.neoterm.ui.setup
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.widget.Button
import android.widget.Toast
import com.igalata.bubblepicker.BubblePickerListener
import com.igalata.bubblepicker.adapter.BubblePickerAdapter
import com.igalata.bubblepicker.model.BubbleGradient
import com.igalata.bubblepicker.model.PickerItem
import com.igalata.bubblepicker.rendering.BubblePicker
import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.customize.pm.NeoPackageManager
import io.neoterm.customize.pm.NeoPackageManagerUtils
import io.neoterm.customize.setup.BaseFileInstaller
import io.neoterm.preference.NeoPreference
import io.neoterm.preference.NeoTermPath
import io.neoterm.utils.PackageUtils
import io.neoterm.frontend.floating.TerminalDialog
import java.util.*
/**
* @author kiva
*/
class SetupActivity : AppCompatActivity() {
companion object {
private val DEFAULT_PACKAGES = arrayOf(
"zsh", "neoterm-core", "tmux", "nodejs",
"fish", "make", "gdb", "clang", "vim", "emacs", "nano",
"curl", "git", "python", "p7zip", "oh-my-zsh")
}
lateinit var picker: BubblePicker
lateinit var toast: Toast
var aptUpdated = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ui_setup)
picker = findViewById(R.id.bubble_picker) as BubblePicker
val nextButton = findViewById(R.id.setup_next) as Button
nextButton.setOnClickListener {
if (aptUpdated) {
val packageList = mutableListOf("apt", "install", "-y")
var withShell: String? = null
picker.selectedItems
.filterNotNull()
.forEach {
val name = it.title ?: ""
packageList.add(name)
if (name == "zsh" || name == "fish" || name == "bash"
|| name == "mosh" || name == "dash") {
withShell = name
}
}
if (packageList.size == 0) {
return@setOnClickListener
}
TerminalDialog(this@SetupActivity)
.onFinish(object : TerminalDialog.SessionFinishedCallback {
override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) {
if (finishedSession?.exitStatus == 0) {
dialog.dismiss()
if (withShell != null) {
NeoPreference.setLoginShell(withShell!!)
}
} else {
dialog.setTitle(getString(R.string.error))
}
}
})
.execute(NeoTermPath.APT_BIN_PATH, packageList.toTypedArray())
.show(getString(R.string.installer_message))
} else {
finish()
}
}
setupBubbles()
installBaseFiles()
}
@ -132,84 +74,31 @@ class SetupActivity : AppCompatActivity() {
})
}
@SuppressLint("ShowToast")
private fun setupBubbles() {
val titles =
if (intent.getBooleanExtra("setup", false))
DEFAULT_PACKAGES
else
randomPackageList()
val colors = resources.obtainTypedArray(R.array.bubble_colors)
toast = Toast.makeText(this, null, Toast.LENGTH_LONG)
picker.bubbleSize = 25
picker.adapter = object : BubblePickerAdapter {
override val totalCount = titles.size
override fun getItem(position: Int): PickerItem {
return PickerItem().apply {
title = titles[position]
textColor = ContextCompat.getColor(this@SetupActivity, android.R.color.white)
gradient = BubbleGradient(colors.getColor((position * 2) % 8, 0),
colors.getColor((position * 2) % 8 + 1, 0), BubbleGradient.VERTICAL)
}
}
}
picker.listener = object : BubblePickerListener {
override fun onBubbleSelected(item: PickerItem) {
val packageName = item.title
val pm = NeoPackageManager.get()
val packageInfo = pm.getPackageInfo(packageName)
if (packageInfo != null) {
val packageDesc = packageInfo.description
toast.cancel()
toast.setText(packageDesc)
toast.show()
}
}
override fun onBubbleDeselected(item: PickerItem) {
toast.cancel()
}
}
colors.recycle()
}
private fun randomPackageList(): Array<String> {
val list = mutableListOf<String>()
val pm = NeoPackageManager.get()
val sourceFiles = NeoPackageManagerUtils.detectSourceFiles()
pm.clearPackages()
for (index in sourceFiles.indices) {
pm.refreshPackageList(sourceFiles[index], false)
}
val limit = DEFAULT_PACKAGES.size
val packageNames = pm.packages.keys
val packageCount = packageNames.size
val random = Random()
var i = 0
while (i < limit) {
val randomIndex = Math.abs(random.nextInt()) % packageCount
val packageName = packageNames.elementAt(randomIndex)
if (packageName.startsWith("lib") || packageName.endsWith("-dev")) {
continue
}
list.add(packageName)
++i
}
return list.toTypedArray()
}
override fun onResume() {
super.onResume()
picker.onResume()
}
override fun onPause() {
super.onPause()
picker.onPause()
}
// private fun randomPackageList(): Array<String> {
// val list = mutableListOf<String>()
// val pm = NeoPackageManager.get()
//
// val sourceFiles = NeoPackageManagerUtils.detectSourceFiles()
// pm.clearPackages()
// for (index in sourceFiles.indices) {
// pm.refreshPackageList(sourceFiles[index], false)
// }
//
// val limit = 20
// val packageNames = pm.packages.keys
// val packageCount = packageNames.size
// val random = Random()
//
// var i = 0
// while (i < limit) {
// val randomIndex = Math.abs(random.nextInt()) % packageCount
// val packageName = packageNames.elementAt(randomIndex)
// if (packageName.startsWith("lib") || packageName.endsWith("-dev")) {
// continue
// }
// list.add(packageName)
// ++i
// }
// return list.toTypedArray()
// }
}

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:backgroundColor="@android:color/white">
android:background="@android:color/white">
<TextView
android:id="@+id/titleTextView"
@ -28,18 +27,6 @@
android:textColor="@color/colorAccent"
android:textSize="16sp" />
<TextView
android:id="@+id/hintTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="14dp"
android:gravity="center"
android:lineSpacingMultiplier="1"
android:text="@string/setup_info2"
android:textColor="#9b9b9b"
android:textSize="14sp" />
<Button
android:id="@+id/setup_next"
android:layout_gravity="end"
@ -52,9 +39,4 @@
android:textColor="#9b9b9b"
android:textStyle="bold" />
<com.igalata.bubblepicker.rendering.BubblePicker
android:id="@+id/bubble_picker"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -1,21 +0,0 @@
package io.neoterm
import io.neoterm.customize.pm.NeoPackageManager
import io.neoterm.customize.pm.NeoPackageManagerUtils
import org.junit.Test
import java.io.File
import java.net.URL
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see [Testing documentation](http://d.android.com/tools/testing)
*/
class ExampleUnitTest {
@Test
fun test_pkg_parser() {
val prefix = NeoPackageManagerUtils.detectSourceFilePrefix("https://baidu.com:81")
println(prefix)
}
}

View File

@ -0,0 +1,19 @@
package io.neoterm
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see [Testing documentation](http://d.android.com/tools/testing)
*/
class NeoLangTest {
@Test
fun testNeoLangParser() {
val parser = io.neolang.parser.NeoLangParser()
parser.setInputSource("app: { x: {} \n x: hello \n a: 1111 \n x: { x: 123 } }")
val ast = parser.parse()
println(ast)
}
}