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