Toy 语言到 LLVM IR 实现源码注释

news2024/10/5 21:25:31

对从程序源代码到AST的转换部分做了注释

源码:

toy.cpp

#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include <cctype>
#include <cstdio>
#include <map>
#include <string>
#include <vector>
//using namespace llvm;

enum Token_Type { EOF_TOKEN = 0, DEF_TOKEN, IDENTIFIER_TOKEN, NUMERIC_TOKEN };

FILE *file;// toy 语言源代码文件
static std::string Identifier_string;//用来存储 toy 语言程序中当前 get 到的标识符字符串
static int Numeric_Val;// 用来存储 toy 语言程序中的数值量

static int get_token()// 返回下一个当前的 token 类型,并改写全局变量 Identifier_str 或者 NumStr和Numeric_Val
{
  static int LastChar = ' ';

  while (isspace(LastChar))// 只要是空白键,就接着往下取,直到遇到非空白键字符才跳出循环
    LastChar = fgetc(file);

  if (isalpha(LastChar)) {// 如果是字母开头的字符串的话,有可能是普通标识符,也有可能是函数定义关键字 def
    Identifier_string = LastChar;
    while (isalnum((LastChar = fgetc(file))))//如果字符串接下来依然是字母或者数字,则继续读出并拼接在一起
      Identifier_string += LastChar;

    if (Identifier_string == "def")// 字符串可能是toy 语言关键字 def
      return DEF_TOKEN;

    return IDENTIFIER_TOKEN;//多数情况下是 普通标识符
  }

  if (isdigit(LastChar)) {//如果是数字开头的字符串的话,会是一个整数
    std::string NumStr;
    do {
      NumStr += LastChar;
      LastChar = fgetc(file);
    } while (isdigit(LastChar));

    Numeric_Val = strtod(NumStr.c_str(), 0);//将表示整数的字符串转换成整数
    return NUMERIC_TOKEN;
  }

  if (LastChar == '#') {// 整数开头的部分是注释
    do
      LastChar = fgetc(file);
    while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');// 三种情况下注释结束,文件末尾、行尾、回车

    if (LastChar != EOF)//如果一行注释结束了,但是下一行依然存在代码等,这是本函数还没有返回token type,则继续递归分析下一个字符串的token type 并返回这个token type
      return get_token();
  }

  if (LastChar == EOF)// 如果遇到了文件结尾
    return EOF_TOKEN;

  int ThisChar = LastChar;//既不是EOF,也不是字符串或数字,那可能是运算符;程序跑到本函数时,本程序还没有检查程序的正确性,故潜在认为这可能是个运算符;后便会检查
  LastChar = fgetc(file);
  return ThisChar;
}

namespace {

class BaseAST {// AST 基类,本类的子类都是一种句法结构的AST类;表达式AST,控制语句AST,
public:
  virtual ~BaseAST() {}
  virtual llvm::Value *Codegen() = 0;//每种具体的AST 都需要生成代码,故定义一个 interface
};

class NumericAST : public BaseAST {// 表示数值的 AST 类
  int numeric_val;

public:
  NumericAST(int val) : numeric_val(val) {}// 构造时赋值
  virtual llvm::Value *Codegen();
};

class VariableAST : public BaseAST {// 表示变量标识符的 AST
  std::string Var_Name;

public:
  VariableAST(const std::string &name) : Var_Name(name) {}
  virtual llvm::Value *Codegen();
};

class BinaryAST : public BaseAST {// 表示 二值运算符表达式的 AST
  std::string Bin_Operator;
  BaseAST *LHS, *RHS;// 分别指向两个操作数的 AST 实例

public:
  BinaryAST(std::string op, BaseAST *lhs, BaseAST *rhs)// 这意味着自底向上构造 AST,否则二值操作数的参数 AST 实例不存在
      : Bin_Operator(op), LHS(lhs), RHS(rhs) {}
  virtual llvm::Value *Codegen();
};

class FunctionCallAST : public BaseAST {// 调用函数的 AST,被调函数名和参数列表
  std::string Function_Callee;
  std::vector<BaseAST *> Function_Arguments;// 参数列表,每个参数都是一个 AST 实例

public:
  FunctionCallAST(const std::string &callee, std::vector<BaseAST *> &args)
      : Function_Callee(callee), Function_Arguments(args) {}
  virtual llvm::Value *Codegen();
};

class FunctionDeclAST {// 函数声明 AST 类,因为仅仅做原型对比,故参数是字符串,toy中都是int型的,故只存名字串即可
  std::string Func_Name;
  std::vector<std::string> Arguments;

public:
  FunctionDeclAST(const std::string &name, const std::vector<std::string> &args)
      : Func_Name(name), Arguments(args){};
  llvm::Function *Codegen();
};

class FunctionDefnAST {// 函数定义的 AST 类,由 函数声明部分和函数体构成
  FunctionDeclAST *Func_Decl;
  BaseAST *Body; // 没有定义函数体的 AST 类,故这里的Body 可以只是指向一个表达式的 AST

public:
  FunctionDefnAST(FunctionDeclAST *proto, BaseAST *body)
      : Func_Decl(proto), Body(body) {}
  llvm::Function *Codegen();
};
} // namespace

