Skip to content

Commit

Permalink
Implemented repeat loops
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobias-Kohn committed May 25, 2020
1 parent 9f3b0c1 commit c222c3c
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 41 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

### 3.0 Early Access Preview 4 (... 2020)

Implemented:
- Support for `repeat` loops;
- TigerJython specific libraries (only as part of the release, no sources);


### 3.0 Early Access Preview 3 (25 May 2020)

Expand All @@ -9,7 +15,7 @@ Implemented:
- Syntax checker honours Python version and other preferences;
- Customise appearance with font, font-size, and themes;

Bugfixes:
Bug fixes:
- Line numbers on JRE 8 were not displayed correctly;


Expand Down
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ val buildDate = "%d %s %d".format(

val buildTag = "-SNAPSHOT"

val buildVersion = "ea+03"
val buildVersion = "ea+04"

// This is needed to run/test the project without having to restart SBT afterwards
fork in run := true
Expand Down Expand Up @@ -96,7 +96,7 @@ libraryDependencies += "org.openjfx" % "javafx-graphics" % fxVersion classifier

// Other dependencies
libraryDependencies += "org.fxmisc.richtext" % "richtextfx" % "0.10.5"
libraryDependencies += "org.python" % "jython" % "2.7.2"
libraryDependencies += "org.python" % "jython-standalone" % "2.7.2"

// When building a common JAR, some files are typically shared and we need to discard
// any superfluous files
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/tigerjython/core/JythonExecutor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ object JythonExecutor {
def run(filename: String): Unit = {
Options.importSite = false
Options.Qnew = true
PythonInterpreter.initialize(System.getProperties, null, null)
val postProperties = new java.util.Properties()
postProperties.setProperty("python.cachedir.skip", "false")
PythonInterpreter.initialize(System.getProperties, postProperties, null)
jython.JythonBuiltins.initialize()
interpreter = new PythonInterpreter()
interpreter.execfile(filename)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ object StaticErrorChecker {
else
new SyntaxChecker(programCode, filename)
syntaxChecker.strictCode = Preferences.syntaxCheckIsStrict.get
syntaxChecker.rejectDeadCode = Preferences.syntaxCheckRejectDeadCode.get
// syntaxChecker.rejectDeadCode = Preferences.syntaxCheckRejectDeadCode.get
syntaxChecker.rejectDeadCode = Preferences.syntaxCheckIsStrict.get
syntaxChecker.repeatStatement = Preferences.repeatLoop.get
syntaxChecker.check() match {
case Some((pos, msg)) =>
Expand Down
80 changes: 80 additions & 0 deletions src/main/scala/tigerjython/execute/PythonCodeTranslator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package tigerjython.execute

import tigerjython.core.Preferences
import tigerpython.parser.errors.ErrorHandler
import tigerpython.parser.lexer.{Lexer, TokenType}
import tigerpython.parser.parsing.ParserState

/**
* In order to support some syntax enhancements such as `repeat` loops, we need to replace some tokens in the source
* code by others to create a fully compatible script. This object is the interface for such code transformations.
*
* @author Tobias Kohn
*/
object PythonCodeTranslator {

private val REPEAT_STRING = Map[Int, String](
2 -> "for _tj_repeat_counter_ in xrange(long(%s))",
3 -> "for _tj_repeat_counter in range(int(%s))"
)

/**
* Takes a Python program as input and transforms it so as to build a fully compatible script.
*
* If the code is transformed, the new program is returned. If the code requires no changes, the function returns
* `None`.
*
* @param code The source code as a `String` value.
* @return Either `None` or a new `String` representing the transformed code.
*/
def translate(code: String): Option[String] = {
val version = PythonInstallations.getSelectedVersionNumber
if (Preferences.repeatLoop.get)
_translate(code, version)
else
None
}

protected def _translate(code: String, pythonVersion: Int): Option[String] = {
val parserState = ParserState(code, pythonVersion, ErrorHandler.SilentErrorHandler)
parserState.repeatStatement = Preferences.repeatLoop.get
val lexer = new Lexer(code, parserState, 0)
val result = new StringBuilder()
var position: Int = 0
for (token <- lexer)
token.tokenType match {
case TokenType.REPEAT =>
result ++= code.substring(position, token.pos)
val n = lexer.head
if (n != null && n.tokenType == TokenType.COLON) {
result ++= "while True"
position = token.endPos
} else
if (n != null) {
position = n.pos
var bracketLevel: Int = 0
while (lexer.hasNext && !(bracketLevel == 0 && lexer.head.tokenType == TokenType.COLON))
lexer.next.tokenType match {
case TokenType.LEFT_BRACE | TokenType.LEFT_BRACKET | TokenType.LEFT_PARENS =>
bracketLevel += 1
case TokenType.RIGHT_BRACE | TokenType.RIGHT_BRACKET | TokenType.RIGHT_PARENS =>
bracketLevel -= 1
case _ =>
}
val hd = lexer.head
if (hd != null) {
val argument = code.substring(position, hd.pos)
result ++= REPEAT_STRING(pythonVersion).format(argument)
position = hd.pos
}
} else
position = token.endPos
case _ =>
}
if (position > 0) {
result ++= code.substring(position)
Some(result.toString)
} else
None
}
}
8 changes: 8 additions & 0 deletions src/main/scala/tigerjython/jython/Builtins.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,12 @@ public static PyObject msgDlg(PyObject message) {
JOptionPane.showMessageDialog(null, message);
return Py.None;
}

public static java.awt.Color makeColor(String value) {
javafx.scene.paint.Color color = javafx.scene.paint.Color.valueOf(value);
int r = (int)Math.round(color.getRed() * 255);
int g = (int)Math.round(color.getGreen() * 255);
int b = (int)Math.round(color.getBlue() * 255);
return new java.awt.Color(r, g, b);
}
}
2 changes: 1 addition & 1 deletion src/main/scala/tigerjython/jython/JythonBuiltins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object JythonBuiltins {
* This is the list of all names of methods to be redefined.
*/
val builtinNames: Array[String] = Array(
"input", "msgDlg", "raw_input"
"input", "msgDlg", "raw_input", "makeColor"
)

/**
Expand Down
82 changes: 49 additions & 33 deletions src/main/scala/tigerjython/ui/editor/EditorTab.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import javafx.stage.Popup
import org.fxmisc.flowless.VirtualizedScrollPane
import org.fxmisc.richtext.CodeArea
import tigerjython.errorhandling._
import tigerjython.execute.PythonExecutor
import tigerjython.execute.{PythonCodeTranslator, PythonExecutor}
import tigerjython.plugins.EventManager
import tigerjython.ui.{TabFrame, TigerJythonApplication, ZoomMixin}

Expand Down Expand Up @@ -177,6 +177,8 @@ abstract class EditorTab extends TabFrame {

def getFile: java.io.File = file

def getDefaultFileSuffix: String = ".py"

def getSelectedText: String =
editor.getSelectedText

Expand All @@ -185,19 +187,15 @@ abstract class EditorTab extends TabFrame {

def hasExecutableFile: Boolean =
if (file != null) {
save()
_execFile = file
saveExecutable()
true
} else
if (!isEmpty) {
} else if (!isEmpty) {
if (_execFile == null) {
_execFile = java.io.File.createTempFile(caption.getValue, ".py")
_execFile = java.io.File.createTempFile(caption.getValue + " (", ")" + getDefaultFileSuffix)
_execFile.deleteOnExit()
}
val writer = new FileWriter(_execFile)
val printer = new PrintWriter(writer)
printer.print(editor.getText())
printer.close()
saveExecutable()
true
} else
false
Expand Down Expand Up @@ -233,33 +231,33 @@ abstract class EditorTab extends TabFrame {
}

def run(): Unit = {
if (hasExecutableFile) {
outputPane.clear()
errorPane.clear()
infoPane.getSelectionModel.select(0)
EventManager.fireOnRun()
if (isRunning)
stop()
outputPane.clear()
errorPane.clear()
infoPane.getSelectionModel.select(0)
EventManager.fireOnRun()

// Check syntax
onFX(() => {
StaticErrorChecker.checkSyntax(caption.get(), editor.getText()) match {
case Some((line, offs, msg)) =>
displayError(line, offs, msg)
case None =>
Platform.runLater(() => _run())
}
})
} else
new Alert(Alert.AlertType.ERROR, "Please save the file first!", ButtonType.OK).showAndWait()
// Check syntax
onFX(() => {
StaticErrorChecker.checkSyntax(caption.get(), editor.getText()) match {
case Some((line, offs, msg)) =>
displayError(line, offs, msg)
case None =>
Platform.runLater(() => _run())
}
})
}

protected def _run(): Unit = {
// Execute the code
val executor = PythonExecutor(this)
if (executor != null)
executor.run()
else
new Alert(Alert.AlertType.ERROR, "Please choose an appropriate interpreter", ButtonType.OK).showAndWait()
}
protected def _run(): Unit =
if (hasExecutableFile) {
// Execute the code
val executor = PythonExecutor(this)
if (executor != null)
executor.run()
else
new Alert(Alert.AlertType.ERROR, "Please choose an appropriate interpreter", ButtonType.OK).showAndWait()
}

def save(): Unit =
if (file != null) synchronized {
Expand All @@ -269,6 +267,23 @@ abstract class EditorTab extends TabFrame {
printer.close()
}

protected def saveExecutable(): Unit = {
val editorText = editor.getText()
val text =
PythonCodeTranslator.translate(editorText) match {
case Some(text) =>
text
case None =>
editorText
}
synchronized {
val writer = new FileWriter(_execFile)
val printer = new PrintWriter(writer)
printer.print(text)
printer.close()
}
}

def setFile(file: java.io.File): Unit = {
this.file = file
if (file != null) {
Expand Down Expand Up @@ -299,5 +314,6 @@ abstract class EditorTab extends TabFrame {
handleError(errorText)
})
EventManager.fireOnStopped()
save()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,23 @@ class PythonPreferencesPane extends PreferencePane {
checkErrors.selectedProperty().bindBidirectional(Preferences.checkSyntax)
strictErrorChecking.setSelected(Preferences.syntaxCheckIsStrict.get)
strictErrorChecking.selectedProperty().bindBidirectional(Preferences.syntaxCheckIsStrict)
strictErrorChecking.disableProperty().bind(checkErrors.selectedProperty().not())
Seq(checkErrors, strictErrorChecking)
}

protected def createExtOptions(): Seq[Node] = {
val repeatLoop = new CheckBox("Repeat loop")
UIString("prefs.jython.repeatloop") += repeatLoop.textProperty()
repeatLoop.setSelected(Preferences.repeatLoop.get)
repeatLoop.selectedProperty().bindBidirectional(Preferences.repeatLoop)
Seq(repeatLoop)
}

override lazy val node: Node = {
val result = new VBox()
result.getChildren.addAll(createInstallationChooser(): _*)
result.getChildren.addAll(createErrorCheckOptions(): _*)
result.getChildren.addAll(createExtOptions(): _*)
new StackPane(result)
}
}
4 changes: 2 additions & 2 deletions src/main/scala/tigerjython/utils/OSProcess.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ class OSProcess(val cmd: String) {
def abort(): Unit =
if (process != null) synchronized {
process.destroy()
_execTime = System.currentTimeMillis() - _execTime
_completed(-1)
//_execTime = System.currentTimeMillis() - _execTime
//_completed(-1)
}

private def _completed(result: Int): Unit = {
Expand Down

0 comments on commit c222c3c

Please sign in to comment.