Lexer
This commit is contained in:
commit
c991893f76
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
bin/
|
||||
obj/
|
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/bin/Debug/net7.0/finn.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
41
.vscode/tasks.json
vendored
Normal file
41
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/finn.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/finn.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/finn.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
345
Program.cs
Normal file
345
Program.cs
Normal file
@ -0,0 +1,345 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Finn;
|
||||
|
||||
class Program
|
||||
{
|
||||
static bool hadError = false;
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
Console.WriteLine("Usage: finn [script]");
|
||||
Environment.Exit(64);
|
||||
}
|
||||
else if (args.Length == 1)
|
||||
{
|
||||
runFile(args[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
runPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
static void runFile(string path)
|
||||
{
|
||||
var src = File.ReadAllText(path);
|
||||
run(src);
|
||||
}
|
||||
|
||||
static void run(string src)
|
||||
{
|
||||
var scanner = new Scanner(src);
|
||||
List<Token> tokens = scanner.scanTokens();
|
||||
|
||||
foreach (Token token in tokens)
|
||||
{
|
||||
Console.WriteLine(token);
|
||||
}
|
||||
}
|
||||
|
||||
static void runPrompt()
|
||||
{
|
||||
var input = new BufferedStream(Console.OpenStandardInput());
|
||||
var reader = new StreamReader(input);
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write("> ");
|
||||
var line = reader.ReadLine();
|
||||
if (line == null) break;
|
||||
run(line);
|
||||
}
|
||||
}
|
||||
|
||||
public static void error(int line, String message)
|
||||
{
|
||||
report(line, "", message);
|
||||
}
|
||||
|
||||
static void report(int line, String where, String message)
|
||||
{
|
||||
Console.WriteLine($"[line {line}] Error{where}: {message}");
|
||||
hadError = true;
|
||||
}
|
||||
}
|
||||
|
||||
enum TokenType
|
||||
{
|
||||
LParen, RParen,
|
||||
LBrace, RBrace,
|
||||
LBracket, RBracket,
|
||||
Less, Greater,
|
||||
Pipe,
|
||||
Colon,
|
||||
Tick,
|
||||
Backtick,
|
||||
Comma,
|
||||
Period,
|
||||
Equal,
|
||||
Plus, Minus, Asterisk, Slash,
|
||||
DoubleEqual,
|
||||
Bang, BangEqual,
|
||||
LessEqual, GreaterEqual,
|
||||
SingleArrow,
|
||||
DoubleArrow,
|
||||
PipeRight,
|
||||
PipeLeft,
|
||||
ComposeRight,
|
||||
ComposeLeft,
|
||||
When,
|
||||
Is,
|
||||
Let,
|
||||
In,
|
||||
With,
|
||||
Fn,
|
||||
Type,
|
||||
Alias,
|
||||
Recurse,
|
||||
Blank,
|
||||
Identifier, Number, String,
|
||||
EOF
|
||||
}
|
||||
|
||||
class Token
|
||||
{
|
||||
public readonly TokenType type;
|
||||
public readonly String lexeme;
|
||||
public readonly Object? literal;
|
||||
public readonly int line;
|
||||
|
||||
public Token(TokenType type, String lexeme, Object? literal, int line)
|
||||
{
|
||||
this.type = type;
|
||||
this.lexeme = lexeme;
|
||||
this.literal = literal;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{type} {lexeme} {literal}";
|
||||
}
|
||||
}
|
||||
|
||||
class Scanner
|
||||
{
|
||||
private readonly String source;
|
||||
private readonly List<Token> tokens = new List<Token>();
|
||||
private int start = 0;
|
||||
private int current = 0;
|
||||
private int line = 1;
|
||||
|
||||
private static readonly Dictionary<String, TokenType> keywords = new Dictionary<string, TokenType>()
|
||||
{
|
||||
{"when", TokenType.When},
|
||||
{"is", TokenType.Is},
|
||||
{"let", TokenType.Let},
|
||||
{"in", TokenType.In},
|
||||
{"with", TokenType.With},
|
||||
{"fn", TokenType.Fn},
|
||||
{"type", TokenType.Type},
|
||||
{"alias", TokenType.Alias},
|
||||
{"recurse", TokenType.Recurse},
|
||||
{"_", TokenType.Blank},
|
||||
};
|
||||
|
||||
public Scanner(String source)
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
private bool isAtEnd()
|
||||
{
|
||||
return current >= source.Length;
|
||||
}
|
||||
|
||||
private char advance()
|
||||
{
|
||||
return source[current++];
|
||||
}
|
||||
|
||||
private void addToken(TokenType type)
|
||||
{
|
||||
addToken(type, null);
|
||||
}
|
||||
|
||||
private void addToken(TokenType type, Object? literal)
|
||||
{
|
||||
String text = source.Substring(start, current - start);
|
||||
tokens.Add(new Token(type, text, literal, line));
|
||||
}
|
||||
|
||||
private bool match(char expected)
|
||||
{
|
||||
if (isAtEnd()) return false;
|
||||
if (source[current] != expected) return false;
|
||||
|
||||
current++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool match(Predicate<char> pred)
|
||||
{
|
||||
if (isAtEnd()) return false;
|
||||
if (!pred(source[current])) return false;
|
||||
|
||||
current++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private char? peek()
|
||||
{
|
||||
if (isAtEnd()) return null;
|
||||
return source[current];
|
||||
}
|
||||
|
||||
private void stringLiteral()
|
||||
{
|
||||
while (peek() != '"' && !isAtEnd())
|
||||
{
|
||||
if (peek() == '\n') line++;
|
||||
advance();
|
||||
}
|
||||
|
||||
if (isAtEnd())
|
||||
{
|
||||
Program.error(line, "Unterminated string.");
|
||||
return;
|
||||
}
|
||||
|
||||
// The closing ".
|
||||
advance();
|
||||
|
||||
// Trim the surrounding quotes.
|
||||
String value = source.Substring(start + 1, current - start - 2);
|
||||
addToken(TokenType.String, value);
|
||||
}
|
||||
|
||||
private bool isDigitOrSeparator(char c)
|
||||
{
|
||||
return Char.IsAsciiDigit(c) || c == '_';
|
||||
}
|
||||
|
||||
private char? peekNext()
|
||||
{
|
||||
if (current + 1 >= source.Length) return null;
|
||||
return source[current + 1];
|
||||
}
|
||||
private void numberLiteral()
|
||||
{
|
||||
while (match(isDigitOrSeparator)) ;
|
||||
|
||||
// Look for a fractional part.
|
||||
if (peek() == '.' && isDigitOrSeparator(peekNext() ?? '\0'))
|
||||
{
|
||||
while (match(isDigitOrSeparator)) ;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isIdentifierStartChar(char c)
|
||||
{
|
||||
return Char.IsAsciiLetter(c) || c == '_';
|
||||
}
|
||||
|
||||
private bool isIdentifierChar(char c)
|
||||
{
|
||||
return isIdentifierStartChar(c) || Char.IsAsciiDigit(c);
|
||||
}
|
||||
|
||||
private void identifier()
|
||||
{
|
||||
while (match(isIdentifierChar)) ;
|
||||
String text = source.Substring(start, current - start);
|
||||
TokenType type;
|
||||
var isKeyword = keywords.TryGetValue(text, out type);
|
||||
addToken(isKeyword ? type : TokenType.Identifier);
|
||||
}
|
||||
|
||||
private void scanToken()
|
||||
{
|
||||
char c = advance();
|
||||
switch (c)
|
||||
{
|
||||
case '(': addToken(TokenType.LParen); break;
|
||||
case ')': addToken(TokenType.RParen); break;
|
||||
case '{': addToken(TokenType.LBrace); break;
|
||||
case '}': addToken(TokenType.RBrace); break;
|
||||
case '[': addToken(TokenType.LBracket); break;
|
||||
case ']': addToken(TokenType.RBracket); break;
|
||||
case ':': addToken(TokenType.Colon); break;
|
||||
case '\'': addToken(TokenType.Tick); break;
|
||||
case '`': addToken(TokenType.Backtick); break;
|
||||
case ',': addToken(TokenType.Comma); break;
|
||||
case '.': addToken(TokenType.Period); break;
|
||||
case '+': addToken(TokenType.Plus); break;
|
||||
case '*': addToken(TokenType.Asterisk); break;
|
||||
case '/': addToken(TokenType.Slash); break;
|
||||
case '-':
|
||||
addToken(match('>') ? TokenType.SingleArrow : TokenType.Minus);
|
||||
break;
|
||||
case '!':
|
||||
addToken(match('=') ? TokenType.BangEqual : TokenType.Bang);
|
||||
break;
|
||||
case '=':
|
||||
addToken(match('=') ? TokenType.DoubleEqual :
|
||||
match('>') ? TokenType.DoubleArrow :
|
||||
TokenType.Equal);
|
||||
break;
|
||||
case '<':
|
||||
addToken(match('=') ? TokenType.LessEqual :
|
||||
match('<') ? TokenType.ComposeLeft :
|
||||
match('|') ? TokenType.PipeLeft :
|
||||
TokenType.Less);
|
||||
break;
|
||||
case '>':
|
||||
addToken(match('=') ? TokenType.GreaterEqual :
|
||||
match('>') ? TokenType.ComposeRight : TokenType.Greater);
|
||||
break;
|
||||
case '|':
|
||||
addToken(match('>') ? TokenType.PipeRight : TokenType.Pipe);
|
||||
break;
|
||||
case '"': stringLiteral(); break;
|
||||
case '#':
|
||||
while (peek() != '\n' && !isAtEnd()) advance();
|
||||
break;
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\t':
|
||||
break;
|
||||
case '\n':
|
||||
line++;
|
||||
break;
|
||||
default:
|
||||
if (Char.IsAsciiDigit(c))
|
||||
{
|
||||
numberLiteral();
|
||||
}
|
||||
else if (isIdentifierStartChar(c))
|
||||
{
|
||||
identifier();
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.error(line, "Unexpected character.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public List<Token> scanTokens()
|
||||
{
|
||||
while (!isAtEnd())
|
||||
{
|
||||
start = current;
|
||||
scanToken();
|
||||
}
|
||||
|
||||
tokens.Add(new Token(TokenType.EOF, "", null, line));
|
||||
return tokens;
|
||||
}
|
||||
}
|
8
finn.csproj
Normal file
8
finn.csproj
Normal file
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RootNamespace>Finn</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue
Block a user