static int Current_token;// 存储当前被分析的 token 的类型 enumerate 值;
static int next_token() { return Current_token = get_token(); }// 通过全局变量File指针等读取下一个 token

static std::map<char, int> Operator_Precedence;// 运算符优先级

static int getBinOpPrecedence() {// 获取二元运算符的优先级
  if (!isascii(Current_token))//参数检查, 二元运算符是个ascii 码, +-*/
    return -1;

  int TokPrec = Operator_Precedence[Current_token];
  if (TokPrec <= 0)
    return -1;
  return TokPrec;// 返回优先级
}

static BaseAST *expression_parser();// 声明表达式解析器函数,下面实现

static BaseAST *identifier_parser() {// 标识符解析器函数
  std::string IdName = Identifier_string;// 当前普通标识符串

  next_token();// 返回下一个当前的 token 类型,并改写全局变量 Identifier_str 或者 NumStr和Numeric_Val

  if (Current_token != '(')//如果 标识符的接下来的token 不是(, 那么这个标识符是一个变量,否则是一个函数
    return new VariableAST(IdName);// 创建变量的 AST

  next_token();// 走到这里,说明当前搞到了一个(,那么标识符 IdName 是一个函数名,接下来尝试获取参数

  std::vector<BaseAST *> Args;//函数参数可能有多个
  if (Current_token != ')') { // 如果get到的当前token 不是),说明参数不为空,而且当前token就是这个参数
    while (1) {
      BaseAST *Arg = expression_parser();// 函数调用时,每个参数都可能是一个表达式
      if (!Arg)// 什么情况为 NULL?
        return 0;
      Args.push_back(Arg);// 入列表

      if (Current_token == ')')//参数结束否
        break;

      if (Current_token != ',')// 为真时,出错;既不是)又不是,  是不符合语法要求的。
        return 0;
      next_token();
    }
  }
  next_token();

  return new FunctionCallAST(IdName, Args);
}

static BaseAST *numeric_parser() {
  BaseAST *Result = new NumericAST(Numeric_Val);
  next_token();
  return Result;
}

static BaseAST *paran_parser() {//如果遇到了表达式中含(, 则忽略这个(,并继续尝试解析接下来的表达式,可能会递归陷入,但最后每每都需要递归检查)的对应存在
  next_token();
  BaseAST *V = expression_parser();
  if (!V)
    return 0;

  if (Current_token != ')')
    return 0;
  return V;
}

