2023-07-06 07:11:00 +00:00
using System ;
2023-09-28 00:16:19 +00:00
using System.Linq ;
2023-07-06 08:29:04 +00:00
using System.Collections.Generic ;
2023-07-06 07:11:00 +00:00
using System.Collections.Immutable ;
using AST = Finn . AST ;
2023-09-28 00:16:19 +00:00
using System.Runtime.CompilerServices ;
using System.Net ;
2023-09-28 04:34:38 +00:00
using Finn.AST ;
2023-07-06 07:11:00 +00:00
namespace Finn ;
2023-07-11 06:51:51 +00:00
public class RuntimeError : Exception
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
public readonly Token Token ;
internal RuntimeError ( Token token , String message ) : base ( message )
{
Token = token ;
}
}
public class Env
{
private readonly Env ? enclosing ;
private readonly Dictionary < string , object > values = new Dictionary < string , object > ( ) ;
public Env ( )
{
2023-09-28 00:16:19 +00:00
enclosing = null ;
2023-07-11 06:51:51 +00:00
}
public Env ( Env enclosing )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
this . enclosing = enclosing ;
}
2023-07-06 07:11:00 +00:00
2023-09-28 00:16:19 +00:00
public object this [ string name ]
2023-07-11 06:51:51 +00:00
{
set
{
2023-09-28 00:16:19 +00:00
// Redefinition within the same scope should never happen, but front
// end should catch it.
2023-07-17 05:30:20 +00:00
values [ name ] = value ;
2023-07-11 06:51:51 +00:00
}
2023-09-28 00:16:19 +00:00
}
public object this [ Token identifier ]
{
2023-07-11 06:51:51 +00:00
get
2023-07-06 07:11:00 +00:00
{
2023-07-25 19:19:07 +00:00
var name = ( string ) identifier . Literal ! ;
2023-07-11 06:51:51 +00:00
try
{
2023-07-17 05:30:20 +00:00
return values [ name ] ;
2023-07-11 06:51:51 +00:00
}
catch
{
if ( enclosing ! = null )
{
2023-07-17 05:30:20 +00:00
return enclosing [ identifier ] ;
2023-07-11 06:51:51 +00:00
}
2023-07-17 05:30:20 +00:00
throw new RuntimeError ( identifier , $"Undefined variable {name}." ) ;
2023-07-11 06:51:51 +00:00
}
2023-07-06 07:11:00 +00:00
}
}
2023-10-04 23:13:48 +00:00
public Env Ancestor ( int distance )
{
Env ? curr = this ;
for ( int i = 0 ; i < distance ; i + + )
{
curr = curr . enclosing ;
if ( curr is null )
{
throw new IndexOutOfRangeException ( ) ;
}
}
return curr ;
}
2023-07-11 06:51:51 +00:00
}
2023-07-06 07:11:00 +00:00
2023-09-28 00:16:19 +00:00
public interface Callable
{
object Call ( Interpreter interpreter , object [ ] arguments ) ;
}
public record NativeFunction ( Func < object [ ] , object > Function ) : Callable
{
public object Call ( Interpreter interpreter , object [ ] arguments )
{
return this . Function ( arguments ) ;
}
public override string ToString ( ) = > "<native func>" ;
}
2023-07-11 06:51:51 +00:00
public class Interpreter : AST . IExprVisitor < Env , object >
{
2023-08-06 23:07:00 +00:00
private class PatternMismatchException : Exception
2023-07-11 18:59:15 +00:00
{
2023-08-06 23:07:00 +00:00
public readonly Token Start ;
public PatternMismatchException ( Token start , string message ) : base ( message )
{
Start = start ;
}
2023-07-11 18:59:15 +00:00
}
2023-08-06 23:07:00 +00:00
private class VariantTagMismatchException : PatternMismatchException
{
public VariantTagMismatchException ( Token start , string patternTag , string valueTag ) : base ( start , $"Pattern tag {patternTag} does not match value tag {valueTag}." ) { }
}
2023-07-11 18:59:15 +00:00
class PatternBinder : AST . IPatternVisitor < ( object , Env ) , ValueTuple >
{
public ValueTuple visitFieldPatternPattern ( ( object , Env ) context , AST . FieldPattern pattern )
{
2023-08-06 21:44:15 +00:00
return ( pattern . Pattern ? ? new AST . SimplePattern ( pattern . Name , pattern . Name ) ) . accept ( context , this ) ;
2023-07-11 18:59:15 +00:00
}
public ValueTuple visitRecordPatternPattern ( ( object , Env ) context , AST . RecordPattern pattern )
{
var ( obj , env ) = context ;
switch ( obj )
{
case Record r :
var removedLabels = new List < string > ( ) ;
foreach ( var field in pattern . Fields )
{
2023-07-25 19:19:07 +00:00
string name = ( string ) field . Name . Literal ! ;
2023-07-17 05:30:20 +00:00
removedLabels . Add ( name ) ;
var fieldValue = r . Get ( name ) ;
2023-07-11 18:59:15 +00:00
field . accept ( ( fieldValue , env ) , this ) ;
}
if ( pattern . Rest ! = null )
{
var rest = r . Without ( removedLabels ) ;
pattern . Rest . accept ( ( rest , env ) , this ) ;
}
return ValueTuple . Create ( ) ;
}
2023-08-06 23:07:00 +00:00
throw new PatternMismatchException ( pattern . Start , "Matched value {obj} is not a record." ) ;
2023-07-11 18:59:15 +00:00
}
public ValueTuple visitSimplePatternPattern ( ( object , Env ) context , AST . SimplePattern pattern )
{
var ( obj , env ) = context ;
if ( pattern . Identifier = = null )
{
return ValueTuple . Create ( ) ;
}
2023-09-28 00:16:19 +00:00
env [ ( string ) pattern . Identifier . Literal ! ] = obj ;
2023-07-11 18:59:15 +00:00
return ValueTuple . Create ( ) ;
}
public ValueTuple visitVariantPatternPattern ( ( object , Env ) context , AST . VariantPattern pattern )
{
var ( obj , env ) = context ;
switch ( obj )
{
case Variant v :
2023-07-25 19:19:07 +00:00
var tag = ( string ) pattern . Tag . Literal ! ;
2023-07-17 05:30:20 +00:00
if ( v . Tag ! = tag )
2023-07-11 18:59:15 +00:00
{
2023-08-06 23:07:00 +00:00
throw new VariantTagMismatchException ( pattern . Start , tag , v . Tag ) ;
2023-07-11 18:59:15 +00:00
}
if ( v . Value = = null & & pattern . Argument = = null )
{
return ValueTuple . Create ( ) ;
}
if ( v . Value ! = null & & pattern . Argument ! = null )
{
return pattern . Argument . accept ( ( v . Value , env ) , this ) ;
}
2023-08-06 23:07:00 +00:00
throw new PatternMismatchException ( pattern . Start , "Variant pattern arity does not match variant value." ) ;
2023-07-11 18:59:15 +00:00
}
2023-08-06 23:07:00 +00:00
throw new PatternMismatchException ( pattern . Start , $"Matched value {obj} is not a variant." ) ;
2023-07-11 18:59:15 +00:00
}
}
2023-07-06 08:29:04 +00:00
private class List
{
public required ImmutableList < object > Items { get ; init ; }
public static List Empty = new List { Items = ImmutableList < object > . Empty } ;
public object this [ double i ]
{
get { return Items [ ( int ) i ] ; }
}
public bool IsEmpty
{
get { return Items . IsEmpty ; }
}
public List Add ( object item )
{
return new List { Items = Items . Add ( item ) } ;
}
public override string ToString ( )
{
System . IO . StringWriter sw = new System . IO . StringWriter ( ) ;
sw . Write ( "[" ) ;
foreach ( var item in this . Items )
{
sw . Write ( $" {item}," ) ;
}
sw . Write ( " ]" ) ;
return sw . ToString ( ) ;
}
}
2023-07-06 07:11:00 +00:00
private class Record
{
public static Record Empty = new Record
{
2023-07-06 08:29:04 +00:00
Fields = ImmutableSortedDictionary < string , ImmutableStack < object > > . Empty ,
2023-07-06 07:11:00 +00:00
} ;
2023-07-06 08:29:04 +00:00
public required ImmutableSortedDictionary < string , ImmutableStack < object > > Fields { get ; init ; }
2023-07-06 07:11:00 +00:00
public Record Update ( string name , object value )
{
ImmutableStack < object > ? values ;
if ( ! Fields . TryGetValue ( name , out values ) )
{
throw new ArgumentException ( $"no such field: {name}" ) ;
}
return new Record { Fields = Fields . SetItem ( name , values ! . Pop ( ) . Push ( value ) ) } ;
}
public object Get ( string name )
{
ImmutableStack < object > ? values ;
if ( ! Fields . TryGetValue ( name , out values ) )
{
throw new ArgumentException ( $"no such field: {name}" ) ;
}
return values . Peek ( ) ;
}
2023-07-11 18:59:15 +00:00
public Record Without ( IEnumerable < string > labels )
{
return new Record { Fields = Fields . RemoveRange ( labels ) } ;
}
2023-07-06 07:11:00 +00:00
public Record Remove ( string name )
{
ImmutableStack < object > ? values ;
if ( ! Fields . TryGetValue ( name , out values ) )
{
throw new ArgumentException ( $"no such field: {name}" ) ;
}
var value = values . Peek ( ) ;
var popped = values . Pop ( ) ;
if ( popped . IsEmpty )
{
return new Record { Fields = Fields . Remove ( name ) } ;
}
return new Record { Fields = Fields . SetItem ( name , popped ) } ;
}
public Record Extend ( string name , object value )
{
var values = Fields . GetValueOrDefault ( name , ImmutableStack < object > . Empty ) ;
return new Record { Fields = Fields . SetItem ( name , values . Push ( value ) ) } ;
}
2023-07-06 08:29:04 +00:00
public override string ToString ( )
{
System . IO . StringWriter sw = new System . IO . StringWriter ( ) ;
sw . Write ( "{" ) ;
foreach ( ( var label , var values ) in this . Fields )
{
foreach ( var value in values )
{
sw . Write ( $" {label} = {value}," ) ;
}
}
sw . Write ( " }" ) ;
return sw . ToString ( ) ;
}
2023-07-06 07:11:00 +00:00
}
private record Variant ( string Tag , object? Value )
{
public static readonly Variant True = new Variant ( "true" , null ) ;
public static readonly Variant False = new Variant ( "false" , null ) ;
public static Variant FromBool ( bool b )
{
return b ? True : False ;
}
public bool IsEmpty { get { return Value = = null ; } }
public override string ToString ( )
{
if ( Value = = null )
{
return $"`{Tag}" ;
}
else
{
return $"`{Tag}({Value})" ;
}
}
}
2023-09-28 04:34:38 +00:00
public record FinnFunction ( FuncBinding binding , Env closure ) : Callable
{
public object Call ( Interpreter interpreter , object [ ] arguments )
{
Env env = new Env ( closure ) ;
for ( int i = 0 ; i < binding . Params . Length ; i + + )
{
binding . Params [ i ] . accept ( ( arguments [ i ] , env ) , new PatternBinder ( ) ) ;
}
return interpreter . evaluate ( env , binding . Value ) ;
}
}
protected internal readonly Env Globals = new Env ( ) ;
2023-10-04 23:13:48 +00:00
private readonly Resolver Resolver ;
2023-09-28 00:16:19 +00:00
2023-10-04 23:13:48 +00:00
public Interpreter ( Resolver resolver )
2023-09-28 00:16:19 +00:00
{
2023-10-04 23:13:48 +00:00
Resolver = resolver ;
2023-09-28 04:34:38 +00:00
Globals [ "clock" ] = new NativeFunction ( ( args ) = >
2023-09-28 00:16:19 +00:00
{
return ( double ) DateTimeOffset . Now . ToUnixTimeSeconds ( ) ;
} ) ;
}
2023-07-06 07:11:00 +00:00
public void Interpret ( AST . Expr expression )
{
try
{
2023-09-28 04:34:38 +00:00
var value = evaluate ( Globals , expression ) ;
2023-07-06 07:11:00 +00:00
Console . WriteLine ( value ) ;
}
catch ( RuntimeError err )
{
Program . runtimeError ( err ) ;
}
}
2023-07-11 06:51:51 +00:00
private object evaluate ( Env env , AST . Expr expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
return expr . accept ( env , this ) ;
2023-07-06 07:11:00 +00:00
}
2023-07-06 08:29:04 +00:00
private List checkListOperand ( Token op , Object operand )
{
if ( operand is List l ) return l ;
throw new RuntimeError ( op , "Operand must be a record." ) ;
}
private Record checkRecordOperand ( Token op , Object operand )
{
if ( operand is Record r ) return r ;
throw new RuntimeError ( op , "Operand must be a record." ) ;
}
private double checkNumberOperand ( Token op , Object operand )
2023-07-06 07:11:00 +00:00
{
2023-07-06 08:29:04 +00:00
if ( operand is double d ) return d ;
2023-07-06 07:11:00 +00:00
throw new RuntimeError ( op , "Operand must be a number." ) ;
}
private void checkNumberOperands ( Token op , object left , object right )
{
if ( left is double & & right is double ) return ;
throw new RuntimeError ( op , "Operands must be numbers." ) ;
}
private void checkStringOperands ( Token op , object left , object right )
{
if ( left is string & & right is string ) return ;
throw new RuntimeError ( op , "Operands must be strings." ) ;
}
2023-07-06 08:29:04 +00:00
private Variant checkBoolOperand ( Token op , object operand )
{
if ( operand is Variant v )
{
if ( v = = Variant . True | | v = = Variant . False ) return v ;
}
throw new RuntimeError ( op , "Operand must be <true,false>." ) ;
}
2023-07-11 06:51:51 +00:00
public object visitBinaryExpr ( Env env , AST . Binary expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
var left = evaluate ( env , expr . Left ) ;
var right = evaluate ( env , expr . Right ) ;
2023-07-06 07:11:00 +00:00
2023-07-25 19:19:07 +00:00
switch ( expr . Op . Type )
2023-07-06 07:11:00 +00:00
{
case TokenType . Minus :
checkNumberOperands ( expr . Op , left , right ) ;
return ( double ) left - ( double ) right ;
case TokenType . Plus :
checkNumberOperands ( expr . Op , left , right ) ;
return ( double ) left + ( double ) right ;
case TokenType . Slash :
checkNumberOperands ( expr . Op , left , right ) ;
return ( double ) left / ( double ) right ;
case TokenType . Asterisk :
checkNumberOperands ( expr . Op , left , right ) ;
return ( double ) left * ( double ) right ;
case TokenType . PlusPlus :
checkStringOperands ( expr . Op , left , right ) ;
return ( string ) left + ( string ) right ;
case TokenType . Greater :
checkNumberOperands ( expr . Op , left , right ) ;
return Variant . FromBool ( ( double ) left > ( double ) right ) ;
case TokenType . GreaterEqual :
checkNumberOperands ( expr . Op , left , right ) ;
return Variant . FromBool ( ( double ) left > = ( double ) right ) ;
case TokenType . Less :
checkNumberOperands ( expr . Op , left , right ) ;
return Variant . FromBool ( ( double ) left < ( double ) right ) ;
case TokenType . LessEqual :
checkNumberOperands ( expr . Op , left , right ) ;
return Variant . FromBool ( ( double ) left < = ( double ) right ) ;
case TokenType . BangEqual :
return Variant . FromBool ( ( left , right ) switch
{
2023-10-04 23:13:48 +00:00
( string l , string r ) = > l ! = r ,
2023-07-06 07:11:00 +00:00
( double l , double r ) = > l ! = r ,
_ = > throw new ArgumentException ( ) ,
} ) ;
case TokenType . DoubleEqual :
return Variant . FromBool ( ( left , right ) switch
{
2023-10-04 23:13:48 +00:00
( string l , string r ) = > l = = r ,
2023-07-06 07:11:00 +00:00
( double l , double r ) = > l = = r ,
_ = > throw new ArgumentException ( ) ,
} ) ;
}
throw new ArgumentException ( $"bad binary op: {expr.Op}" ) ;
}
2023-07-11 06:51:51 +00:00
public object visitCallExpr ( Env env , AST . Call expr )
2023-07-06 07:11:00 +00:00
{
2023-09-28 00:16:19 +00:00
var callee = evaluate ( env , expr . Left ) ;
object [ ] args = new object [ expr . Arguments . Length ] ;
for ( int i = 0 ; i < args . Length ; i + + )
{
var argExpr = expr . Arguments [ i ] ;
if ( argExpr is null )
{
throw new NotImplementedException ( "partial application not implemented" ) ;
}
args [ i ] = evaluate ( env , argExpr ) ;
}
Callable function = ( Callable ) callee ;
return function . Call ( this , args ) ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitGroupingExpr ( Env env , AST . Grouping expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
return evaluate ( env , expr . Expression ) ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitVariableExpr ( Env env , AST . Variable expr )
2023-07-06 07:11:00 +00:00
{
2023-10-04 23:13:48 +00:00
var name = ( string ) expr . Value . Literal ! ;
if ( Resolver . TryResolve ( expr , out var distance ) )
{
return env . Ancestor ( distance ) [ expr . Value ] ;
}
else
{
return Globals [ expr . Value ] ;
}
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitIfExpr ( Env env , AST . If expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
var cond = evaluate ( env , expr . Condition ) ;
2023-08-06 21:44:15 +00:00
var vb = checkBoolOperand ( expr . Condition . Start , cond ) ;
2023-07-06 08:29:04 +00:00
if ( vb = = Variant . True )
{
2023-07-11 06:51:51 +00:00
return evaluate ( env , expr . Then ) ;
2023-07-06 08:29:04 +00:00
}
else
{
2023-07-11 06:51:51 +00:00
return evaluate ( env , expr . Else ) ;
2023-07-06 08:29:04 +00:00
}
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitIndexerExpr ( Env env , AST . Indexer expr )
2023-07-06 07:11:00 +00:00
{
2023-08-06 21:44:15 +00:00
var left = checkListOperand ( expr . Left . Start , evaluate ( env , expr . Left ) ) ;
var index = checkNumberOperand ( expr . Index . Start , evaluate ( env , expr . Index ) ) ;
2023-07-06 08:29:04 +00:00
try
{
var item = left [ index ] ;
return new Variant ( "some" , item ) ;
}
catch { return new Variant ( "nothing" , null ) ; }
2023-07-06 07:11:00 +00:00
}
2023-09-28 04:34:38 +00:00
public object visitLetExpr ( Env env , Let expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
var newEnv = new Env ( env ) ;
foreach ( var binding in expr . Bindings )
{
switch ( binding )
{
2023-09-28 04:34:38 +00:00
case VarBinding ( var pattern , var valueExpr ) :
2023-09-28 04:38:32 +00:00
// By passing newEnv, we let the var definition refer to
// earlier bindings in the list.
var value = evaluate ( newEnv , valueExpr ) ;
2023-07-11 18:59:15 +00:00
try
{
pattern . accept ( ( value , newEnv ) , new PatternBinder ( ) ) ;
}
catch ( Exception e )
2023-07-11 06:51:51 +00:00
{
2023-10-04 23:13:48 +00:00
var start = e switch
{
PatternMismatchException pme = > pme . Start ,
_ = > binding . Start ,
} ;
2023-08-06 23:07:00 +00:00
throw new RuntimeError ( start , e . Message ) ;
2023-07-11 06:51:51 +00:00
}
break ;
2023-09-28 04:34:38 +00:00
case FuncBinding fb :
newEnv [ ( string ) fb . Name . Literal ! ] = new FinnFunction ( fb , newEnv ) ;
break ;
2023-07-11 06:51:51 +00:00
default :
2023-09-28 04:34:38 +00:00
throw new Exception ( "wtf there are no other binding types" ) ;
2023-07-11 06:51:51 +00:00
}
}
return evaluate ( newEnv , expr . Body ) ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitListExpr ( Env env , AST . List expr )
2023-07-06 07:11:00 +00:00
{
2023-07-06 08:29:04 +00:00
List l = List . Empty ;
foreach ( var itemExpr in expr . Elements )
{
2023-08-06 23:14:18 +00:00
l = l . Add ( evaluate ( env , itemExpr ) ) ;
2023-07-06 08:29:04 +00:00
}
return l ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitLiteralExpr ( Env env , AST . Literal expr )
2023-07-06 07:11:00 +00:00
{
return expr . Value ;
}
2023-07-11 06:51:51 +00:00
public object visitRecordExpr ( Env env , AST . Record expr )
2023-07-06 07:11:00 +00:00
{
2023-07-06 08:29:04 +00:00
Record rec = Record . Empty ;
if ( expr . Base ! = null )
{
2023-07-11 06:51:51 +00:00
var baseRecValue = evaluate ( env , expr . Base . Value ) ;
2023-07-06 08:29:04 +00:00
if ( baseRecValue is not Record )
{
2023-08-06 21:44:15 +00:00
throw new RuntimeError ( expr . Base . Value . Start , "Base record must be a record." ) ;
2023-07-06 08:29:04 +00:00
}
var baseRec = ( Record ) baseRecValue ;
// Updates
HashSet < string > updateLabels = new HashSet < string > ( ) ;
foreach ( AST . Field update in expr . Base . Updates )
{
2023-07-25 19:19:07 +00:00
var label = ( string ) update . Name . Literal ! ;
2023-07-06 08:29:04 +00:00
if ( updateLabels . Contains ( label ) )
{
2023-08-06 21:44:15 +00:00
throw new RuntimeError ( update . Name , "Record updates must be to unique fields." ) ;
2023-07-06 08:29:04 +00:00
}
updateLabels . Add ( label ) ;
2023-08-06 21:44:15 +00:00
( var updateValue , var updateValueToken ) =
update . Value = = null ?
( env [ update . Name ] , update . Name ) :
( evaluate ( env , update . Value ) , update . Value . Start ) ;
2023-07-06 08:29:04 +00:00
try
{
2023-07-11 19:12:02 +00:00
baseRec = baseRec . Update ( label , updateValue ) ;
2023-07-06 08:29:04 +00:00
}
catch
{
2023-08-06 21:44:15 +00:00
throw new RuntimeError ( updateValueToken , "Field update must have same type as previous value." ) ;
2023-07-06 08:29:04 +00:00
}
}
rec = baseRec ;
}
// Extensions
HashSet < string > extLabels = new HashSet < string > ( ) ;
foreach ( AST . Field extension in expr . Extensions )
{
2023-07-25 19:19:07 +00:00
var label = ( string ) extension . Name . Literal ! ;
2023-07-06 08:29:04 +00:00
if ( extLabels . Contains ( label ) )
{
2023-08-06 21:44:15 +00:00
throw new RuntimeError ( extension . Name , "Record extensions must have unique field names." ) ;
2023-07-06 08:29:04 +00:00
}
extLabels . Add ( label ) ;
2023-07-11 19:14:02 +00:00
var extensionValue = extension . Value = = null ? env [ extension . Name ] : evaluate ( env , extension . Value ) ;
rec = rec . Extend ( label , extensionValue ) ;
2023-07-06 08:29:04 +00:00
}
return rec ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitSelectorExpr ( Env env , AST . Selector expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
var left = evaluate ( env , expr . Left ) ;
2023-08-06 21:44:15 +00:00
var r = checkRecordOperand ( expr . Left . Start , left ) ;
2023-07-06 08:29:04 +00:00
try
{
2023-07-25 19:19:07 +00:00
return r . Get ( ( string ) expr . FieldName . Literal ! ) ;
2023-07-06 08:29:04 +00:00
}
catch
{
2023-08-06 21:44:15 +00:00
throw new RuntimeError ( expr . FieldName , "Operand must have selected field." ) ;
2023-07-06 08:29:04 +00:00
}
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitSequenceExpr ( Env env , AST . Sequence expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
evaluate ( env , expr . Left ) ;
return evaluate ( env , expr . Right ) ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitUnaryExpr ( Env env , AST . Unary expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 06:51:51 +00:00
object right = evaluate ( env , expr . Right ) ;
2023-07-25 19:19:07 +00:00
switch ( expr . Op . Type )
2023-07-06 07:11:00 +00:00
{
case TokenType . Minus :
checkNumberOperand ( expr . Op , right ) ;
return - ( double ) right ;
case TokenType . Bang :
2023-07-06 08:29:04 +00:00
if ( checkBoolOperand ( expr . Op , right ) = = Variant . True )
2023-07-06 07:11:00 +00:00
{
2023-07-06 08:29:04 +00:00
return Variant . False ;
}
else
{
return Variant . True ;
2023-07-06 07:11:00 +00:00
}
default :
// Unreachable
throw new Exception ( $"bad unary op: {expr.Op}" ) ;
}
}
2023-07-11 06:51:51 +00:00
public object visitVariantExpr ( Env env , AST . Variant expr )
2023-07-06 07:11:00 +00:00
{
2023-07-25 19:19:07 +00:00
var tag = ( string ) expr . Tag . Literal ! ;
2023-07-11 18:59:15 +00:00
if ( expr . Argument = = null )
{
2023-07-17 05:30:20 +00:00
return new Variant ( tag , null ) ;
2023-07-11 18:59:15 +00:00
}
2023-07-17 05:30:20 +00:00
return new Variant ( tag , evaluate ( env , expr . Argument ) ) ;
2023-07-06 07:11:00 +00:00
}
2023-07-11 06:51:51 +00:00
public object visitWhenExpr ( Env env , AST . When expr )
2023-07-06 07:11:00 +00:00
{
2023-07-11 19:55:06 +00:00
var head = evaluate ( env , expr . Head ) ;
foreach ( var c in expr . Cases )
{
try
{
var newEnv = new Env ( env ) ;
c . Pattern . accept ( ( head , newEnv ) , new PatternBinder ( ) ) ;
return evaluate ( newEnv , c . Value ) ;
}
2023-08-06 23:07:00 +00:00
catch ( VariantTagMismatchException )
2023-07-11 19:55:06 +00:00
{
continue ;
}
2023-08-06 23:07:00 +00:00
catch ( PatternMismatchException e )
2023-07-11 19:55:06 +00:00
{
2023-08-06 23:07:00 +00:00
throw new RuntimeError ( e . Start , e . Message ) ;
2023-07-11 19:55:06 +00:00
}
}
2023-08-06 23:07:00 +00:00
throw new RuntimeError ( expr . Start , "No matching patterns." ) ;
2023-07-06 07:11:00 +00:00
}
}