Lexer
This commit is contained in:
		
							
								
								
									
										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>
 | 
			
		||||
		Reference in New Issue
	
	Block a user