pascalabcnet/SyntaxVisitors/SugarVisitors/CacheFunctionVisitor.cs
Александр Земляк 1f4b44c2c0
Исправление ошибок при перекомпиляции pcu файлов (#3398)
* Move yield string constants to StringConstants class

* Add GeneratedNamesManager class to store all generated names during unit compilation

* Make GeneratedNamesManager class non static

* Add Utils project to installer files

* Add GeneratedNamesManager usage in CapturedVariablesSubstitutionsManager

* Move GeneratedNamesManager initialization in CompilationUnit constructor

* More GeneratedNamesManager usage

* QuestionPointDesugarVisitor fix
2026-03-08 18:30:33 +03:00

187 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using PascalABCCompiler.CoreUtils;
using PascalABCCompiler.SyntaxTree;
namespace SyntaxVisitors.SugarVisitors
{
public class CacheFunctionVisitor : BaseChangeVisitor
{
private readonly GeneratedNamesManager generatedNamesManager;
private CacheFunctionVisitor(GeneratedNamesManager generatedNamesManager)
{
this.generatedNamesManager = generatedNamesManager;
}
public static CacheFunctionVisitor Create(GeneratedNamesManager generatedNamesManager) => new CacheFunctionVisitor(generatedNamesManager);
private string GenerateNewName(string prefix)
{
return generatedNamesManager.GenerateName(prefix);
}
public override void visit(simple_attribute_list al)
{
base.visit(al);
}
public override void visit(procedure_definition pd)
{
var attrs = pd.proc_header.attributes;
// Проверить, что среди атрибутов есть [Cache]
// Проверить, что [Cache] - без параметров
// Можно [Cache 100] - ограничение на количество кешируемых значений
// И тогда - какая стратегия отброса старых:
//var qq1 = attrs?.attributes?.SelectMany(att => att?.attributes).ToArray();
List<ident> qq = null;
if (attrs != null)
qq = attrs.attributes?.SelectMany(att => att?.attributes)?.Where(at => at?.type is named_type_reference)
.Select(at=>at.type.names).Where(ns=>ns.Count==1).Select(nm => nm[0])
.ToList();
//if (attrs?.attributes?[0].attributes?[0]?.type?.names?[0]?.name?.ToLower()=="cache") // слишком быстро
if (qq != null && qq.Any(nm=>nm.name.ToLower()=="cache"))
{
// проверить, что
// это функция +
// это не метод расширения +
// параметры есть (хоть один) +
// параметров не более семи +
// тип возвращаемого значения задаётся явно +
// имя функции не составное +
// отсутствие var и const в параметрах +
// функция определена глобально
if (pd.DescendantNodes().OfType<yield_node>().Count() > 0)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_CANNOT_HAVE_YIELD", pd.proc_header);
var fh = pd.proc_header as function_header;
if (fh == null)
throw new SyntaxVisitorError("ONLY_FUNCTION_SHOULD_HAVE_CACHE_ATTRIBUTE",pd.proc_header);
var isExtension = fh.proc_attributes.proc_attributes.Any(attr => attr.name == "extensionmethod");
if (isExtension)
throw new SyntaxVisitorError("EXTENSION_FUNCTIONS_SHOULD_NOT_HAVE_CACHE_ATTRIBUTE", fh);
if (fh.parameters == null || fh.parameters != null && fh.parameters.Count == 0)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_SHOULD_HAVE_PARAMETERS", fh);
if (fh.parameters != null && fh.parameters.params_list.Sum(tp => tp.idents.idents.Count) > 7)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_SHOULD_HAVE_LESSTHEN8_PARAMETERS", fh);
if (fh.return_type == null)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_SHOULD_HAVE_EXPLICIT_RETURN_TYPE", fh);
if (fh.name.class_name != null)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_SHOULD_NOT_HAVE_COMPOUND_NAME", fh.name);
var notByValue = pd.proc_header.parameters.params_list.Any(tp => tp.param_kind != parametr_kind.none);
if (notByValue)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_SHOULD_NOT_HAVE_VAR_CONST_PARAMS_MODIFIERS", fh);
var pp = pd.proc_header.parameters.params_list.SelectMany(tp => tp.idents.idents.Select(id => Tuple.Create(id, tp.vars_type))).ToArray();
// var @tpar := Tuple.Create(все параметры)
var tupleIdent = new ident(GenerateNewName("@tpar"));
// SSM 08.05.22 - один параметр не надо оборачивать в кортеж
// SSM 09.05.22 - для реализации кортежа используется ValueTuple если он определен
var TupleName = "Tuple";
if (System.Type.GetType("System.ValueTuple") != null)
TupleName = "ValueTuple";
var_statement vs;
if (pp.Length == 1)
vs = new var_statement(tupleIdent, pp[0].Item1 as expression);
else vs = new var_statement(tupleIdent,
new method_call(
new dot_node(new dot_node("?System", TupleName),new ident("Create")),
new expression_list(pp.Select(pair => pair.Item1 as expression).ToList())
)
);
var proc_code = (pd.proc_body as block).program_code;
proc_code.list.Insert(0, vs);
if (proc_code.list[proc_code.list.Count - 1] is empty_statement)
proc_code.list.RemoveAt(proc_code.list.Count - 1);
// var @ИмяФункцииDict := new Dictionary<типTuple, тип возвр значения функции>
// var d := new PABCSystem.Dictionary<System.Tuple<integer,integer,real>,integer>;
// SSM 08.05.22 - один параметр не надо оборачивать в кортеж
template_param_list ttp;
if (pp.Length == 1)
ttp = new template_param_list(pp[0].Item2 as type_definition);
else
ttp = new template_param_list(new List<type_definition>(pp.Select(pair => pair.Item2 as type_definition)));
type_definition ttr_tuple;
if (pp.Length == 1)
ttr_tuple = pp[0].Item2 as type_definition;
else
ttr_tuple = new template_type_reference(new named_type_reference(new List<ident> { new ident("System"), new ident(TupleName) }),
ttp
);
var ttr_dict = new template_type_reference(new named_type_reference(new List<ident> { new ident("PABCSystem"), new ident("Dictionary") }),
new template_param_list(new List<type_definition> { ttr_tuple,(pd.proc_header as function_header).return_type }));
var new_ex = new new_expr(ttr_dict, new expression_list());
var sug_ex = new sugared_expression(ttp, new_ex, new_ex.source_context); // семантическая проверка выражения
var dictIdent = new ident(GenerateNewName("@" + pd.proc_header.name.ToString() + "DictCache"));
var vsglobal = new var_statement(dictIdent, sug_ex);
var declarations = pd.Parent as declarations;
if (declarations == null)
throw new SyntaxVisitorError("FUNCTIONS_WITH_CACHE_ATTRIBUTE_SHOULD_BE_DEFINED_GLOBALLY", fh);
var ind = declarations.list.FindIndex(decl => decl == pd);
declarations.list.Insert(ind, vsglobal);
// До:
// if d.ContainsKey(tup) then
// begin
// Result := d[tup];
// exit;
// end;
// а надо другой код
/*var indr = new indexer(dictIdent, new expression_list(tupleIdent));
var assRes = new assign("Result", indr);
var exitst = new procedure_call(new ident("exit"));
var stlst = new statement_list(assRes, exitst);
var cond = new method_call(new dot_node(dictIdent, new ident("ContainsKey")),
new expression_list(tupleIdent));
var ifstatement = new if_node(cond, stlst);*/
// if d.TryGetValue(tup,Result) then exit;
var indr = new indexer(dictIdent, new expression_list(tupleIdent));
var exitst = new procedure_call(new ident("exit"));
var cond = new method_call(new dot_node(dictIdent, new ident("TryGetValue")),
new expression_list(new List<expression> { tupleIdent, new ident("Result") }));
var ifstatement = new if_node(cond, exitst);
proc_code.list.Insert(1, ifstatement);
// В конец: d[tup] := Result. И надо то же после каждого exit
var assEnd = new assign(indr, new ident("Result"));
// Вставить d[tup] := Result перед каждым exit начиная с конца
var pcc = pd.DescendantNodes().OfType<procedure_call>().Where(pc => pc.func_name is ident id && id.name.ToLower() == "exit" && pc.source_context != null).ToArray();
foreach (var p in pcc.Reverse())
{
var ae = assEnd.Clone() as assign;
if (p.Parent is statement_list sl)
sl.ReplaceInList(p, new List<statement> { ae, p});
else p.Parent.ReplaceDescendant(p as statement, new statement_list(new List<statement> { ae, p }));
}
if (proc_code.list[proc_code.list.Count - 1] is procedure_call pcall && pcall.func_name is ident idd && idd.name.ToLower() == "exit")
; // ничего не добавлять - т.к. перед exit всё добавлено
else proc_code.list.Add(assEnd);
// Добавить в начало функции семантические проверки:
// отсутствие делегатов и pointer в параметрах
// К сожалению, это описание
// var d := new PABCSystem.Dictionary<System.Tuple<function(i: integer): integer,integer>,integer>;
// даёт ошибку раньше, поскольку размещено в разделе описаний
// Нужно включать семантическую проверку в выражение new
var semchk1 = new semantic_check_sugared_statement_node(typeof(semantic_check_delegates_pointers_in_cached_function), new List<syntax_tree_node> { pd.proc_header.parameters }, pd.source_context);
(pd.proc_body as block).program_code.list.Insert(0, semchk1);
semchk1.Parent = (pd.proc_body as block).program_code;
}
}
}
}