static BaseAST *Base_Parser() {
  switch (Current_token) {
  default:
    return 0;
  case IDENTIFIER_TOKEN:
    return identifier_parser();//
  case NUMERIC_TOKEN:
    return numeric_parser();//创建数字 AST 实例
  case '(':
    return paran_parser();//陷入递归调用解析expression_parser()
  }
}
//这个函数比较复杂一些,需要处理递归的运算符;    x * 3 + y;   x + 3 * y;  x * (3+y); (x*3) + 6; (x + 3) * y
static BaseAST *binary_op_parser(int Old_Prec, BaseAST *LHS) {//binary_op_parser() -> paran_parser() -> expression_parser()
  while (1) {
    int Operator_Prec = getBinOpPrecedence();

    if (Operator_Prec < Old_Prec)
      return LHS;//最后返回的LHS是 LHS = new BinaryAST(sting, LHS, RHS);

    int BinOp = Current_token;
    next_token();

    BaseAST *RHS = Base_Parser();
    if (!RHS)
      return 0;

    int Next_Prec = getBinOpPrecedence();
    if (Operator_Prec < Next_Prec) {
      RHS = binary_op_parser(Operator_Prec + 1, RHS);
      if (RHS == 0)
        return 0;
    }

    LHS = new BinaryAST(std::to_string(BinOp), LHS, RHS);
  }
}

static BaseAST *expression_parser() {
  BaseAST *LHS = Base_Parser();//取运算符左边的变量(可能是数字,也可能是标识符变量),创建对应的 AST 实例,被LHS指向之。
  if (!LHS)
    return 0;
  return binary_op_parser(0, LHS);// 第一个参数作为binary op AST 的左操作数,同时解析出操作符和右边的操作数,右操作数有可能也是个表达式,故可能递归进expression_parser()的调用中
}

static FunctionDeclAST *func_decl_parser() {
  if (Current_token != IDENTIFIER_TOKEN)//函数名字,必须是个字符串
    return 0;

  std::string FnName = Identifier_string;//将当前token名字转存入 FnName,函数名字token
  next_token();//函数名后跟着个(

  if (Current_token != '(')//如果不是(,则出错
    return 0;

  std::vector<std::string> Function_Argument_Names;//开始解析函数参数列表
  while (next_token() == IDENTIFIER_TOKEN)// 要么参数列表非空,则取到参数名字;为空,则取到的token应该是).
    Function_Argument_Names.push_back(Identifier_string);//两个及以上参数之间用空格隔开,此处未处理“,”
  if (Current_token != ')')
    return 0;

  next_token();//拿到下一个token后,为函数返回做后续准备
  //创建函数声明的 AST,并返回之,可能作为函数 定义AST 实例的一个成员
  return new FunctionDeclAST(FnName, Function_Argument_Names);
}

static FunctionDefnAST *func_defn_parser() {
  next_token();//这是def之后的token,即函数名字,必须是个字符串
  FunctionDeclAST *Decl = func_decl_parser();
  if (Decl == 0)
    return 0;
  //在 expression_parser 运行之前,已经取到了下一个 token,发生在func_decl_parser()最后的部分
  if (BaseAST *Body = expression_parser())//def 后边必然先出现函数声明,再出现函数体,即表达式。
    return new FunctionDefnAST(Decl, Body);
  return 0;
}

static FunctionDefnAST *top_level_parser() {
  if (BaseAST *E = expression_parser()) {
    FunctionDeclAST *Func_Decl =
        new FunctionDeclAST("", std::vector<std::string>());
    return new FunctionDefnAST(Func_Decl, E);
  }
  return 0;
}

static void init_precedence() {
  Operator_Precedence['-'] = 1;
  Operator_Precedence['+'] = 2;
  Operator_Precedence['/'] = 3;
  Operator_Precedence['*'] = 4;
}

static llvm::Module *Module_Ob;
static llvm::LLVMContext MyGlobalContext;
static llvm::IRBuilder<> Builder(MyGlobalContext);
static std::map<std::string, llvm::Value *> Named_Values;

llvm::Value *NumericAST::Codegen() {
  return llvm::ConstantInt::get(llvm::Type::getInt32Ty(MyGlobalContext), numeric_val);
}

llvm::Value *VariableAST::Codegen() {
  llvm::Value *V = Named_Values[Var_Name];
  return V ? V : 0;
}

llvm::Value *BinaryAST::Codegen() {
  llvm::Value *L = LHS->Codegen();
  llvm::Value *R = RHS->Codegen();
  if (L == 0 || R == 0)
    return 0;

  switch (atoi(Bin_Operator.c_str())) {
  case '+':
    return Builder.CreateAdd(L, R, "addtmp");
  case '-':
    return Builder.CreateSub(L, R, "subtmp");
  case '*':
    return Builder.CreateMul(L, R, "multmp");
  case '/':
    return Builder.CreateUDiv(L, R, "divtmp");
  default:
    return 0;
  }
}

