首頁 後端開發 PHP問題 php 實作類似pyhon中的Construct函式庫的功能(三)實作if-else功能

php 實作類似pyhon中的Construct函式庫的功能(三)實作if-else功能

Aug 19, 2019 pm 04:48 PM

引言

在文章《php 實作類似pyhon中的Construct函式庫的功能(一)基本設計思路》介紹了用php解析二進位資料的基本思路

在文章《php 實作類似pyhon中的Construct函式庫的功能(二)實作適配器功能》說明如何實作適配器功能。

以上兩篇都是對靜態資料結構進行解析。接下來要逐步實作動態資料結構的解析。也就是說資料結構的定義與上下文有關,要在資料解析時才能真正確定。

推薦相關PHP影片教學:https://www.php.cn/course/list/29/type/2.html

這次要實現的是if-else功能。

基本想法

1,修改詞法分析規則,使其可以接受if,else關鍵字

#2,修改語法分析規則,使其可以接受if,else語句

3,修改編碼器,產生可運行的php目標程式碼

#其中主要的工作內容是修改語法分析規則文件。

實作內容

準備解析的結構體定義檔

struct student
{
  char name[2];
  int num;
  if(num.value==1 ){
  	int age;
  }else{
  	char addr[3];
  } 
  
};
登入後複製

為了聚焦在if-else功能的實作上,這次只定義了一個結構體 student。與先前的靜態結構體定義最大的不同,在於如下定義

  if(num.value==1 ){
  	int age;
  }else{
  	char addr[3];
  }
登入後複製

如果解析過程中,num字段的值為1則定義一個age字段,否則定義一個addr字段。

詞法規則檔改動不大,增加對if,else關鍵字的匹配就可以了

		['/^if\b/','_if'		,'i'],
		['/^else\b/','_else'	,'e'],
登入後複製

語法規則檔需改動的地方比較多

首先,增加一個符號移進時的處理函數。在前面介紹的處理函數都是在歸約時被調用,但對於更複雜的情形,有必要在移進時也進行處理。

先看一下script_parser.php中的移進處理的基本操作

//移进
	private function shift($token){

		//处理记号栈
		$this->tokenStack= $this->tokenStack.$token[0];
		
		if($this->debugMode){
			echo I('srcline:'),$token[2],I(' shifted :'),$this->tokenStack,"\n";
		}
		
		//处理语法栈,栈中元素为[记号名,记号值,起始位置,结束位置,[附加信息]]
		array_push($this->syntaxStack, [$token[0],$token[1],$this->tokenIndex-1,$this->tokenIndex-1,[]]);

		//调用规则处理中的移进处理函数
		$extra=$this->rulesHandler->handleShift($token[0],$this->syntaxStack,$this->coder);

		//在栈中保存附加信息
		$this->syntaxStack[count($this->syntaxStack)-1][TokenExtraIndex]=$extra;
						
	}
登入後複製

其中放置了一個鉤子函數的呼叫

//调用规则处理中的移进处理函数
		$extra=$this->rulesHandler->handleShift($token[0],$this->syntaxStack,$this->coder);
登入後複製

在語法規則處理的基底類別中定義了一個空的handleShift 方法。
我們需要做的就是在語法規則處理類別中重載 handleShift 方法。

//处理移进,返回附加信息数组
function handleShift($tokenName,$stack,$coder){

	if($tokenName=='_if'){
		//插入一个空行,空行所在的序号存入附加信息数组,以后可以替换为正确的内容
		return [$coder->pushLine('')];
	}

	if($tokenName=='_else'){
		//插入一个空行,空行所在的序号存入附加信息数组,以后可以替换为正确的内容
		return [$coder->pushLine('')];
	}	
	return [];
}
登入後複製

從以上程式碼可以看出,在編譯過程中,如果發生了if 或else的移進操作,則在要產生的目標程式碼中插入一個空白行,並且將這個空白行的位址儲存起來,在對if-else語句進行歸約時用最終確定的內容取代空白行。

為什麼要這麼做呢?

因為 if 語句中,if 或else的區塊語句先完成歸約,整個if語句在此之後才完成歸約。而目標程式碼的生成是邊歸約邊生成,所以要先為if 或else搶佔一個位置。

