protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var newEnv = env; var newClauses = new List <SucoListClause>(); var anySingleton = false; foreach (var clause in Clauses) { var newFromExpression = clause.FromExpression?.DeduceTypes(newEnv, context); // Deduce the type of the iterator variable SucoType collectionType; if (newFromExpression != null) { collectionType = newFromExpression.Type; } else if (clause.HasDollar) { collectionType = SucoType.Cell.List(); } else { try { collectionType = env.GetVariableType("cells"); } catch (SucoTempCompileException tc) { throw new SucoCompileException(tc.Message, clause.StartIndex, clause.EndIndex); } } if (collectionType is not SucoListType { ElementType: SucoType elementType })
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var valueExpr = ValueExpression.DeduceTypes(env, context); var innerExpr = InnerExpression.DeduceTypes(env.DeclareVariable(VariableName, valueExpr.Type), context); return(new SucoLetExpression(StartIndex, EndIndex, VariableName, valueExpr, innerExpr, innerExpr.Type)); }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var condition = Condition.DeduceTypes(env, context); if (!(condition.Type is SucoBooleanType)) { throw new SucoCompileException($"The condition in a ?: operator must be a boolean.", Condition.StartIndex, Condition.EndIndex); } var trueExpr = True.DeduceTypes(env, context); var falseExpr = False.DeduceTypes(env, context); SucoType thisType; if (trueExpr.Type.Equals(falseExpr.Type)) { thisType = trueExpr.Type; } else if (trueExpr.Type.ImplicitlyConvertibleTo(falseExpr.Type)) { trueExpr = trueExpr.ImplicitlyConvertTo(falseExpr.Type); thisType = falseExpr.Type; } else if (falseExpr.Type.ImplicitlyConvertibleTo(trueExpr.Type)) { falseExpr = falseExpr.ImplicitlyConvertTo(trueExpr.Type); thisType = trueExpr.Type; } else { throw new SucoCompileException($"Types “{trueExpr.Type}” and “{falseExpr.Type}” are not compatible.", True.StartIndex, False.EndIndex); } return(new SucoConditionalExpression(StartIndex, EndIndex, condition, trueExpr, falseExpr, thisType)); }
public override SucoListCondition DeduceTypes(SucoTypeEnvironment env, SucoContext context, SucoType elementType) { var innerExpression = Expression.DeduceTypes(env, context); if (!innerExpression.Type.ImplicitlyConvertibleTo(SucoType.Boolean)) { throw new SucoCompileException($"A condition expression must be a boolean (or implicitly convertible to one).", StartIndex, EndIndex); } return(new SucoListExpressionCondition(StartIndex, EndIndex, innerExpression.ImplicitlyConvertTo(SucoType.Boolean))); }
public override SucoListCondition DeduceTypes(SucoTypeEnvironment env, SucoContext context, SucoType elementType) { switch (Name) { case "first": case "last": case "before": case "after": case "~": break; case "^": case ">": case "v": case "<": case "↑": case "→": case "↓": case "←": case "diagonal": case "adjacent": case "orthogonal": case "above": case "right": case "below": case "left": case "samerow": case "samecol": case "samebox": case "topleft": case "topright": case "bottomleft": case "bottomright": case "lefttop": case "righttop": case "leftbottom": case "rightbottom": if (!elementType.Equals(SucoType.Cell)) { throw new SucoCompileException($"“{Name}” can only be used on lists of cells.", StartIndex, EndIndex); } break; default: throw new SucoCompileException($"Unknown shortcut condition: “{Name}”.", StartIndex, EndIndex); } return(this); }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { try { var type = env.GetVariableType(Name); if (type == null) { throw new SucoCompileException($"Unknown variable “{Name}”.", StartIndex, EndIndex); } return(new SucoIdentifierExpression(StartIndex, EndIndex, Name, type)); } catch (SucoTempCompileException tc) { throw new SucoCompileException(tc.Message, StartIndex, EndIndex); } }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { if (Elements.Count == 0) { throw new SucoCompileException("Empty array literals are not currently supported.", StartIndex, EndIndex); } var newElements = Elements.Select(e => e.DeduceTypes(env, context)).ToList(); for (var i = 0; i < newElements.Count; i++) { if (newElements.All(e => e.Type.ImplicitlyConvertibleTo(newElements[i].Type))) { return(new SucoArrayExpression(StartIndex, EndIndex, newElements.Select(e => e.ImplicitlyConvertTo(newElements[i].Type)).ToList(), newElements[i].Type.List())); } } throw new SucoCompileException("This array contains elements that are not compatible with one another.", StartIndex, EndIndex); }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var newPieces = Pieces.Select(p => { if (p is not SucoStringLiteralPieceExpression expr) { return(p); } var typedExpr = expr.Expression.DeduceTypes(env, context); if (!typedExpr.Type.ImplicitlyConvertibleTo(SucoType.String)) { throw new SucoCompileException($"Expression interpolated into a string literal is of type “{typedExpr.Type}”, which not implicitly convertible to string.", expr.Expression.StartIndex, expr.Expression.EndIndex); } return(typedExpr.ImplicitlyConvertTo(SucoType.String)); }).ToArray(); return(new SucoStringLiteralExpression(StartIndex, EndIndex, newPieces, SucoType.String)); }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var op = Operand.DeduceTypes(env, context); try { var resultType = op.Type.GetUnaryOperatorType(Operator); if (resultType == null) { throw new SucoCompileException($"Type “{op.Type}” does not support the “{Operator}” unary operator.", StartIndex, EndIndex); } return(new SucoUnaryOperatorExpression(StartIndex, EndIndex, op, Operator, resultType)); } catch (SucoTempCompileException ce) { throw new SucoCompileException(ce.Message, StartIndex, EndIndex); } }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var op = Operand.DeduceTypes(env, context); try { // Special case: the “.pos” member should work on any variable from a list comprehension if (MemberName == "pos" && op is SucoIdentifierExpression ident && env.IsVariableInListComprehension(ident.Name)) { return(new SucoPositionExpression(ident.StartIndex, ident.EndIndex, ident.Name, SucoType.Integer)); } return(new SucoMemberAccessExpression(StartIndex, EndIndex, op, MemberName, op.Type.GetMemberType(MemberName, context))); } catch (SucoTempCompileException ce) { throw new SucoCompileException(ce.Message, StartIndex, EndIndex); } }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { var left = Left.DeduceTypes(env, context); var right = Right.DeduceTypes(env, context); try { var resultType = left.Type.GetBinaryOperatorType(Operator, right.Type, context); if (resultType == null) { throw new SucoCompileException($"Types “{left.Type}” and “{right.Type}” do not support the “{Operator}” operator.", left.StartIndex, right.EndIndex); } return(new SucoBinaryOperatorExpression(StartIndex, EndIndex, left, right, Operator, resultType)); } catch (SucoTempCompileException ce) { throw new SucoCompileException(ce.Message, StartIndex, EndIndex); } }
public static bool IsValidCode(string source, SucoTypeEnvironment env, SucoContext context, SucoType expectedResultType, out string error) { try { ParseCode(source, env, context, expectedResultType); error = null; return(true); } catch (SucoCompileException sce) { error = sce.Message; return(false); } catch (SucoParseException spe) { error = spe.Message; return(false); } }
public SucoExpression DeduceTypes(SucoTypeEnvironment env, SucoContext context) { var result = deduceTypes(env, context); switch (context) { case SucoContext.Constraint: if (result.Type is SucoDecimalType) { throw new SucoCompileException("You cannot use decimal numbers in a puzzle constraint.", StartIndex, EndIndex); } if (result.Type is SucoStringType) { throw new SucoCompileException("You cannot use strings in a puzzle constraint.", StartIndex, EndIndex); } break; case SucoContext.Svg: break; } return(result); }
public SucoParser(string source, SucoContext context) : base(source, Ut.NewArray( // MAKE SURE that every multi-character token that has another token as its prefix comes before said prefix // Arithmetic "+", "-", "−", "*", "×", "^", "%", "/", // Relational "<=", "<", "≤", ">=", ">", "≥", "=", "!=", "≠", // Logical "&", "|", "?", ":", "!", // Structural "{", "}", ",", "(", ")", ".", "[", "]", ";", // Flags; "+" is listed in Arithmetic; "1" is parsed as a numeral "$", // String literals "\"", // Shortcut filters "~", "←", "→", "↑", "↓" // “v” parses as an identifier; “<”/“>” are in Relational; “^” is in Arithmetic )) { Context = context; }
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) { try { var operand = Operand.DeduceTypes(env, context); if (operand.Type is not SucoFunctionType fnc) { throw new SucoCompileException($"“{operand.Type}” is not a function type.", operand.StartIndex, operand.EndIndex); } var newArguments = Arguments.Select(arg => arg.DeduceTypes(env, context)).ToArray(); var(parameterTypes, returnType) = fnc.Resolve(newArguments.Select(a => a.Type).ToArray()); for (var i = 0; i < newArguments.Length; i++) { newArguments[i] = newArguments[i].ImplicitlyConvertTo(parameterTypes[i]); } return(new SucoCallExpression(StartIndex, EndIndex, operand, newArguments, returnType)); } catch (SucoFunctionResolutionException re) { throw new SucoCompileException(re.Message, StartIndex, EndIndex); } }
public override SucoType GetBinaryOperatorType(BinaryOperator op, SucoType rightType, SucoContext context) => (op, rightType) switch { // Decimal op Decimal (BinaryOperator.Equal, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.NotEqual, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.LessThan, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.LessThanOrEqual, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.GreaterThan, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.GreaterThanOrEqual, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.Plus, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Minus, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Times, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Modulo, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Divide, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Power, SucoDecimalType) => SucoType.Decimal, // Decimal op Int (BinaryOperator.Equal, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.NotEqual, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.LessThan, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.LessThanOrEqual, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.GreaterThan, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.GreaterThanOrEqual, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.Plus, SucoIntegerType) => SucoType.Decimal, (BinaryOperator.Minus, SucoIntegerType) => SucoType.Decimal, (BinaryOperator.Times, SucoIntegerType) => SucoType.Decimal, (BinaryOperator.Modulo, SucoIntegerType) => SucoType.Decimal, (BinaryOperator.Divide, SucoIntegerType) => SucoType.Decimal, (BinaryOperator.Power, SucoIntegerType) => SucoType.Decimal, _ => base.GetBinaryOperatorType(op, rightType, context), };
protected abstract SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context);
public static SucoExpression ParseCode(string source, SucoTypeEnvironment env, SucoContext context, SucoType expectedResultType = null) { if (source == null) { throw new ArgumentNullException(nameof(source)); } try { // Try parsing as a standalone expression (e.g. “cells.unique”). var parser = new SucoParser(source, context); var ret = parser.parseExpression(); parser.EnforceEof(); ret = ret.DeduceTypes(env, context); if (expectedResultType != null && !ret.Type.ImplicitlyConvertibleTo(expectedResultType)) { throw new SucoParseException($"The expression is of type “{ret.Type}”, which is not implicitly convertible to the required type, “{expectedResultType}”.", ret.StartIndex, ret.EndIndex); } return(expectedResultType == null ? ret : ret.ImplicitlyConvertTo(expectedResultType)); } catch (SucoParseException pe1) { try { // Try parsing as a list comprehension (e.g. “a: a.odd”). var parser = new SucoParser(source, context); var ret = parser.parseListComprehension(); parser.EnforceEof(); ret = ret.DeduceTypes(env, context); if (expectedResultType != null && !ret.Type.ImplicitlyConvertibleTo(expectedResultType)) { throw new SucoParseException($"The expression is of type “{ret.Type}”, which is not implicitly convertible to the required type, “{expectedResultType}”.", ret.StartIndex, ret.EndIndex); } return(expectedResultType == null ? ret : ret.ImplicitlyConvertTo(expectedResultType)); } catch (SucoParseException pe2) when(pe2.StartIndex <= pe1.StartIndex) { } throw; } }
public abstract SucoListCondition DeduceTypes(SucoTypeEnvironment env, SucoContext context, SucoType elementType);
public override SucoType GetBinaryOperatorType(BinaryOperator op, SucoType rightType, SucoContext context) => (op, rightType) switch { (BinaryOperator.Equal, SucoBooleanType) => SucoType.Boolean, (BinaryOperator.NotEqual, SucoBooleanType) => SucoType.Boolean, (BinaryOperator.And, SucoBooleanType) => SucoType.Boolean, (BinaryOperator.Or, SucoBooleanType) => SucoType.Boolean, _ => base.GetBinaryOperatorType(op, rightType, context) };
public override SucoType GetMemberType(string memberName, SucoContext context) => memberName switch { "hash" => SucoType.String,
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) => this;
public override SucoType GetMemberType(string memberName, SucoContext context) => memberName switch { //"value" => SucoType.Integer, "x" => SucoType.Integer,
public virtual SucoType GetBinaryOperatorType(BinaryOperator op, SucoType rightType, SucoContext context) => throw new SucoTempCompileException($"Binary operator “{op}” not defined on types “{this}” and “{rightType}”.");
public override SucoType GetBinaryOperatorType(BinaryOperator op, SucoType rightType, SucoContext context) => (op, rightType) switch { // Comparison with Int (BinaryOperator.Equal, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.NotEqual, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.LessThan, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.LessThanOrEqual, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.GreaterThan, SucoIntegerType) => SucoType.Boolean, (BinaryOperator.GreaterThanOrEqual, SucoIntegerType) => SucoType.Boolean, // Comparison with Decimal (BinaryOperator.Equal, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.NotEqual, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.LessThan, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.LessThanOrEqual, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.GreaterThan, SucoDecimalType) => SucoType.Boolean, (BinaryOperator.GreaterThanOrEqual, SucoDecimalType) => SucoType.Boolean, // Arithmetic with Int (BinaryOperator.Plus, SucoIntegerType) => SucoType.Integer, (BinaryOperator.Minus, SucoIntegerType) => SucoType.Integer, (BinaryOperator.Times, SucoIntegerType) => SucoType.Integer, (BinaryOperator.Modulo, SucoIntegerType) => SucoType.Integer, (BinaryOperator.Divide, SucoIntegerType) => context != SucoContext.Constraint ? SucoType.Decimal : throw new SucoTempCompileException("Suco does not allow the use of division in puzzle constraints. Rewrite the equation to use multiplication instead (for example: instead of a.value/b.value = 2, write a.value = 2*b.value)."), (BinaryOperator.Power, SucoIntegerType) => SucoType.Integer, // Arithmetic with Decimal (BinaryOperator.Plus, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Minus, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Times, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Modulo, SucoDecimalType) => SucoType.Decimal, (BinaryOperator.Power, SucoDecimalType) => SucoType.Decimal, _ => base.GetBinaryOperatorType(op, rightType, context), };
protected override SucoExpression deduceTypes(SucoTypeEnvironment env, SucoContext context) => new SucoBooleanLiteralExpression(StartIndex, EndIndex, LiteralValue, SucoType.Boolean);
public virtual SucoType GetMemberType(string memberName, SucoContext context) => throw new SucoTempCompileException($"Member “{memberName}” is not defined on type “{this}”.");