llvm::Value *FunctionCallAST::Codegen() {
  llvm::Function *CalleeF = Module_Ob->getFunction(Function_Callee);

  std::vector<llvm::Value *> ArgsV;
  for (unsigned i = 0, e = Function_Arguments.size(); i != e; ++i) {
    ArgsV.push_back(Function_Arguments[i]->Codegen());
    if (ArgsV.back() == 0)
      return 0;
  }

  return Builder.CreateCall(CalleeF, ArgsV, "calltmp");
}

llvm::Function *FunctionDeclAST::Codegen() {
  std::vector<llvm::Type *> Integers(Arguments.size(),
                               llvm::Type::getInt32Ty(MyGlobalContext));
  llvm::FunctionType *FT =
      llvm::FunctionType::get(llvm::Type::getInt32Ty(MyGlobalContext), Integers, false);
  llvm::Function *F =
      llvm::Function::Create(FT, llvm::Function::ExternalLinkage, Func_Name, Module_Ob);

  if (F->getName() != Func_Name) {
    F->eraseFromParent();
    F = Module_Ob->getFunction(Func_Name);

    if (!F->empty())
      return 0;

    if (F->arg_size() != Arguments.size())
      return 0;
  }

  unsigned Idx = 0;
  for (llvm::Function::arg_iterator Arg_It = F->arg_begin(); Idx != Arguments.size();
       ++Arg_It, ++Idx) {
    Arg_It->setName(Arguments[Idx]);
    Named_Values[Arguments[Idx]] = Arg_It;
  }

  return F;
}

llvm::Function *FunctionDefnAST::Codegen() {//由函数声明 Codegen 和 函数体的 binary op Codegen拼出来的
  Named_Values.clear();

  llvm::Function *TheFunction = Func_Decl->Codegen();
  if (TheFunction == 0)
    return 0;

  llvm::BasicBlock *BB = llvm::BasicBlock::Create(MyGlobalContext, "entry", TheFunction);
  Builder.SetInsertPoint(BB);

  if (llvm::Value *RetVal = Body->Codegen()) {//Body->Codegen 调用 binary op Codegen
    Builder.CreateRet(RetVal);
    verifyFunction(*TheFunction);
    return TheFunction;
  }

  TheFunction->eraseFromParent();
  return 0;
}

static void HandleDefn() {
  if (FunctionDefnAST *F = func_defn_parser()) {
    if (llvm::Function *LF = F->Codegen()) {
    }
  } else {
    next_token();
  }
}

static void HandleTopExpression() {
  if (FunctionDefnAST *F = top_level_parser()) {
    if (llvm::Function *LF = F->Codegen()) {
    }
  } else {
    next_token();
  }
}

static void Driver() {// 完成对全部源文件的读入和 tokenize
  while (1) {
    switch (Current_token) {
    case EOF_TOKEN:// 源码文件结束,返回
      return;
    case ';':// 遇到;时,忽略,继续下一个 token
      next_token();
      break;
    case DEF_TOKEN:// 遇到 def token,接下来是构建函数定义
      HandleDefn();
      break;
    default:
      HandleTopExpression();//
      break;
    }
  }
}

extern "C" double putchard(double X) {
  putchar((char)X);
  return 0;
}

int main(int argc, char *argv[])
{
  llvm::LLVMContext &Context = MyGlobalContext;
  init_precedence();// 运算符优先级

  file = fopen(argv[1], "r");

  if (file == 0) {
    printf("Could not open file\n");
  }

  next_token();
  Module_Ob = new llvm::Module("my compiler", Context);
  Driver();
  Module_Ob->print(llvm::outs(), nullptr);

  return 0;
}

如果是比较久的llvm版本,比如llvm-3.5 则将倒数第2行代码替换成:

  Module_Ob->dump();

2, Makefile

LLVM_CONFIG ?= llvm-config
#CXX := clang++
ifndef VERBOSE
QUIET :=@
endif