下而是if-else的語法規則處理函數

// if          {{{

function _ifStatement_0_ifStatement_else_blockStatement($stack,$coder){	

	//取出_else 记号中保存的空白行所在的地址,替换为正确的内容
	$t1= $this->topItem($stack,2);
	$lineIndex=$t1[TokenExtraIndex][0];

	$content='else{';

	$coder->resetLine($lineIndex,$content);	

	$coder->pushLine('}');	

	return $this->pass($stack,3);
}

function _ifStatement_0_if_wholeExpression_blockStatement($stack,$coder){
	
	//取出_if 记号中保存的空白行所在的地址,替换为正确的内容
	$t1= $this->topItem($stack,3);
	$lineIndex=$t1[TokenExtraIndex][0];

	$t2= $this->topItem($stack,2);
	$condtionExp=$t2[TokenValueIndex];

	$content='if'.$condtionExp.'{';

	$coder->resetLine($lineIndex,$content);	

	$coder->pushLine('}');

	return $this->pass($stack,3);
}

//  if        }}}
登入後複製

著重分析if語句的處理,處理函數如下所示

function _ifStatement_0_if_wholeExpression_blockStatement($stack,$coder){
	
	//取出_if 记号中保存的空白行所在的地址,替换为正确的内容
	$t1= $this->topItem($stack,3);
	$lineIndex=$t1[TokenExtraIndex][0];

	$t2= $this->topItem($stack,2);
	$condtionExp=$t2[TokenValueIndex];

	$content='if'.$condtionExp.'{';

	$coder->resetLine($lineIndex,$content);	

	$coder->pushLine('}');

	return $this->pass($stack,3);
}
登入後複製

以下語句

$t1= $this->topItem($stack,3);
登入後複製
登入後複製

的意思是從目前語法堆疊的堆疊頂端取出元素,第2個參數3 顯示是從堆疊頂端計數,取第三個元素。計數時從1開始

_ifStatement_0_if_wholeExpression_blockStatement

所包含的語法規則就是:

當堆疊頂部出現了_if, _wholeExpression, _blockStatement 三個符號是,這三個符號就可以歸為_ifStatement_0 是一個分隔串,分隔語法規則的左部與右部。

在ados腳本語言中採用了一種設計技巧,就是以產生式(語法規則)做為函數的名稱,語法規則與語法規則處理函數合而為一。

這樣做的好處是不用分別維護語法規則與語法規則處理函數,不用時時刻刻保持兩者的同步。

$t1= $this->topItem($stack,3);
登入後複製
登入後複製

取出的是_if 這個符號在語法堆疊中對應的內容。前面已經介紹過,在_if 符號移進時,插入了一個空白行,在符加資訊數組中保存了這個空白行的位址。這時將其取出。

$t2= $this->topItem($stack,2);
$condtionExp=$t2[TokenValueIndex];
登入後複製

_wholeExpression 對應的語法堆疊元素中取出對應的條件表達式,組成一個完整的內容後取代先前的空白行。

請留意,這時if語句區塊內容已經寫入到目標程式碼中。
接下來補上一個if語句區塊的結束標記 ‘}’ 就OK了。

接下來實作對屬性運算子. 的處理,在例子中就是對

num.value
登入後複製

這種形式的表達式的處理

語法規則處理函數如下

function _term_0_term_dot_iden($stack,$coder){  
		
	$t1= $this->topItem($stack,3);
	$obj = $t1[TokenValueIndex];
	$t2= $this->topItem($stack,1);
	$var = $t2[TokenValueIndex];
	$exp = '$'.$obj.'[\''.$var.'\']';

	return [$exp,[]];
}
登入後複製

從範例來看,原始碼是num.value ,最終得到的目標程式碼是$num['value']
也就是將類別C的原始碼變成了php程式碼

接下來還要實現對比較運算子== 的處理:

function _wholeExpression_0_wholeExpression_bieq_expression($stack,$coder){
	
	return $this->biOpertors($stack,3,'==',1,$coder);
}

