{
26 | public HaskellVaridStub(StubElement parent, IStubElementType elementType, StringRef name) {
27 | super(parent, elementType, name);
28 | }
29 |
30 | public HaskellVaridStub(StubElement parent, IStubElementType elementType, String name) {
31 | super(parent, elementType, name);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/psi/stubs/index/HaskellAllNameIndex.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2020 Rik van der Kleij
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package intellij.haskell.psi.stubs.index
18 |
19 | import com.intellij.psi.stubs.{StringStubIndexExtension, StubIndexKey}
20 | import intellij.haskell.psi.HaskellNamedElement
21 |
22 | object HaskellAllNameIndex {
23 |
24 | val Key: StubIndexKey[String, HaskellNamedElement] = StubIndexKey.createIndexKey("haskell.all.name")
25 | val Version = 1
26 | }
27 |
28 | class HaskellAllNameIndex extends StringStubIndexExtension[HaskellNamedElement] {
29 |
30 | override def getVersion: Int = {
31 | super.getVersion + HaskellAllNameIndex.Version
32 | }
33 |
34 | def getKey: StubIndexKey[String, HaskellNamedElement] = {
35 | HaskellAllNameIndex.Key
36 | }
37 | }
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/psi/stubs/types/HaskellStubElementType.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2020 Rik van der Kleij
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package intellij.haskell.psi.stubs.types
18 |
19 | import com.intellij.psi.stubs.{IStubElementType, IndexSink, StubElement}
20 | import intellij.haskell.HaskellLanguage
21 | import intellij.haskell.psi.HaskellCompositeElement
22 |
23 | abstract class HaskellStubElementType[S <: StubElement[T], T <: HaskellCompositeElement](debugName: String) extends IStubElementType[S, T](debugName, HaskellLanguage.Instance) {
24 |
25 | def indexStub(stub: S, sink: IndexSink): Unit
26 |
27 | def getExternalId: String = {
28 | "haskell." + super.toString
29 | }
30 | }
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/psi/stubs/types/HaskellVarsymStubElementType.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.psi.stubs.types
2 |
3 | import com.intellij.psi.PsiElement
4 | import com.intellij.psi.stubs.{StubElement, StubInputStream}
5 | import intellij.haskell.psi.HaskellVarsym
6 | import intellij.haskell.psi.impl.HaskellVarsymImpl
7 | import intellij.haskell.psi.stubs.HaskellVarsymStub
8 |
9 | class HaskellVarsymStubElementType(debugName: String) extends HaskellNamedStubElementType[HaskellVarsymStub, HaskellVarsym](debugName) {
10 | def createPsi(stub: HaskellVarsymStub): HaskellVarsym = {
11 | new HaskellVarsymImpl(stub, this)
12 | }
13 |
14 | def createStub(psi: HaskellVarsym, parentStub: StubElement[_ <: PsiElement]): HaskellVarsymStub = {
15 | new HaskellVarsymStub(parentStub, this, psi.getName)
16 | }
17 |
18 | def deserialize(dataStream: StubInputStream, parentStub: StubElement[_ <: PsiElement]): HaskellVarsymStub = {
19 | new HaskellVarsymStub(parentStub, this, dataStream.readName)
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/HaskellStackConfigurationType.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig
2 |
3 | import javax.swing.Icon
4 | import com.intellij.execution.configurations.{ConfigurationFactory, ConfigurationType}
5 | import icons.HaskellIcons
6 | import intellij.haskell.runconfig.console.HaskellConsoleConfigurationFactory
7 | import intellij.haskell.runconfig.run.HaskellRunConfigurationFactory
8 | import intellij.haskell.runconfig.test.HaskellTestConfigurationFactory
9 |
10 | class HaskellStackConfigurationType extends ConfigurationType {
11 | def getDisplayName: String = "Haskell Stack"
12 |
13 | def getConfigurationTypeDescription: String = "Haskell Stack configuration"
14 |
15 | def getIcon: Icon = HaskellIcons.HaskellLogo
16 |
17 | def getId = "HaskellStackConfigurationType"
18 |
19 | def getConfigurationFactories: Array[ConfigurationFactory] = Array[ConfigurationFactory](
20 | new HaskellConsoleConfigurationFactory(this),
21 | new HaskellRunConfigurationFactory(this),
22 | new HaskellTestConfigurationFactory(this)
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/console/HaskellConsoleActionPromoter.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.console
2 |
3 | import com.intellij.openapi.actionSystem.{ActionPromoter, AnAction, DataContext}
4 | import com.intellij.openapi.editor.actions.EnterAction
5 | import com.intellij.util.containers.ContainerUtil
6 |
7 | import java.util
8 | import java.util.Comparator
9 |
10 | class HaskellConsoleActionPromoter extends ActionPromoter {
11 | private val Comparator = new Comparator[AnAction]() {
12 | def compare(o1: AnAction, o2: AnAction): Int = {
13 | (notEnter(o1), notEnter(o2)) match {
14 | case (false, true) => 1
15 | case (true, false) => -1
16 | case _ => 0
17 | }
18 | }
19 |
20 | private def notEnter(o: AnAction) = !o.isInstanceOf[EnterAction]
21 | }
22 |
23 | override def promote(actions: util.List[_ <: AnAction], context: DataContext): util.List[AnAction] = {
24 | val result = new util.ArrayList[AnAction](actions)
25 | ContainerUtil.sort(result, Comparator)
26 | result
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/console/HaskellConsoleConfigurationFactory.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.console
2 |
3 | import com.intellij.execution.configurations.{ConfigurationFactory, ConfigurationType}
4 | import com.intellij.openapi.project.Project
5 |
6 | class HaskellConsoleConfigurationFactory(val typez: ConfigurationType) extends ConfigurationFactory(typez) {
7 | private val name = "Haskell Stack REPL"
8 |
9 | override def createTemplateConfiguration(project: Project) = new HaskellConsoleConfiguration(name, project, this)
10 |
11 | override def getName: String = name
12 |
13 | override def getId: String = getName
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/console/HaskellConsoleExecuteAction.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.console
2 |
3 | import com.intellij.openapi.actionSystem.{AnAction, AnActionEvent, CommonDataKeys}
4 | import com.intellij.openapi.editor.Editor
5 | import com.intellij.openapi.editor.ex.EditorEx
6 |
7 | class HaskellConsoleExecuteAction extends AnAction {
8 |
9 | override def update(actionEvent: AnActionEvent): Unit = {
10 | val presentation = actionEvent.getPresentation
11 | val editor: Editor = actionEvent.getData(CommonDataKeys.EDITOR)
12 | if (!editor.isInstanceOf[EditorEx] || editor.asInstanceOf[EditorEx].isRendererMode) {
13 | presentation.setEnabled(false)
14 | } else {
15 | HaskellConsoleViewMap.getConsole(editor) match {
16 | case Some(consoleView) => presentation.setEnabledAndVisible(consoleView.isRunning)
17 | case None => presentation.setEnabled(false)
18 | }
19 | }
20 | }
21 |
22 | override def actionPerformed(actionEvent: AnActionEvent): Unit = {
23 | for {
24 | editor <- Option(actionEvent.getData(CommonDataKeys.EDITOR))
25 | consoleView <- HaskellConsoleViewMap.getConsole(editor)
26 | } yield consoleView.execute()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/console/HaskellConsoleHighlightingUtil.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.console
2 |
3 | import scala.util.matching.Regex
4 |
5 | object HaskellConsoleHighlightingUtil {
6 | private val ID = "[A-Z]\\w*"
7 | private val Module = s"\\*?$ID(\\.$ID)*"
8 | private val Modules = s"($Module\\s*)*"
9 | val PromptArrow = ">"
10 | val LambdaArrow = "λ> "
11 | val LineWithPrompt = new Regex(s"($Modules$PromptArrow)")
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/console/HaskellConsoleProcessHandler.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.console
2 |
3 | import java.nio.charset.Charset
4 |
5 | import com.intellij.execution.console.LanguageConsoleImpl
6 | import com.intellij.execution.process.ColoredProcessHandler
7 |
8 | class HaskellConsoleProcessHandler private[runconfig](val process: Process, val commandLine: String, val console: HaskellConsoleView) extends ColoredProcessHandler(process, commandLine, Charset.forName("UTF-8")) {
9 |
10 | def getLanguageConsole: LanguageConsoleImpl = console
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/console/HaskellConsoleViewMap.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.console
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 |
5 | import com.intellij.openapi.editor.Editor
6 | import com.intellij.openapi.project.Project
7 | import com.intellij.psi.PsiFile
8 |
9 | import scala.collection.concurrent
10 | import scala.jdk.CollectionConverters._
11 |
12 | object HaskellConsoleViewMap {
13 | private val consoleViews = new ConcurrentHashMap[Editor, HaskellConsoleView]().asScala
14 |
15 | def addConsole(console: HaskellConsoleView): Unit = {
16 | consoleViews.put(console.getConsoleEditor, console)
17 | }
18 |
19 | def delConsole(console: HaskellConsoleView): Unit = {
20 | consoleViews.remove(console.getConsoleEditor)
21 | }
22 |
23 | def getConsole(editor: Editor): Option[HaskellConsoleView] = {
24 | consoleViews.get(editor)
25 | }
26 |
27 | def getConsole(editor: Project): Option[HaskellConsoleView] = {
28 | consoleViews.values.find(console => console.project == editor && console.isShowing)
29 | }
30 |
31 | // File is project file and not file which represents console
32 | val projectFileByConfigName: concurrent.Map[String, PsiFile] = new ConcurrentHashMap[String, PsiFile]().asScala
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/run/HaskellRunConfigurationFactory.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.run
2 |
3 | import com.intellij.execution.configurations.{ConfigurationFactory, ConfigurationType}
4 | import com.intellij.openapi.project.Project
5 |
6 | class HaskellRunConfigurationFactory(val typez: ConfigurationType) extends ConfigurationFactory(typez) {
7 | private val name = "Haskell Stack Runner"
8 |
9 | override def createTemplateConfiguration(project: Project) = new HaskellRunConfiguration(name, project, this)
10 |
11 | override def getName: String = name
12 |
13 | override def getId: String = name
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/runconfig/test/HaskellTestConfigurationFactory.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.runconfig.test
2 |
3 | import com.intellij.execution.configurations.{ConfigurationFactory, ConfigurationType}
4 | import com.intellij.openapi.project.Project
5 |
6 | class HaskellTestConfigurationFactory(val typez: ConfigurationType) extends ConfigurationFactory(typez) {
7 | private val name = "Haskell Stack Tester"
8 |
9 | override def createTemplateConfiguration(project: Project) = new HaskellTestConfiguration(name, project, this)
10 |
11 | override def getName: String = name
12 |
13 | override def getId: String = name
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/sdk/HaskellStackVersionValidator.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.sdk
2 |
3 | import com.intellij.util.text.VersionComparatorUtil
4 |
5 | object HaskellStackVersionValidator {
6 |
7 | final val MinimumVersion = "1.7.0"
8 |
9 | def validate(maybeVersion: Option[String]): Unit = {
10 | validate(maybeVersion, MinimumVersion)
11 | }
12 |
13 | private[sdk] def validate(version: Option[String], minimumVersion: String): Unit = {
14 | if (!isValid(version, minimumVersion)) {
15 | throw new Exception(s"Stack version should be > $minimumVersion")
16 | }
17 | }
18 |
19 | private[sdk] def isValid(version: Option[String], minimumVersion: String): Boolean = {
20 | version.map(_.trim) match {
21 | case Some(v) if VersionComparatorUtil.compare(v, minimumVersion) >= 0 => true
22 | case _ => false
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/spellchecker/CabalSpellcheckingStrategy.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.spellchecker
2 |
3 | import com.intellij.psi.PsiElement
4 | import com.intellij.spellchecker.tokenizer.{SpellcheckingStrategy, Tokenizer}
5 | import intellij.haskell.cabal.CabalLanguage
6 |
7 | /**
8 | * Provide spellchecker support for Cabal sources.
9 | */
10 | class CabalSpellcheckingStrategy extends SpellcheckingStrategy {
11 | override def isMyContext(element: PsiElement): Boolean = CabalLanguage.Instance.is(element.getLanguage)
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/spellchecker/HaskellBundledDictionaryProvider.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.spellchecker
2 |
3 | import com.intellij.spellchecker.BundledDictionaryProvider
4 |
5 | /**
6 | * Provides a custom dictionary for the Haskell spellchecker.
7 | */
8 | class HaskellBundledDictionaryProvider extends BundledDictionaryProvider {
9 | override def getBundledDictionaries: Array[String] = Array("/dictionary/haskell.dic")
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/spellchecker/HaskellSpellcheckingStrategy.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.spellchecker
2 |
3 | import intellij.haskell.HaskellLanguage
4 | import com.intellij.psi.PsiElement
5 | import com.intellij.spellchecker.tokenizer.{Tokenizer, SpellcheckingStrategy}
6 |
7 | /**
8 | * Provide spellchecker support for Haskell sources.
9 | */
10 | class HaskellSpellcheckingStrategy extends SpellcheckingStrategy {
11 | override def isMyContext(element: PsiElement): Boolean = HaskellLanguage.Instance.is(element.getLanguage)
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/ui/EnterNameDialog.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.ui
2 |
3 | import java.awt.BorderLayout
4 |
5 | import com.intellij.openapi.ui.DialogWrapper
6 | import javax.swing.{JComponent, JLabel, JPanel, JTextField}
7 |
8 | class EnterNameDialog(prompt: String, suggestion: String = "") extends DialogWrapper(true) {
9 | private val textField = if (suggestion.isEmpty) new JTextField(10) else new JTextField(suggestion)
10 | init()
11 | setTitle(prompt)
12 | override def createCenterPanel(): JComponent = {
13 | val dialogPanel: JPanel = new JPanel(new BorderLayout)
14 |
15 | val label: JLabel = new JLabel(prompt)
16 | dialogPanel.add(label, BorderLayout.NORTH)
17 |
18 | dialogPanel.add(textField, BorderLayout.SOUTH)
19 |
20 | dialogPanel
21 | }
22 |
23 | override def getPreferredFocusedComponent: JComponent = textField
24 |
25 | def getName: String = textField.getText
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/util/FutureUtil.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.util
2 |
3 | import java.util.concurrent.{Future, TimeUnit, TimeoutException}
4 |
5 | import com.intellij.openapi.project.Project
6 | import intellij.haskell.HaskellNotificationGroup
7 |
8 | object FutureUtil {
9 |
10 | def waitForValue[T](project: Project, future: Future[T], actionDescription: String, timeoutInSeconds: Int = 5): Option[T] = {
11 | try {
12 | Option(future.get(timeoutInSeconds, TimeUnit.SECONDS))
13 | } catch {
14 | case _: TimeoutException =>
15 | HaskellNotificationGroup.logInfoEvent(project, s"Timeout while $actionDescription")
16 | None
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/scala/intellij/haskell/util/HtmlElement.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.util
2 |
3 | object HtmlElement {
4 | final val Quot = """
5 | final val Lt = "<"
6 | final val Gt = ">"
7 | final val Amp = "&"
8 | final val Nbsp = " "
9 | final val HtmlStart = ""
10 | final val BodyStart = ""
11 | final val HtmlEnd = ""
12 | final val BodyEnd = ""
13 | final val Break = "
"
14 | final val HorizontalLine = "
"
15 | final val PreStart = ""
16 | final val PreEnd = "
"
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/scala/intellij/haskell/HaskellParsingTest.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell
2 |
3 | import com.intellij.testFramework.ParsingTestCase
4 |
5 | class HaskellParsingTest extends ParsingTestCase("", "hs", new HaskellParserDefinition) {
6 | override def getTestDataPath: String = "src/test/testData/parsing-hs"
7 |
8 | def testPragma(): Unit = {
9 | doTest(true)
10 | }
11 |
12 | def testComplicatedPragma(): Unit = {
13 | doTest(true)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/scala/intellij/haskell/alex/AlexParsingTest.scala:
--------------------------------------------------------------------------------
1 | package intellij.haskell.alex
2 |
3 | import com.intellij.testFramework.ParsingTestCase
4 | import intellij.haskell.alex.lang.parser.AlexParserDefinition
5 |
6 | class AlexParsingTest extends ParsingTestCase("", "x", new AlexParserDefinition) {
7 | override def getTestDataPath: String = "src/test/testData/parsing"
8 |
9 | def testSimple(): Unit = {
10 | doTest(true)
11 | }
12 |
13 | def testRules(): Unit = {
14 | doTest(true)
15 | }
16 |
17 | def testRuleDescription(): Unit = {
18 | doTest(true)
19 | }
20 |
21 | def testLexerOwO(): Unit = {
22 | doTest(true)
23 | }
24 |
25 | def testMixedStatefulStateless(): Unit = {
26 | doTest(true)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/testData/parsing-hs/ComplicatedPragma.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE TheWorldOverHeaven#-}
2 | {-# OPTIONS_GHC "String" #-}
3 | {-# OPTIONS_GHC " \" Escape \' \n" #-}
4 | {-# OPTIONS_GHC
5 | MULTI
6 | LINE
7 | #-}
8 |
9 | {-# LANGUAGE Comma,Separated,Pragmas #-}
10 |
11 |
--------------------------------------------------------------------------------
/src/test/testData/parsing-hs/Pragma.hs:
--------------------------------------------------------------------------------
1 | {-# Hey #-}
2 | {-# LANGUAGE OhMyGod #-}
3 | {-# OPTIONS_GHC --omG #-}
4 |
--------------------------------------------------------------------------------
/src/test/testData/parsing/MixedStatefulStateless.x:
--------------------------------------------------------------------------------
1 | {
2 | module MixedStatefulStateless where
3 |
4 | import Komeji.Satori
5 | import Komeji.Koishi
6 | }
7 |
8 | tokens :-
9 |
10 | @reimu { simple ReimuToken }
11 |
12 | {
13 | @alice { simple AliceToken }
14 | }
15 |
16 | {
17 | Stop the time!
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/testData/parsing/RuleDescription.x:
--------------------------------------------------------------------------------
1 | {
2 | {-# LANGUAGE LambdaCase #-}
3 |
4 | module RuleDescription where
5 |
6 | import Alice.Margatroid
7 | import Marisa.Kirisame
8 | }
9 |
10 | %wrapper "bytestring"
11 |
12 | $yasaka_kanako = [a-z]
13 | $moriya_suwako = [A-Z]
14 |
15 | tokens :-
16 |
17 | {
18 | "kochiya sanae" { simple "Jusei no kokosei" }
19 | $yasaka_kanako { simple "Central Goddess" }
20 | $moriya_suwako { simple "Local Goddess" }
21 | }
22 |
23 | @reiuji { simple ReiujiUtsuho }
24 |
25 | {
26 | Jojo! This is the last of my hamon!
27 | Take it from meeeeee!
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/testData/parsing/Rules.x:
--------------------------------------------------------------------------------
1 | {
2 | {-# LANGUAGE LambdaCase #-}
3 |
4 | module Rules where
5 |
6 | import Crazy.Diamond
7 | import Killer.Queen
8 | }
9 |
10 | %wrapper "bytestring"
11 |
12 | $sanae = god
13 | @marisa = $black $white
14 | @alice = love $marisa
15 | @reimu = friend $marisa love $sanae
16 |
17 | tokens :-
18 |
19 | {
20 | @alice { simple AliceToken }
21 | }
22 |
23 | @reimu { simple ReimuToken }
24 |
25 | {
26 | it was me, dio!
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/testData/parsing/Simple.x:
--------------------------------------------------------------------------------
1 | {
2 | {-# LANGUAGE LambdaCase #-}
3 |
4 | module Simple where
5 |
6 | import Star.Platinum
7 | import The.World
8 | }
9 |
10 | %wrapper "bytestring"
11 |
12 | $sanae = god
13 | $pachouli = [a-zA-Z]
14 |
15 | @marisa = $black $white
16 | @alice = love $marisa
17 | @reimu = friend $marisa love $sanae
18 |
19 | tokens :-
20 |
21 | {
22 | I reject my humanity, jojo!
23 | }
24 |
--------------------------------------------------------------------------------