SRC_DIR ?= $(PWD)
LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags) 
COMMON_FLAGS = -Wall -Wextra
CXXFLAGS += $(COMMON_FLAGS) $(shell $(LLVM_CONFIG) --cxxflags)
LCXX :=$(shell $(LLVM_CONFIG) --cxxflags)
CPPFLAGS += $(shell $(LLVM_CONFIG) --cppflags) -I$(SRC_DIR)

CLANGLIBS = \
  -Wl,--start-group \
  -lclang \
  -lclangFrontend \
  -lclangDriver \
  -lclangSerialization \
  -lclangParse \
  -lclangSema \
  -lclangAnalysis \
  -lclangEdit \
  -lclangAST \
  -lclangLex \
  -lclangBasic \
  -Wl,--end-group

LLVMLIBS = $(shell $(LLVM_CONFIG) --libs)

PROJECT = toy
PROJECT_OBJECTS = toy.o

default: $(PROJECT)

%.o : $(SRC_DIR)/%.cpp
	@echo Compiling $*.cpp
	$(QUIET)$(CXX) -g -c $(CPFLAGS) $(CXXFLAGS) $<

$(PROJECT) : $(PROJECT_OBJECTS) 
	@echo Linking $@
	$(QUIET)$(CXX) -g -o $@ $(LDFLAGS) $^ $(CLANGLIBS) $(LLVMLIBS) -lncurses

.PHONY: clean
clean:
	$(QUIET)rm -f $(PROJECT) $(PROJECT_OBJECTS)


.PHONY: echo
echo:
	@echo "CXX 	is	$(CXX)"
	@echo "LDFLAGS 	is	$(LDFLAGS)}"
	@echo "CXXFLAGS	is	$(CXXFLAGS)"
	@echo "CPPFLAGS	is	$(CPPFLAGS)"
	@echo "SRC_DIR	is	$(SRC_DIR)"



3, 运行效果

本运行测试是在llvm-18环境测试,更低的版本也没问题,指示如前所述,更改一句源码为dump()即可。

test case:

hello.t

def add (x y)
x + ( y +  77 );

实际的编译命令如下:

g++ -g -c  -Wall -Wextra -I/home/hipper/llvm_3_4_0_ex/browse_llvm_17/local_d/include -std=c++17   -fno-exceptions -funwind-tables -fno-rtti -D_GNU_SOURCE -D_DEBUG -D_GLIBCXX_ASSERTIONS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS /home/hipper/llvm_3_4_0_ex/browse_llvm_17/ex/toy_cooboo/toy.cpp
echo Linking toy
g++ -g -o toy -L/home/hipper/llvm_3_4_0_ex/browse_llvm_17/local_d/lib   toy.o -Wl,--start-group -lclang -lclangFrontend -lclangDriver -lclangSerialization -lclangParse -lclangSema -lclangAnalysis -lclangEdit -lclangAST -lclangLex -lclangBasic -Wl,--end-group -lLLVMWindowsManifest -lLLVMXRay -lLLVMLibDriver -lLLVMDlltoolDriver -lLLVMTextAPIBinaryReader -lLLVMCoverage -lLLVMLineEditor -lLLVMNVPTXCodeGen -lLLVMNVPTXDesc -lLLVMNVPTXInfo -lLLVMX86TargetMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMX86Desc -lLLVMX86Info -lLLVMOrcDebugging -lLLVMOrcJIT -lLLVMWindowsDriver -lLLVMMCJIT -lLLVMJITLink -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMDWP -lLLVMDebugInfoLogicalView -lLLVMDebugInfoGSYM -lLLVMOption -lLLVMObjectYAML -lLLVMObjCopy -lLLVMMCA -lLLVMMCDisassembler -lLLVMLTO -lLLVMPasses -lLLVMHipStdPar -lLLVMCFGuard -lLLVMCoroutines -lLLVMipo -lLLVMVectorize -lLLVMLinker -lLLVMInstrumentation -lLLVMFrontendOpenMP -lLLVMFrontendOffloading -lLLVMFrontendOpenACC -lLLVMFrontendHLSL -lLLVMFrontendDriver -lLLVMExtensions -lLLVMDWARFLinkerParallel -lLLVMDWARFLinkerClassic -lLLVMDWARFLinker -lLLVMGlobalISel -lLLVMMIRParser -lLLVMAsmPrinter -lLLVMSelectionDAG -lLLVMCodeGen -lLLVMTarget -lLLVMObjCARCOpts -lLLVMCodeGenTypes -lLLVMIRPrinter -lLLVMInterfaceStub -lLLVMFileCheck -lLLVMFuzzMutate -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoBTF -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMIRReader -lLLVMAsmParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMFuzzerCLI -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMTargetParser -lLLVMTableGen -lLLVMSupport -lLLVMDemangle -lncurses

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1514876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java Day9 Stream流