//二元操作符的通用处理函数
function biOpertors($stack,$op1Index,$op,$op2Index,$coder){

	$t1= $this->topItem($stack,$op1Index);
	$exp1=$t1[TokenValueIndex];

	$t2= $this->topItem($stack,$op2Index);
	$exp2=$t2[TokenValueIndex];

	$s=$exp1.$op.$exp2;
	return [$s,[]];
}
登入後複製

下面是完整的語法規則處理文件的內容

0){
		return intval($extraArray[0]);
	}else{
		return 0;
	}
}
//二元操作符的通用处理函数
function biOpertors($stack,$op1Index,$op,$op2Index,$coder){
	$t1= $this->topItem($stack,$op1Index);
	$exp1=$t1[TokenValueIndex];
	$t2= $this->topItem($stack,$op2Index);
	$exp2=$t2[TokenValueIndex];
	$s=$exp1.$op.$exp2;
	return [$s,[]];
}
//处理移进,返回附加信息数组
function handleShift($tokenName,$stack,$coder){

	if($tokenName=='_if'){
		//插入一个空行,空行所在的序号存入附加信息数组,以后可以替换为正确的内容
		return [$coder->pushLine('')];
	}

	if($tokenName=='_else'){
		//插入一个空行,空行所在的序号存入附加信息数组,以后可以替换为正确的内容
		return [$coder->pushLine('')];
	}	
	return [];
}
//语法规则处理函数名由规则右边部分与规则左边部分拼接而成
//语法规则定义的先后决定了归约时匹配的顺序,要根据实际的语法安排
//如果不熟悉语法,随意调整语法规则的先后次序将有可能导致语法错误
// struct list  {{{
function _structList_0_structList_struct($stack,$coder){
	$coder->pushBlockTail();
	return ['#',[]];
}
function _structList_0_struct($stack,$coder){	
	$coder->pushBlockTail();
	return ['#',[]];
}
// struct list  }}}
// struct   {{{
function _struct_0_structName_blockStatement_semi($stack,$coder){
	$t1= $this->topItem($stack,3);
	$structName = 	$t1[TokenValueIndex];
	$t2= $this->topItem($stack,2);
	$extraArray=$t2[TokenExtraIndex];
	return [$structName,$extraArray];
}
// struct   }}}
// struct name     {{{
function _structName_0_strukey_iden($stack,$coder){  
	$t1= $this->topItem($stack,1);
	$structName = 	$t1[TokenValueIndex];
	$coder->pushBlockHeader($structName);	
	return $this->pass($stack,1);
}
// struct name     }}}
// blockStatement    {{{
function _blockStatement_0_lcb_statementList_rcb($stack,$coder){
	return $this->pass($stack,2);
}
// blockStatement    }}}
// statement list  {{{
function _statementList_0_statementList_statement($stack,$coder){	
	return $this->pass($stack,1);
}
function _statementList_0_statement($stack,$coder){
	//此处0表示statementList是上一级节点,要做特殊处理
	return $this->pass($stack,1);
}
// statement list  }}}
// statement       {{{
function _statement_0_wholeExpression_semi($stack,$coder){
	$t1= $this->topItem($stack,2);
	$elementName = 	$t1[TokenValueIndex];	
	$coder->pushCheckBody($elementName);	
	return $this->pass($stack,2);
}
function _statement_0_ifStatement($stack,$coder){
	return $this->pass($stack,1);
}
// statement        }}}
// if          {{{
function _ifStatement_0_ifStatement_else_blockStatement($stack,$coder){	
	//取出_else 记号中保存的空白行所在的地址,替换为正确的内容
	$t1= $this->topItem($stack,2);
	$lineIndex=$t1[TokenExtraIndex][0];
	$content='else{';
	$coder->resetLine($lineIndex,$content);	
	$coder->pushLine('}');	
	return $this->pass($stack,3);
}
function _ifStatement_0_if_wholeExpression_blockStatement($stack,$coder){
	//取出_if 记号中保存的空白行所在的地址,替换为正确的内容
	$t1= $this->topItem($stack,3);
	$lineIndex=$t1[TokenExtraIndex][0];
	$t2= $this->topItem($stack,2);
	$condtionExp=$t2[TokenValueIndex];
	$content='if'.$condtionExp.'{';
	$coder->resetLine($lineIndex,$content);	
	$coder->pushLine('}');
	return $this->pass($stack,3);
}
//  if        }}}
// function expression {{{
//函数表达式
function _term_0_funcTerm($stack,$coder){  
	$t1= $this->topItem($stack,1);
	$funcName=$t1[TokenValueIndex];
	$paraArray=$t1[TokenExtraIndex]; 
	$paras = implode(",", $paraArray);
	$exp = $funcName.'('.$paras.')';	
	return [$exp,[]];
}
function _funcTerm_0_funcExpLp_rp($stack,$coder){  
	return $this->pass($stack,2);
}
function _funcTerm_0_funcExpLeft_rp($stack,$coder){  
	return $this->pass($stack,2);
}
function _funcExpLeft_0_funcExpLeft_comma_expression($stack,$coder){  		
	$t1= $this->topItem($stack,3);
	$t2= $this->topItem($stack,1);
	//函数的参数列表存放在附加信息中
	$paraArray=$t1[TokenExtraIndex]; 
	array_push($paraArray, $t2[TokenValueIndex]);
	return [$t1[TokenValueIndex],$paraArray];
}
function _funcExpLeft_0_funcExpLp_expression($stack,$coder){  
	$t1= $this->topItem($stack,2);
	$t2= $this->topItem($stack,1);
	//函数的参数列表存放在附加信息中
	$paraArray=$t1[TokenExtraIndex]; 
	array_push($paraArray, $t2[TokenValueIndex]);
	return [$t1[TokenValueIndex],$paraArray];
}
function _funcExpLp_0_iden_lp($stack,$coder){  
	return $this->pass($stack,2);
}
// function expression }}}
// whole Expression  {{{
function _wholeExpression_0_wholeExpression_bieq_expression($stack,$coder){	
	return $this->biOpertors($stack,3,'==',1,$coder);
}
function _wholeExpression_0_expression($stack,$coder){	
	return $this->pass($stack,1);
}
// whole Expression      }}}
//    Expression         {{{
//表达式可以进行管道运算
function _expression_0_expression_pipe_factor($stack,$coder){
	$t1= $this->topItem($stack,1);
	$handlerName = 	$t1[TokenValueIndex];
	$t2= $this->topItem($stack,3);
	$elementName = 	$t2[TokenValueIndex];
	$coder->pushPipeBody($handlerName,$elementName);	
	return $this->pass($stack,3);
}
function _expression_0_double_factor($stack,$coder){	
	$t1= $this->topItem($stack,1);
	$elementName = 	$t1[TokenValueIndex];
	$parseFuncName = 'parseDouble';	
	$coder->pushParseBody($parseFuncName,$elementName);		
	return $this->pass($stack,1);
}
function _expression_0_float_factor($stack,$coder){	
	$t1= $this->topItem($stack,1);
	$elementName = 	$t1[TokenValueIndex];
	$parseFuncName = 'parseFloat';	
	$coder->pushParseBody($parseFuncName,$elementName);		
	return $this->pass($stack,1);
}
function _expression_0_char_factor($stack,$coder){
	$t1= $this->topItem($stack,1);
	$elementName = 	$t1[TokenValueIndex];
	$size = $this->elementSize($t1[TokenExtraIndex]);	
	$parseFuncName = 'parseFixStr';	
	$coder->pushParseBody($parseFuncName,$elementName,$size);		
	return $this->pass($stack,1);
}
function _expression_0_int_factor($stack,$coder){	
	$t1= $this->topItem($stack,1);
	$elementName = 	$t1[TokenValueIndex];
	$parseFuncName = 'parseInt';	
	$coder->pushParseBody($parseFuncName,$elementName,4);		
	return $this->pass($stack,1);
}
function _expression_0_factor($stack,$coder){	
	return $this->pass($stack,1);
}
//   Expression         }}}
// factor       {{{
function _factor_0_term($stack,$coder){
	
	return $this->pass($stack,1);
}

