This is the one announced in the EP exercise of the FMS department of Meiji University on September 17, 2020. Well, I don't care about that, so please take a look. I put the source code on github.
The program itself is implemented using a language called Processing, which seems to be a cover of java. The execution screen consists of a text editor, console, and preview screen.
Basically, it can be written in the same syntax as java. There are some parts that are different from java, so I will explain a little.
The variable declaration has the following syntax.
let a = 10;
println(a); // -> 10
println(typeof(a)); // -> int
let b: float = 10;
println(typeof(b)); // -> float
Currently available built-in types
int
float
bool
string
4 types and their arrangement. If you feel like it, I want to increase it soon. The array declaration can be written as:
let arr1 = {0, 1, 2};
let arr2 = {{0, 1}, {1, 2}};
let arr3 = int[2][2]; // make {{0, 0}, {0, 0}}
Also, there is Rust in a language with a similar grammar, but in the case of Rust, if you declare it with let
, it becomes a constant, and if you add mut
, it becomes a variable, whereas in this language, let
The variable is declared with. I haven't made a constant declaration yet, but I'm thinking of making it a const
statement like JavaScript.
You can also write the following.
let c, d = 10, 20;
println(c, d); // -> 10 20
c, d = d, c; // swap
println(c, d); // -> 20 10
The function can be defined with the following syntax.
fn add(a: int, b: int) -> int {
return a + b;
}
Since it is made by a lot of rush work, there are many built-in functions that can not be supported compared to other languages, and there are a lot of problems such as not being able to support object orientation in the first place. please forgive me...
Next, let's see how this program works.
Creates a window (corresponding screen) in the preview screen. This can be done in a similar way to Processing.
In this example, the screen with the ratio corresponding to the 800x600 window is displayed on the preview screen.
You can also specify the background color using the background ()
function, just like Processing.
To print the string to the console, use the println ()
function.
If you don't want to print a newline at the end of the line, you can also use the print ()
function.
The ability to enter into the console has not yet been implemented. sorry.
Similar to Processing, you can write frame-by-frame processing by defining the draw ()
function.
The following example implements the behavior of gradually changing the background color from black to white.
Implemented the function to call when the mouse is clicked if the mouseX
, mouseY
and the mousePressed ()
function that indicate the mouse coordinates are defined. So, you can write the following processing that inverts the clicked part and the squares on the top, bottom, left, and right.
The code is cut off, so I'll put it separately.
size(500, 500);
let board = int[5][5];
let count = 0;
board[0][2] = 1;
board[1][1] = 1;
board[1][2] = 1;
board[2][3] = 1;
board[2][4] = 1;
board[3][3] = 1;
fn draw() -> void {
background(0);
fill(255, 255, 0);
count = 0;
for(let i = 0; i < 5; i++) {
for(let j = 0; j < 5; j++) {
if(board[i][j] == 1) {
rect(100 * i, 100 * j, 100, 100);
}
else {
count++;
}
}
}
if(count == 25) {
textSize(32);
textAlign(CENTER);
text("CLEAR!", width / 2, height / 2);
}
}
fn mousePressed() -> void {
let x = mouseX / 100;
let y = mouseY / 100;
for(let i = max(0, x - 1); i <= min(4, x + 1); i++) {
board[i][y] = 1 - board[i][y];
}
for(let i = max(0, y - 1); i <= min(4, y + 1); i++) {
board[x][i] = 1 - board[x][i];
}
board[x][y] = 1 - board[x][y];
}
In the above program, some functions that you are familiar with in Processing have appeared. Some of the functions available in Processing are implemented for use in this homebrew language as well. Currently available built-in functions are all of the following, with or without Processing.
size()
background()
fill()
stroke()
strokeWeight()
textSize()
textAlign()
text()
circle()
rect()
ellipse()
println()
print()
typeof ()
(check variable type)millis()
wait ()
(wait (ms) => ms stop processing for milliseconds)random()
min ()
(returns the smallest value of multiple arguments)max ()
(returns the maximum value of multiple arguments)range ()
(returns an array of integer values in the specified range)Now let's move on to the detailed implementation inside. From this point on, it may be a little difficult for those who have insufficient knowledge of Processing or who do not have much programming experience.
It is implemented by making full use of keyPressed
, keyPressed ()
function, keyTyped ()
function, etc.
First, let's take a look at the following code inside the draw ()
function.
if(keyPressed) {
if(prevKey.equals(String.valueOf(key))) {
if(keyt < dur) keyt++;
else {
keyt = dur / 2;
if(key != CODED) keyTyped();
}
}
else {
keyt = 0;
if(key != CODED) keyTyped();
}
}
else {
keyt = 0;
prevKey = "";
}
prevKey
is a variable that holds the previously entered key. keyt
represents how many frames have passed since the key was entered. dur
is the value set as the threshold for keyt
.
If keyt
exceeds the threshold dur
while the same key as last time is entered, substitute dur / 2
for keyt
and key
shift You are calling the keyTyped ()
function when it is not an encoded key such as or Control. If a key different from the previous one is entered, keyt
is not checked and 0
is assigned to keyt
.
Also, the keyPressed ()
function has the following implementation.
void keyPressed() {
if(key == CODED) {
if(keyCode == LEFT) {
cursor.prev();
beforeCursor.prev();
}
else if(keyCode == RIGHT) {
cursor.next();
beforeCursor.next();
}
else if(keyCode == UP) {
cursor.up();
beforeCursor.up();
}
else if(keyCode == DOWN) {
cursor.down();
beforeCursor.down();
}
else {
keyFlags.put(keyCode, true);
}
}
}
cursor
, beforeCursor
is a Cursor
type object and has methods line
, row
to represent rows and columns. cursor
represents the current cursor position. Since beforeCursor
is used to implement range selection, I will explain it later. Also, keyFlags
is a HashMap <Integer, Boolean>
type object, and indicates whether keyCode
indicating an encoded key other than the cross key is entered.
In this keyPressed ()
function, the cursor is moved when the cross key is pressed, and keyFlags
is changed when other encoded keys are input. .. Also, in order to change keyFlags
when the key whose keyFlags
is true
is released, I defined the keyReleased ()
function as follows.
void keyReleased() {
if(key == CODED) {
if(keyFlags.get(keyCode) != null) {
keyFlags.put(keyCode, false);
}
}
keyPressed = false;
}
The process of changing keyPressed
to false
should not be necessary, but if you do not write it for some reason, the behavior of keyPressed
will be strange, so I will write it.
The keyTyped ()
function describes the behavior corresponding to each key entered. If you write it here, it will be long, so if you want to see it, you may want to check it from Source code link.
The entered string is stored in a ʻArrayList <String>
`type object line by line. It just displays them in order.
When the number of lines exceeds the display range of the editor, the scroll bar is displayed on the right side of the editor. The height at which the character string is output changes according to the position of the scroll bar.
In order to call yourself an editor, if you can just enter characters, it is no different from Notepad, so you need some functions. The following are the functions of the editor currently implemented.
(
for )
and }
for {
The implementation of range selection is achieved by having one of the beforeCursor
and cursor
as the beginning of the range and the other as the end of the range. The rest is messed up in the keyTyped ()
function, so if you're interested, take a look.
The console also stores strings in the ʻArrayList <String>
`type object. The display is the same as the editor.
The language implemented this time is a simple dynamically typed interprinter language. However, I don't like dynamically typed languages, so the code looks like a statically typed language doing type inference. The flow of processing is a little special, and the original lexical analysis and parsing are performed, and the abstract syntax tree is interpreted and executed, but the one implemented this time is a crude implementation that omits various things. It is. For example, consider running the following code.
let a = 10;
At this time, the Lexer
class will generate the following calculation result.
[
Token("let", "let"),
Token("id", "a"),
Token("expr", "expr"),
Token("int", "10"),
Token("endExpr", "endExpr"),
Token("endLet", "endLet")
]
This is an object of type ʻArrayList <Token>
`. An object of type Token
has two fields, kind
and value
. Token (a, b)
is written as Token
whose value is kind
and value
is b
. Think of it as representing a type object.
Then, this array is interpreted as an instruction sequence and executed. Execution of the let
statement is done by the following code.
.
.
.
else if(res.get(i).kind.equals("let")) {
ArrayList<String> vars = new ArrayList<String>();
ArrayList<String> values = new ArrayList<String>();
i++;
String type = "", exprType = "";
if(res.get(i).kind.equals("type")) {
type = res.get(i).value;
i++;
}
while(!res.get(i).kind.equals("endLet")) {
if(res.get(i).kind.equals("id")) {
vars.add(res.get(i).value);
varNames.add(loc + "$" + res.get(i).value);
}
else if(res.get(i).kind.equals("expr")) {
ArrayList<Token> expr = new ArrayList<Token>();
i++;
int exprNum = 0;
while(exprNum > 0 || !res.get(i).kind.equals("endExpr")) {
if(res.get(i).kind.equals("expr")) exprNum++;
if(res.get(i).kind.equals("endExpr")) exprNum--;
expr.add(res.get(i));
i++;
}
exprType = calc(expr, loc);
values.add(expr.get(0).value);
}
i++;
}
int vs = values.size();
for(int j = 0; j < vars.size(); j++) {
if(j < vs) {
if(type.isEmpty()) {
variables.put(loc + "$" + vars.get(j), new Variable(exprType, vars.get(j), values.get(j)));
}
else {
variables.put(loc + "$" + vars.get(j), new Variable(type, vars.get(j), values.get(j)));
}
}
else {
if(type.isEmpty()) {
variables.put(loc + "$" + vars.get(j), new Variable(exprType, vars.get(j), values.get(vs - 1)));
}
else {
variables.put(loc + "$" + vars.get(j), new Variable(type, vars.get(j), values.get(vs - 1)));
}
}
}
}
res
is a variable of type ʻArrayList <Token> `` that receives the calculation result of `` Lexer``. `` Variables`` is a variable of type `` HashMap <String, Variable> `` that stores variables. `` varNames`` is a variable of type
ʻArrayList that stores the names of variables with scope in mind. Variable scope management uses
$ . Specifically, the scoped name of the variable
ain the global area is
main $ a, and the variable defined in the function
func () in the global area. The scoped name of ```a
is main $ func $ a
.
Since ʻexpr`` to
ʻendExprrepresent an expression, pass the
Token column from `ʻexpr
to` ʻendExprto the
calc () function. By doing so, you can calculate the formula. In this way you can execute the
let`` statement.
For other statements, the Token
column is generated and executed in the same way, so please refer to the implementation of the statement ()
function for the detailed implementation of each.
There are many other parts that can explain the implementation, but freshness is the life of the story, so I would like to end the explanation here because I want to post an article on the day when this work is announced. If you have any requests, questions, suggestions, suggestions, taunts, ridicule, etc., such as wanting to add a commentary, please comment on this article or reply directly to My Twitter account. Or send a DM.
We deeply apologize for the miscellaneous implementation, miscellaneous commentary, and miscellaneous endings. sorry.
If you would like to hear a more detailed and polite explanation of the implementation of the language processing system, please see this article.