Stream流 1、认识2、Stream流使用步骤3、如何获取Stream流4.Stream流的中间方法5、 Stream流终结方法 1、认识 2、Stream流使用步骤 3、如何获取Stream流 //list获取stream流List<String> listnew ArrayList<>();Collections.addAll(list,"崔十一","…

【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列二:Fast R-CNN图文详解

RCNN算法详解&#xff1a;【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列一&#xff1a;R-CNN图文详解 学习视频&#xff1a;Faster RCNN理论合集 Fast RCNN 概念辨析 1. RoI 在Fast R-CNN中&#xff0c;RoI&#xff08;Region of Interest&#xff0c;感兴…

Python导入类说一说

要在Python中导入一个类&#xff0c;需要使用import关键字。 详细去看下面的代码 1、多例类 class Restaurant:餐馆类def __init__(self,restaurant_name,cuisine_type):#类的属性self.restaurant_name restaurant_nameself.cuisine_type cuisine_type# self.stregth_leve…

Python网络基础爬虫-python基本语法

文章目录 逻辑语句if,else,elifforwhile异常处理 函数与类defpassclass 逻辑语句 熟悉C/C语言的人们可能很希望Python提供switch语句&#xff0c;但Python中并没有这个关键词&#xff0c;也没有这个语句结构。但是可以通过if-elif-elif-…这样的结构代替&#xff0c;或者使用字…

解决JVM进程被系统杀掉问题

背景 服务A在测试环境&#xff0c;隔几个小时接口就无法访问。登录机器查看&#xff0c;发现进程已经没了。大致猜想是进程使用的内存或CPU资源使用太多&#xff0c;导致被系统kill。 问题定位 使用dmesg命令查看进程被kill的详情。 > dmesg --time-format iso2024-03-0…

【Python如何与电脑玩石头剪刀布游戏】

1、石头剪刀布Python代码如下&#xff1a; import random while True:a random.randint(0, 2)b int(input("请输入一个数字&#xff08;0石头, 1剪刀, 2布&#xff09;: "))c [石头, 剪刀, 布]if b ! 0 and b ! 1 and b ! 2:print("傻子&#xff0c;你出错了…

五子棋小游戏(sut实验报告)

实验目的 实现人与人或人与电脑进行五子棋对弈 实验内容 启动游戏&#xff0c;显示游戏参数设置界面&#xff0c;用户输入参数后进入游戏界面&#xff0c;显示棋盘及双方博弈过程&#xff0c;游戏过程中可选择退出游戏。判定一方获胜后结束本局游戏&#xff0c;可选择继续下…

S4 Hana SD -信贷管理 - 02

2.3 给信贷控制范围分配公司代码 TCODE: SPRO 配置路径:IMG > 企业结构 > 分配 > 财务会计 > 给信贷控制区分配公司代码 配置路径截图: 公司:被分配的公司代码。 公司名称&城市:已在公司代码数据中维护。 CCAR:分配的信贷控制范围。 覆盖CC范围:如…

InstantID Zero-shot Identity-Preserving Generation in Seconds

InstantID: Zero-shot Identity-Preserving Generation in Seconds TL; DR&#xff1a;InstantID IP-Adapter (Face) ControlNet&#xff0c;实现了具有较高保真度的人脸 ID 生成。 方法 InstantID 想做到的事情是&#xff1a;给定一张参考人脸 ID 图片&#xff0c;生成该…