//  factor        }}}

//   term    {{{
function _term_0_lp_wholeExpression_rp($stack,$coder){
	
	$t1= $this->topItem($stack,2);
	$s='('.$t1[TokenValueIndex].')';
	return [$s,[]];
}
function _term_0_term_dot_iden($stack,$coder){  
		
	$t1= $this->topItem($stack,3);
	$obj = $t1[TokenValueIndex];
	$t2= $this->topItem($stack,1);
	$var = $t2[TokenValueIndex];
	$exp = '$'.$obj.'[\''.$var.'\']';

	return [$exp,[]];
}
function _term_0_iden($stack,$coder){
	$t1= $this->topItem($stack,1);
	//未指定数据长度时将长度值设为0 
	$valLen = 	'0';	
	$t2= $this->topItem($stack,2);
	return [$t1[TokenValueIndex],[$valLen]];	
}
function _term_0_num($stack,$coder){
	return $this->pass($stack,1);
}
function _term_0_array($stack,$coder){
	return $this->pass($stack,1);
}
//   term     }}}
// array 	  {{{
function _array_0_arrayLb_num_rb($stack,$coder){
	$t1= $this->topItem($stack,2);
	$valLen = 	$t1[TokenValueIndex];	
	
	$t2= $this->topItem($stack,3);
	//将数据长度放入附加信息 
	return [$t2[TokenValueIndex],[$valLen]];		
}
function _arrayLb_0_iden_lb($stack,$coder){  
	return $this->pass($stack,2);
}
// array 	   }}}
}// end of class
登入後複製

