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.github.wrdlbrnft:sorted-list-adapter:0.2.0.19'
compile 'com.simplecityapps:recyclerview-fastscroll:1.0.16' compile 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
// compile 'com.ramotion.cardslider:card-slider:0.1.0' // 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 package io.neoterm.ui.setup
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.widget.Button import android.widget.Button
import android.widget.Toast 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.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.customize.setup.BaseFileInstaller
import io.neoterm.preference.NeoPreference
import io.neoterm.preference.NeoTermPath
import io.neoterm.utils.PackageUtils import io.neoterm.utils.PackageUtils
import io.neoterm.frontend.floating.TerminalDialog
import java.util.*
/** /**
* @author kiva * @author kiva
*/ */
class SetupActivity : AppCompatActivity() { 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 lateinit var toast: Toast
var aptUpdated = false var aptUpdated = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.ui_setup) setContentView(R.layout.ui_setup)
picker = findViewById(R.id.bubble_picker) as BubblePicker
val nextButton = findViewById(R.id.setup_next) as Button val nextButton = findViewById(R.id.setup_next) as Button
nextButton.setOnClickListener { 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() finish()
} }
}
setupBubbles()
installBaseFiles() installBaseFiles()
} }
@ -132,84 +74,31 @@ class SetupActivity : AppCompatActivity() {
}) })
} }
@SuppressLint("ShowToast") // private fun randomPackageList(): Array<String> {
private fun setupBubbles() { // val list = mutableListOf<String>()
val titles = // val pm = NeoPackageManager.get()
if (intent.getBooleanExtra("setup", false)) //
DEFAULT_PACKAGES // val sourceFiles = NeoPackageManagerUtils.detectSourceFiles()
else // pm.clearPackages()
randomPackageList() // for (index in sourceFiles.indices) {
val colors = resources.obtainTypedArray(R.array.bubble_colors) // pm.refreshPackageList(sourceFiles[index], false)
// }
toast = Toast.makeText(this, null, Toast.LENGTH_LONG) //
// val limit = 20
picker.bubbleSize = 25 // val packageNames = pm.packages.keys
picker.adapter = object : BubblePickerAdapter { // val packageCount = packageNames.size
override val totalCount = titles.size // val random = Random()
override fun getItem(position: Int): PickerItem { //
return PickerItem().apply { // var i = 0
title = titles[position] // while (i < limit) {
textColor = ContextCompat.getColor(this@SetupActivity, android.R.color.white) // val randomIndex = Math.abs(random.nextInt()) % packageCount
gradient = BubbleGradient(colors.getColor((position * 2) % 8, 0), // val packageName = packageNames.elementAt(randomIndex)
colors.getColor((position * 2) % 8 + 1, 0), BubbleGradient.VERTICAL) // if (packageName.startsWith("lib") || packageName.endsWith("-dev")) {
} // continue
} // }
} // list.add(packageName)
picker.listener = object : BubblePickerListener { // ++i
override fun onBubbleSelected(item: PickerItem) { // }
val packageName = item.title // return list.toTypedArray()
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()
}
} }

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
app:backgroundColor="@android:color/white"> android:background="@android:color/white">
<TextView <TextView
android:id="@+id/titleTextView" android:id="@+id/titleTextView"
@ -28,18 +27,6 @@
android:textColor="@color/colorAccent" android:textColor="@color/colorAccent"
android:textSize="16sp" /> 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 <Button
android:id="@+id/setup_next" android:id="@+id/setup_next"
android:layout_gravity="end" android:layout_gravity="end"
@ -52,9 +39,4 @@
android:textColor="#9b9b9b" android:textColor="#9b9b9b"
android:textStyle="bold" /> android:textStyle="bold" />
<com.igalata.bubblepicker.rendering.BubblePicker
android:id="@+id/bubble_picker"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout> </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)
}
}