专升本 C语言笔记-07 逗号运算符

1.逗号表达式的用法 就是用逗号隔开的多个表达式。逗号表达式&#xff0c;从左向右依次执行。 2.逗号表达式的特性 2.1.当没有括号时&#xff0c;第一个表达式为整个表达式的值。 代码 int x 3,y 5,a 0; a x,y; printf("a %d",a); 说明:因为逗号优先级最低,会…

利用Python进行网络爬虫:Beautiful Soup和Requests的应用【第131篇—Beautiful Soup】

利用Python进行网络爬虫&#xff1a;Beautiful Soup和Requests的应用 在网络数据变得日益丰富和重要的今天&#xff0c;网络爬虫成为了获取和分析数据的重要工具之一。Python作为一种强大而灵活的编程语言&#xff0c;在网络爬虫领域也拥有广泛的应用。本文将介绍如何使用Pyth…

【智能硬件、大模型、LLM 智能音箱】MBO:基于树莓派、ChatGPT 的桌面机器人

MAKER:David Packman/译:趣无尽(转载请注明出处) 这是国外 Maker David Packman 制作的基于树莓派机器人 MBO,该机器人的外观设计灵感来自动漫 Adventure Time 中的机器人 MBO。它具有强大的交互功能,可实现脱机唤醒词检测、调用 ChatGPT 3.5 进行聊天、机器视觉对图像进…

解决Git:Author identity unknown Please tell me who you are.

报错信息&#xff1a; 意思&#xff1a; 作者身份未知 ***请告诉我你是谁。 解决办法&#xff1a; git config --global user.name "你的名字"git config --global user.email "你的邮箱"

Android 15 首个开发者预览版到来

作者 / 工程副总裁 Dave Burke Android 15 的首个开发者预览版现已发布&#xff0c;以便各位开发者能与我们通力协作&#xff0c;打造更优秀的 Android 平台。 在 Android 15 中&#xff0c;我们继续致力于打造一个既能提升工作效率&#xff0c;又能提供全新功能的平台。这些新…

蓝桥杯-模拟-4402. 刷题统计

题目 思路 代码 a,b,nmap(int,input().split()) sa*5b*2 resn//s*7 # 存在周期 d[a,a,a,a,a,b,b] n%s i0 while n>0: # 对剩余数量进行枚举&#xff0c;如果等于0&#xff0c;相当于还会再进去加一天n-d[i]i1res1 print(res)

es 聚合操作(一)

前言 Elasticsearch除搜索以外&#xff0c;提供了针对ES 数据进行统计分析的功能。聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 衣服品牌的受欢迎程度这些衣服的平均价格、最高价格、最低价格这些衣服的每天、每月销量如何 使用…

cpp qt 一个奇怪的bug

今天在用cpp qt的时候发现了一个奇怪的东西 这是我的源代码 #include "mywidget.h" #include <QPushButton>myWidget::myWidget(QWidget *parent): QWidget(parent) {QPushButton * btn1 new QPushButton;btn1->show();btn1->setParent(this);btn1-&g…

在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?

目录 一、分布式寻址算法 1. hash 算法 2. 一致性 hash 算法 3. Redis cluster 的 hash slot 算法 二、Redis cluster 的高可用与主备切换原理 1. 判断节点宕机 2. 从节点过滤 3. 从节点选举 4. 与哨兵比较 一、分布式寻址算法 hash 算法(大量缓存重建) 一致性 hash…

【代码随想录 | 链表 02】反转链表

文章目录 2.反转链表2.1题目2.2解法2.2.1双指针法2.2.2递归法 2.反转链表 2.1题目 206.反转链表——力扣链接 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例一&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;…

重建大师出现图中密集匹配失败的情况,是什么原因导致的?

可以检查瓦块是否位于测区边缘&#xff0c;边缘瓦块可以忽略&#xff1b;如果是中间区域的话&#xff0c;可能中间文件有异常&#xff0c;可以新建个reconstruction&#xff0c;然后单独提交失败的瓦块。 重建大师是一款专为超大规模实景三维数据生产而设计的集群并行处理软件&…