diff --git a/.gitignore b/.gitignore index 026ec98..63bcf36 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Ignore Gradle files .gradle **/build/ !src/**/build/ @@ -26,3 +27,22 @@ bin # Ignore Gradle build output directory build + +# Ignore Maven files +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://maven.apache.org/wrapper/#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index f711ee5..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Java application project to get you started. - * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.10.2/userguide/building_java_projects.html in the Gradle documentation. - */ - -plugins { - // Apply the application plugin to add support for building a CLI application in Java. - id 'application' -} - -repositories { - // Use Maven Central for resolving dependencies. - mavenCentral() -} - -dependencies { - // Use JUnit test framework. - testImplementation libs.junit - - // This dependency is used by the application. - implementation libs.guava -} - -// Apply a specific Java toolchain to ease working on different environments. -java { - toolchain { - languageVersion = JavaLanguageVersion.of(23) - } -} - -application { - // Define the main class for the application. - mainClass = 'org.barink.Lox' -} diff --git a/app/src/main/java/org/barink/Scanner.java b/app/src/main/java/org/barink/Scanner.java deleted file mode 100644 index e06f960..0000000 --- a/app/src/main/java/org/barink/Scanner.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.barink; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.barink.TokenType; - -class Scanner { - private final String source; - private final List tokens = new ArrayList<>(); - private int start = 0; - private int current = 0; - private int line = 1; - private static final Map keywords; - - static { - keywords = new HashMap<>(); - keywords.put("and", TokenType.AND); - keywords.put("class", TokenType.CLASS); - keywords.put("else", TokenType.ELSE); - keywords.put("false", TokenType.FALSE); - keywords.put("for", TokenType.FOR); - keywords.put("fun", TokenType.FUN); - keywords.put("if", TokenType.IF); - keywords.put("nil", TokenType.NIL); - keywords.put("or", TokenType.OR); - keywords.put("print", TokenType.PRINT); - keywords.put("return", TokenType.RETURN); - keywords.put("super", TokenType.SUPER); - keywords.put("this", TokenType.THIS); - keywords.put("true", TokenType.TRUE); - keywords.put("var", TokenType.TRUE); - keywords.put("while", TokenType.WHILE); - } - - Scanner(String source){ - this.source = source; - } - - List scanTokens(){ - while (!isAtEnd()){ - // We are at the beginning of the next lexeme - start = current; - scanToken(); - } - tokens.add(new Token(TokenType.EOF, "", null, line)); - return tokens; - } - - private void scanToken () { - char c = advance(); - switch(c){ - case '(': addToken(TokenType.LEFT_PAREN); break; - case ')': addToken(TokenType.RIGHT_PAREN); break; - case '{': addToken(TokenType.LEFT_BRACE); break; - case '}': addToken(TokenType.RIGHT_BRACE); break; - case ',': addToken(TokenType.COMMA); break; - case '.': addToken(TokenType.DOT); break; - case '-': addToken(TokenType.MINUS); break; - case '+': addToken(TokenType.PLUS); break; - case ';': addToken(TokenType.SEMICOLON); break; - case '*': addToken(TokenType.STAR); break; - case '!': - addToken(match('=')? TokenType.BANG_EQUAL : TokenType.BANG); - break; - case '=': - addToken(match('=')? TokenType.EQUAL_EQUAL : TokenType.EQUAL); - break; - case '<': - addToken(match('=')? TokenType.LESS_EQUAL : TokenType.LESS); - break; - case '>': - addToken(match('=')? TokenType.GREATER_EQUAL : TokenType.EQUAL); - break; - case '/': - if (match('/')){ - // A comment goes until the end of the line. - while (peek() != '\n' && !isAtEnd()) advance(); - } else { - addToken(TokenType.SLASH); - } break; - case ' ': - case '\r': - case '\t': - // Ignore whitespace. - break; - case '\n': - line++; - break; - case '"': - string(); - break; - default: - if ( isDigit(c)){ - number(); - }else if (isAlpha(c)) { - identifier(); - } - else { - Lox.error(line, "Unexpected character."); - } - break; - } - - } - - private void identifier(){ - while(isAlphaNumeric(peek())) advance(); - - String text = source.substring(start, current); - TokenType type = keywords.get(text); - if (type == null) type = TokenType.IDENTIFIER; - addToken(type); - } - - private boolean isAlpha(char c){ - return ( c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - c == '_'; - } - - private boolean isAlphaNumeric(char c){ - return isAlpha(c) || isDigit(c); - } - - private boolean isDigit(char c){ - return c >= '0' && c <= '9'; - } - - private void number(){ - while( isDigit(peek())) advance(); - - // Look for a fractional part. - if (peek() == '.' && isDigit(peekNext())){ - // Consume the "." - advance(); - - while (isDigit(peek())) advance(); - - } - - addToken(TokenType.NUMBER, Double.parseDouble(source.substring(start, current))); - } - - private char peekNext(){ - if (current + 1 >= source.length()) return '\0'; - return source.charAt(current + 1); - } - - private void string(){ - while(peek() != '"' && !isAtEnd()){ - if (peek() == '\n') line++; - advance(); - } - - if(isAtEnd()){ - Lox.error(line, "Unterminated string."); - return; - } - - // The closing ". - advance(); - - // Trim the surrounding quotes. - String value = source.substring(start + 1, current - 1); - addToken(TokenType.STRING, value); - } - - private char peek(){ - if (isAtEnd()) return '\0'; - return source.charAt(current); - } - - private boolean match(char expected){ - if(isAtEnd()) return false; - if (source.charAt(current) != expected) return false; - - current ++ ; - return true; - } - private char advance(){ - return source.charAt(current++); - } - private void addToken(TokenType type) - { - addToken(type, null); - } - private void addToken(TokenType type, Object literal) - { - String text = source.substring(start, current); - tokens.add(new Token(type, text, literal, line )); - } - private boolean isAtEnd(){ - return current >= source.length(); - } -} diff --git a/app/src/main/java/org/barink/TokenType.java b/app/src/main/java/org/barink/TokenType.java deleted file mode 100644 index 1e8cbb7..0000000 --- a/app/src/main/java/org/barink/TokenType.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.barink; - -enum TokenType { - // Single-character tokens. - LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, - COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, - - // One or two character tokens. - BANG, BANG_EQUAL, - EQUAL, EQUAL_EQUAL, - GREATER, GREATER_EQUAL, - LESS, LESS_EQUAL, - - // Literals - IDENTIFIER, STRING, NUMBER, - - // Keywords - AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, - PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, - - EOF -} - - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index e74f385..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,10 +0,0 @@ -# This file was generated by the Gradle 'init' task. -# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format - -[versions] -guava = "33.2.1-jre" -junit = "4.13.2" - -[libraries] -guava = { module = "com.google.guava:guava", version.ref = "guava" } -junit = { module = "junit:junit", version.ref = "junit" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index df97d72..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100644 index f5feea6..0000000 --- a/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 9d21a21..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a9329dd --- /dev/null +++ b/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + dev.barink + lox + 1.0 + + + 24 + 24 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + app.Lox + + + + + + + + + diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index bca5f6e..0000000 --- a/settings.gradle +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.10.2/userguide/multi_project_builds.html in the Gradle documentation. - */ - -plugins { - // Apply the foojay-resolver plugin to allow automatic download of JDKs - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' -} - -rootProject.name = 'lox' -include('app') diff --git a/src/main/java/app/Expr.java b/src/main/java/app/Expr.java new file mode 100644 index 0000000..6e6b6c2 --- /dev/null +++ b/src/main/java/app/Expr.java @@ -0,0 +1,16 @@ +package app; + +abstract class Expr { + static class Binary extends Expr { + Binary(Expr left, Token operator, Expr right) { + this.left = left; + this.operator = operator; + this.right = right; + } + + final Expr left; + final Token operator; + final Expr right; + } + // Other expressions +} diff --git a/app/src/main/java/org/barink/Lox.java b/src/main/java/app/Lox.java similarity index 61% rename from app/src/main/java/org/barink/Lox.java rename to src/main/java/app/Lox.java index 652b6fd..4aa96fc 100644 --- a/app/src/main/java/org/barink/Lox.java +++ b/src/main/java/app/Lox.java @@ -1,67 +1,71 @@ -/* - * This Java source file was generated by the Gradle 'init' task. - */ -package org.barink; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.List; - - -public class Lox { - static boolean hadError = false; - - public static void main(String[] args) throws IOException { - if (args.length > 1){ - System.out.println("Usage: jlox [script]"); - System.exit(64); - } else if (args.length == 1) { - runFile(args[0]); - }else{ - runPrompt(); - } - } - - private static void runFile(String path) throws IOException{ - byte[] bytes = Files.readAllBytes(Paths.get(path)); - run(new String(bytes, Charset.defaultCharset())); - if (hadError) System.exit(65); - } - - private static void runPrompt() throws IOException{ - InputStreamReader input = new InputStreamReader(System.in); - BufferedReader reader = new BufferedReader(input); - - for(;;){ - System.out.println("> "); - String line = reader.readLine(); - if(line == null ) break; - run(line); - hadError = false; - } - } - - private static void run(String source){ - Scanner scanner = new Scanner(source); - List tokens = scanner.scanTokens(); - - // For now, just print the tokens - for (Token token : tokens){ - System.out.println(token); - } - } - - static void error(int line, String message){ - report(line, "", message); - } - - private static void report(int line, String where, String message){ - hadError = true; - } - - -} +package app; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class Lox { + static boolean hadError = false; + + public static void main(String[] args) throws IOException { + if (args.length > 1) { + System.out.println("Usage: jlox [script]"); + System.exit(64); + } else if (args.length == 1) { + runFile(args[0]); + } else { + try { + runPrompt(); + } catch (IOException exception) { + System.out.println(exception.getMessage()); + } + } + } + + private static void runFile(String path) throws IOException { + byte[] bytes = Files.readAllBytes(Paths.get(path)); + run(new String(bytes, Charset.defaultCharset())); + if (hadError) { + System.out.println("had Error!"); + System.exit(65); + } + } + + private static void runPrompt() throws IOException { + InputStreamReader input = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(input); + + for (;;) { + System.out.println("> "); + String line = reader.readLine(); + if (line == null) + break; + run(line); + hadError = false; + } + } + + private static void run(String source) { + System.out.println("Starting scan of source"); + + Scanner scanner = new Scanner(source); + List tokens = scanner.scanTokens(); + + // For now, just print the tokens + for (Token token : tokens) { + System.out.println(token); + } + } + + static void error(int line, String message) { + report(line, "", message); + } + + private static void report(int line, String where, String message) { + hadError = true; + } +} diff --git a/src/main/java/app/Scanner.java b/src/main/java/app/Scanner.java new file mode 100644 index 0000000..04b007b --- /dev/null +++ b/src/main/java/app/Scanner.java @@ -0,0 +1,228 @@ +package app; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class Scanner { + private final String source; + private final List tokens = new ArrayList<>(); + private int start = 0; + private int current = 0; + private int line = 1; + private static final Map keywords; + + static { + keywords = new HashMap<>(); + keywords.put("and", TokenType.AND); + keywords.put("class", TokenType.CLASS); + keywords.put("else", TokenType.ELSE); + keywords.put("false", TokenType.FALSE); + keywords.put("for", TokenType.FOR); + keywords.put("fun", TokenType.FUN); + keywords.put("if", TokenType.IF); + keywords.put("nil", TokenType.NIL); + keywords.put("or", TokenType.OR); + keywords.put("print", TokenType.PRINT); + keywords.put("return", TokenType.RETURN); + keywords.put("super", TokenType.SUPER); + keywords.put("this", TokenType.THIS); + keywords.put("true", TokenType.TRUE); + keywords.put("var", TokenType.TRUE); + keywords.put("while", TokenType.WHILE); + } + + Scanner(String source) { + this.source = source; + } + + List scanTokens() { + while (!isAtEnd()) { + // We are at the beginning of the next lexeme + start = current; + scanToken(); + } + tokens.add(new Token(TokenType.EOF, "", null, line)); + return tokens; + } + + private void scanToken() { + char c = advance(); + switch (c) { + case '(': + addToken(TokenType.LEFT_PAREN); + break; + case ')': + addToken(TokenType.RIGHT_PAREN); + break; + case '{': + addToken(TokenType.LEFT_BRACE); + break; + case '}': + addToken(TokenType.RIGHT_BRACE); + break; + case ',': + addToken(TokenType.COMMA); + break; + case '.': + addToken(TokenType.DOT); + break; + case '-': + addToken(TokenType.MINUS); + break; + case '+': + addToken(TokenType.PLUS); + break; + case ';': + addToken(TokenType.SEMICOLON); + break; + case '*': + addToken(TokenType.STAR); + break; + case '!': + addToken(match('=') ? TokenType.BANG_EQUAL : TokenType.BANG); + break; + case '=': + addToken(match('=') ? TokenType.EQUAL_EQUAL : TokenType.EQUAL); + break; + case '<': + addToken(match('=') ? TokenType.LESS_EQUAL : TokenType.LESS); + break; + case '>': + addToken(match('=') ? TokenType.GREATER_EQUAL : TokenType.EQUAL); + break; + case '/': + if (match('/')) { + // A comment goes until the end of the line. + while (peek() != '\n' && !isAtEnd()) + advance(); + } else { + addToken(TokenType.SLASH); + } + break; + case ' ': + case '\r': + case '\t': + // Ignore whitespace. + break; + case '\n': + line++; + break; + case '"': + string(); + break; + default: + if (isDigit(c)) { + number(); + } else if (isAlpha(c)) { + identifier(); + } else { + Lox.error(line, "Unexpected character."); + } + break; + } + + } + + private void identifier() { + while (isAlphaNumeric(peek())) + advance(); + + String text = source.substring(start, current); + TokenType type = keywords.get(text); + if (type == null) + type = TokenType.IDENTIFIER; + addToken(type); + } + + private boolean isAlpha(char c) { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_'; + } + + private boolean isAlphaNumeric(char c) { + return isAlpha(c) || isDigit(c); + } + + private boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + private void number() { + while (isDigit(peek())) + advance(); + + // Look for a fractional part. + if (peek() == '.' && isDigit(peekNext())) { + // Consume the "." + advance(); + + while (isDigit(peek())) + advance(); + + } + + addToken(TokenType.NUMBER, Double.parseDouble(source.substring(start, current))); + } + + private char peekNext() { + if (current + 1 >= source.length()) + return '\0'; + return source.charAt(current + 1); + } + + private void string() { + while (peek() != '"' && !isAtEnd()) { + if (peek() == '\n') + line++; + advance(); + } + + if (isAtEnd()) { + Lox.error(line, "Unterminated string."); + return; + } + + // The closing ". + advance(); + + // Trim the surrounding quotes. + String value = source.substring(start + 1, current - 1); + addToken(TokenType.STRING, value); + } + + private char peek() { + if (isAtEnd()) + return '\0'; + return source.charAt(current); + } + + private boolean match(char expected) { + if (isAtEnd()) + return false; + if (source.charAt(current) != expected) + return false; + + current++; + return true; + } + + private char advance() { + return source.charAt(current++); + } + + private void addToken(TokenType type) { + addToken(type, null); + } + + private void addToken(TokenType type, Object literal) { + String text = source.substring(start, current); + tokens.add(new Token(type, text, literal, line)); + } + + private boolean isAtEnd() { + return current >= source.length(); + } +} diff --git a/app/src/main/java/org/barink/Token.java b/src/main/java/app/Token.java similarity index 83% rename from app/src/main/java/org/barink/Token.java rename to src/main/java/app/Token.java index f1cfb94..f31653d 100644 --- a/app/src/main/java/org/barink/Token.java +++ b/src/main/java/app/Token.java @@ -1,19 +1,19 @@ -package org.barink; - -class Token { - final TokenType type; - final String lexeme; - final Object literal; - final int line; - - Token(TokenType type, String lexeme, Object literal, int line){ - this.type = type; - this.lexeme = lexeme; - this.literal = literal; - this.line = line; - } - - public String toString(){ - return type + " " + lexeme + " " + literal; - } -} +package app; + +class Token { + final TokenType type; + final String lexeme; + final Object literal; + final int line; + + Token(TokenType type, String lexeme, Object literal, int line) { + this.type = type; + this.lexeme = lexeme; + this.literal = literal; + this.line = line; + } + + public String toString() { + return type + " " + lexeme + " " + literal; + } +} diff --git a/src/main/java/app/TokenType.java b/src/main/java/app/TokenType.java new file mode 100644 index 0000000..bff9b12 --- /dev/null +++ b/src/main/java/app/TokenType.java @@ -0,0 +1,22 @@ +package app; + +enum TokenType { + // Single-character tokens. + LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, + COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, + + // One or two character tokens. + BANG, BANG_EQUAL, + EQUAL, EQUAL_EQUAL, + GREATER, GREATER_EQUAL, + LESS, LESS_EQUAL, + + // Literals + IDENTIFIER, STRING, NUMBER, + + // Keywords + AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, + PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, + + EOF +}