下面是改進過後的編碼器的內容

<?php
/*!
 * structwkr编码器,
 *
 * 45022300@qq.com
 * Version 0.9.0
 *
 * Copyright 2019, Zhu Hui
 * Released under the MIT license
 */

namespace Ados;

require_once __SCRIPTCORE__.&#39;coder/base_coder.php&#39;;
require_once __STRUCT_PARSE_TEMP__.&#39;templateReplaceFuncs.php&#39;;


class StructwkrCoder extends BaseCoder{

	public function __construct($engine)
	{
		if($engine){ 
			$this->engine = $engine;
		}else{ 
			exit(&#39;the engine is not valid in StructwkrCoder construct.&#39;);
		}
	}

	//编译得到的最终结果
	public function codeLines(){
		if(count($this->codeLines)<1){
			return &#39;&#39;;
		}
		$script=&#39;&#39;;		
		for ($i=0;$i< count($this->codeLines);$i+=1) {
			$script.=$this->codeLines[$i];
		}	
		return $script;
	}	

	//输出编译后的结果
	public function printCodeLines(){
		echo $this->codeLines();	
	}

	//添加一个块解析函数头
	public function pushLine($content){		
		array_push($this->codeLines, $content);
		$lineIndex=$this->lineIndex;
		$this->lineIndex+=1;
		return $lineIndex;		
	}

	//重置一行的内容
	public function resetLine($lineIndex,$line){

		$this->codeLines[$lineIndex]=$line;
	}

	//添加一个块解析函数头
	public function pushBlockHeader($structName){
		$structName=ucfirst($structName);
		$content = makeBlockHeader($structName);		
		return $this->pushLine($content);		
	}

	//添加一个块解析函数体
	public function pushParseBody($parseFuncName,$filedName=&#39;&#39;,$filedSize=0){
		$content = makeParseBody($parseFuncName,$filedName,$filedSize);
		return $this->pushLine($content);
	}

	//添加一个管道处理
	public function pushPipeBody($handler,$filedName=&#39;&#39;){
		$content = makePipeBody($handler,$filedName);		
		return $this->pushLine($content);
	}

	//添加一个检查结果值的body
	public function pushCheckBody($filedName=&#39;&#39;){
		$content = makeCheckBody($filedName);
		return $this->pushLine($content);
	}

	//添加一个块解析类的tail
	public function pushBlockTail(){
		$content = makeblockTail();
		return $this->pushLine($content);
	}	

}
登入後複製

实现结果

自动生成的测试文件如下

<?php

namespace Ados;

//加载常量定义文件
require_once &#39;const.php&#39;;
require_once __STRUCT_PARSE_TEMP__.&#39;templateBuidinFuncs.php&#39;;
require_once __STRUCT_PARSE_ADAPTER__.&#39;int2str.adapter.php&#39;;
require_once __STRUCT_PARSE_ADAPTER__.&#39;intoffset.adapter.php&#39;;

$context[&#39;pos&#39;]=0;
$context[&#39;data&#39;]="\x41\x43\x01\x00\x00\x00\x02\x00\x00\x00\x41\x42\x43\x41\x42\x01\x00\x00\x00\x41\x42\x43";

$expRes = Student::parse($context);
$context[&#39;pos&#39;]+=$expRes[&#39;size&#39;];
print_r($expRes);

/*
$expRes = Teacher::parse($context);
$context[&#39;pos&#39;]+=$expRes[&#39;size&#39;];
print_r($expRes);
*/



class Student{

	static function parse($context,$size=0){
		$valueArray=[];
		$totalSize = 0;
	
		$name = parseFixStr($context,2);
	
		if($name[&#39;error&#39;]==0){
			$filed = &#39;name&#39;;
			if($filed){
				$valueArray[$filed]=$name[&#39;value&#39;];
			}else{
				$valueArray[]=$name[&#39;value&#39;];
			}		
			$context[&#39;pos&#39;]+=$name[&#39;size&#39;];
			$totalSize+= $name[&#39;size&#39;];
		}else{
			return [&#39;value&#39;=>False,&#39;size&#39;=>0,&#39;error&#39;=>$name[&#39;error&#39;],&#39;msg&#39;=>$name[&#39;msg&#39;]];
		}
	
		$num = parseInt($context,4);
	
		if($num[&#39;error&#39;]==0){
			$filed = &#39;num&#39;;
			if($filed){
				$valueArray[$filed]=$num[&#39;value&#39;];
			}else{
				$valueArray[]=$num[&#39;value&#39;];
			}		
			$context[&#39;pos&#39;]+=$num[&#39;size&#39;];
			$totalSize+= $num[&#39;size&#39;];
		}else{
			return [&#39;value&#39;=>False,&#39;size&#39;=>0,&#39;error&#39;=>$num[&#39;error&#39;],&#39;msg&#39;=>$num[&#39;msg&#39;]];
		}
	if($num[&#39;value&#39;]==1){
		$age = parseInt($context,4);
	
		if($age[&#39;error&#39;]==0){
			$filed = &#39;age&#39;;
			if($filed){
				$valueArray[$filed]=$age[&#39;value&#39;];
			}else{
				$valueArray[]=$age[&#39;value&#39;];
			}		
			$context[&#39;pos&#39;]+=$age[&#39;size&#39;];
			$totalSize+= $age[&#39;size&#39;];
		}else{
			return [&#39;value&#39;=>False,&#39;size&#39;=>0,&#39;error&#39;=>$age[&#39;error&#39;],&#39;msg&#39;=>$age[&#39;msg&#39;]];
		}
	}else{
		$addr = parseFixStr($context,3);
	
		if($addr[&#39;error&#39;]==0){
			$filed = &#39;addr&#39;;
			if($filed){
				$valueArray[$filed]=$addr[&#39;value&#39;];
			}else{
				$valueArray[]=$addr[&#39;value&#39;];
			}		
			$context[&#39;pos&#39;]+=$addr[&#39;size&#39;];
			$totalSize+= $addr[&#39;size&#39;];
		}else{
			return [&#39;value&#39;=>False,&#39;size&#39;=>0,&#39;error&#39;=>$addr[&#39;error&#39;],&#39;msg&#39;=>$addr[&#39;msg&#39;]];
		}
	}
		return [&#39;value&#39;=>$valueArray,&#39;size&#39;=>$totalSize,&#39;error&#39;=>0,&#39;msg&#39;=>&#39;ok&#39;];
	}
}	
登入後複製

运行测试文件的结果

Array
(
    [value] => Array
        (
            [name] => AC
            [num] => 1
            [age] => 2
        )

    [size] => 10
    [error] => 0
    [msg] => ok
)
登入後複製

对比测试数据

$context[&#39;data&#39;]="\x41\x43\x01\x00\x00\x00\x02\x00\x00\x00\x41\x42\x43\x41\x42\x01\x00\x00\x00\x41\x42\x43";
登入後複製

由于字段 num的值为1,所以接下来产生了一个age字段,而并没有产生addr字段。

结论:if-else功能已经实现并通过了验证。

更多相关问题请访问PHP中文网:https://www.php.cn/

以上是php 實作類似pyhon中的Construct函式庫的功能(三)實作if-else功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)