Created
June 20, 2023 05:03
-
-
Save carrotsword/de3a254e6ace5bd41b37952800d94535 to your computer and use it in GitHub Desktop.
vjet2dts : vjet用のAPI定義から .d.ts を生成する。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package vjet2dts; | |
import java.io.*; | |
import java.nio.file.Paths; | |
import java.util.*; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import java.util.stream.Collectors; | |
class TypeInfo{ | |
/* 型の変換マッピング */ | |
public static Map<String, String> typemap = new HashMap<>(); | |
{ | |
typemap.put("String", "string"); | |
typemap.put("int", "number"); | |
typemap.put("Integer", "number"); | |
typemap.put("long", "number"); | |
typemap.put("Long", "number"); | |
typemap.put("Number", "number"); | |
typemap.put("Object", "any"); | |
typemap.put("Array", "Array<any>"); | |
typemap.put("List", "Array<any>"); | |
typemap.put("Map", "Object"); | |
typemap.put("Request", "JSSPRequest"); | |
} | |
/* 生成元のドキュメントで定義されていない型 */ | |
public static Set<String> undefinedTypes = new HashSet<>(); | |
{ | |
undefinedTypes.add("IAclModelKey"); | |
undefinedTypes.add("ActiveStatusType"); | |
undefinedTypes.add("CategoryKey"); | |
undefinedTypes.add("ContentsType"); | |
undefinedTypes.add("WorkspaceItemEditType"); | |
undefinedTypes.add("WorkflowApplyType"); | |
undefinedTypes.add("WorkflowArticleStatus"); | |
undefinedTypes.add("ArticlePublicityStatus"); | |
undefinedTypes.add("WorkflowApply"); | |
undefinedTypes.add("ArticleReadSearchStatus"); | |
undefinedTypes.add("AclActionType"); | |
undefinedTypes.add("DeliveryType"); | |
undefinedTypes.add("WFActiveMatter"); | |
undefinedTypes.add("FileKey"); | |
undefinedTypes.add("WorkflowMatterState"); | |
undefinedTypes.add("NegoInfo"); | |
undefinedTypes.add("TaskQueryModel"); | |
undefinedTypes.add("ExecutionQueryModel"); | |
undefinedTypes.add("SecondSortOrder"); | |
undefinedTypes.add("ProcessResultType"); | |
undefinedTypes.add("OptionDisplayType"); | |
undefinedTypes.add("StartDayType"); | |
undefinedTypes.add("EventKey"); | |
undefinedTypes.add("GradationType"); | |
undefinedTypes.add("NoticeTargetType"); | |
undefinedTypes.add("ListDisplayMode"); | |
undefinedTypes.add("OperationType"); | |
undefinedTypes.add("FRResultInfo"); | |
undefinedTypes.add("ScheduleConnectorModel"); | |
undefinedTypes.add("PrivateType"); | |
undefinedTypes.add("GradiationType"); | |
undefinedTypes.add("CalendarWeekInfo"); | |
undefinedTypes.add("SecondSortColumn"); | |
undefinedTypes.add("DragOpenType"); | |
undefinedTypes.add("InitialDisplaySpType"); | |
undefinedTypes.add("ReferenceType"); | |
undefinedTypes.add("PersonalSetting"); | |
undefinedTypes.add("CategoryAclEditSetting"); | |
undefinedTypes.add("CategoryDetail"); | |
undefinedTypes.add("OpinionType"); | |
undefinedTypes.add("AuthzAclType"); | |
undefinedTypes.add("DispType"); | |
undefinedTypes.add("CompanyGroupBizKey"); | |
undefinedTypes.add("DepartmentBizKey"); | |
undefinedTypes.add("Term"); | |
undefinedTypes.add("CorporationGroupBizKey"); | |
undefinedTypes.add("ItemCategoryBizKey"); | |
undefinedTypes.add("PublicGroupBizKey"); | |
undefinedTypes.add("Category"); | |
undefinedTypes.add("CategoryAclDisplaySetting"); | |
undefinedTypes.add("Timestamp"); | |
undefinedTypes.add("SummaryType"); | |
undefinedTypes.add("ListDisplayType"); | |
undefinedTypes.add("ReadType"); | |
undefinedTypes.add("DayOfWeekType"); | |
undefinedTypes.add("RepeatType"); | |
undefinedTypes.add("ScheduleType"); | |
undefinedTypes.add("SapClientObject"); | |
undefinedTypes.add("TextType"); | |
undefinedTypes.add("EventType"); | |
undefinedTypes.add("UserOperationType"); | |
undefinedTypes.add("VirtualFile"); | |
undefinedTypes.add("InitialDisplayPcType"); | |
undefinedTypes.add("RelatedType"); | |
} | |
/* 省略可能な引数の定義 */ | |
public static Map<String, String[]> omittableParameterDefs = new HashMap<>(); | |
{ | |
// パラメータの並びは定義順であること(後ろから順に省略するため) | |
omittableParameterDefs.put("DateTimeFormatter#format", new String[]{"locale"}); | |
omittableParameterDefs.put("DateTimeFormatter#parseToDate", new String[]{"locale"}); | |
omittableParameterDefs.put("DateTimeFormatter#parseToDateTime", new String[]{"locale"}); | |
omittableParameterDefs.put("SecureTokenManager#createToken", new String[]{"parameter"}); | |
omittableParameterDefs.put("SecureTokenManager#verify", new String[]{"token", "parameter"}); | |
omittableParameterDefs.put("TenantDatabase#executeByTemplate", new String[]{"params"}); | |
omittableParameterDefs.put("TenantDatabase#executeCallable", new String[]{"params"}); | |
omittableParameterDefs.put("TenantDatabase#execute", new String[]{"params"}); | |
omittableParameterDefs.put("TenantDatabase#fetchByTemplate", new String[]{"params"}); | |
omittableParameterDefs.put("TenantDatabase#fetch", new String[]{"params"}); | |
omittableParameterDefs.put("TenantDatabase#getByteReader", new String[]{"params","callback"}); | |
omittableParameterDefs.put("TenantDatabase#getTextReader", new String[]{"params","callback"}); | |
omittableParameterDefs.put("TenantDatabase#remove", new String[]{"params"}); | |
omittableParameterDefs.put("TenantDatabase#select", new String[]{"params", "length"}); | |
omittableParameterDefs.put("TenantDatabase#update", new String[]{"params"}); | |
omittableParameterDefs.put("HTTPResponse#sendMessageBodyFile", new String[]{"isDelete"}); | |
omittableParameterDefs.put("HTTPResponse#sendError", new String[]{"msg"}); | |
} | |
String type; | |
String originalType; | |
String elementalType; | |
boolean isLengthVariable = false; | |
boolean isTranslated = false; | |
boolean isUndefinedType = false; | |
boolean isPatternUnmatchedType = false; | |
boolean isMappedType = false; | |
public TypeInfo(String typeName){ | |
parse(typeName.trim()); | |
} | |
void parse(String typeName){ | |
originalType = typeName; | |
if(typeName.endsWith("...")) { | |
elementalType = typeName.substring(0, typeName.length() - 3).trim(); | |
type = elementalType; | |
isLengthVariable = true; | |
}else if(typeName.endsWith("[]")){ | |
elementalType = typeName.substring(0, typeName.length() -2).trim(); | |
type = "[" + elementalType + "]"; | |
}else{ | |
elementalType = typeName; | |
type = typeName; | |
} | |
if(typemap.containsKey(elementalType)){ | |
isTranslated = true; | |
isMappedType = true; | |
type = typemap.get(elementalType); | |
}else if(undefinedTypes.contains(elementalType)){ | |
isTranslated = true; | |
isUndefinedType = true; | |
type = "any" ; | |
}else if(elementalType.matches("[^a-zA-Z0-9]")){ | |
isTranslated = true; | |
isPatternUnmatchedType = true; | |
type = "any" ; | |
} | |
} | |
String getType(){ | |
return type; | |
} | |
} | |
class Arg { | |
TypeInfo type; | |
String name; | |
String elementalName; | |
public String getElementalName() { | |
return elementalName; | |
} | |
public void setElementalName(String n) { | |
// 予約語回避 | |
if(n.equals("function")){ | |
n = "func"; | |
} | |
this.elementalName = n; | |
if(this.type.isLengthVariable){ | |
n = "..." + n; | |
} | |
this.name = n; | |
} | |
static class Tuple { | |
TypeInfo type; | |
String name; | |
String elemenalName; | |
} | |
/** | |
* 引数定義のような `Type name` 形式を想定 | |
* @param argDef | |
*/ | |
public Arg(String argDef){ | |
String[] s = argDef.split("\\s"); | |
Arg.Tuple translated = translate(s[0], s[1]); | |
this.type = translated.type; | |
this.name = translated.name; | |
this.elementalName = translated.elemenalName; | |
} | |
public Arg(String typeName, String name){ | |
Arg.Tuple translated = translate(typeName, name); | |
this.type = translated.type; | |
this.name = translated.name; | |
this.elementalName = translated.elemenalName; | |
} | |
Arg.Tuple translate(String typeName, String argName){ | |
String name = argName.trim(); | |
String type = typeName.trim(); | |
String elementalName; | |
TypeInfo typeInfo = new TypeInfo(type); | |
// 予約語回避 | |
if(name.equals("function")){ | |
name = "func"; | |
} | |
elementalName = name; | |
if(typeInfo.isLengthVariable){ | |
name = "..." + name; | |
} | |
Arg.Tuple t = new Tuple(); | |
t.type = typeInfo; | |
t.name = name; | |
t.elemenalName = elementalName; | |
return t; | |
} | |
String getParamAnnotation(String comment){ | |
String annotation = "@param {" + type.getType() + "} " + name + " "; | |
if(type.isTranslated){ | |
annotation += "(Original defined as " + type.originalType + ") "; | |
} | |
annotation += (comment == null ? "" : comment); | |
return annotation; | |
} | |
String getTypeAnnotation(String comment){ | |
String annotation = "@type {" + type.getType() + "} " + name + " "; | |
if(type.isTranslated){ | |
annotation += "(Original defined as " + type.originalType + ") "; | |
} | |
annotation += (comment == null ? "" : comment); | |
return annotation; | |
} | |
String getArgumentDefinition(){ | |
return name + ":" + type.getType(); | |
} | |
} | |
class Pro { | |
public enum PARSE_CONTEXT { | |
HEADER, | |
CLASS, | |
PROP, | |
PROTO, | |
OTHER | |
} | |
public static final Pattern DEF_COMMENT_PAT = Pattern.compile("//>([^;]+);"); | |
public static final Pattern DECLARATION_PAT = Pattern.compile("[a-zA-Z0-9_]+\\s*:\\s*[a-zA-Z0-9\"(){}._,-]+?"); | |
public static final Pattern PARAMETER_BLOCK_PAT = Pattern.compile("\\((.*)\\)"); | |
public String declare; | |
public List<String> comments = new ArrayList<String>(); | |
PARSE_CONTEXT context; | |
TypeDef enclosure; | |
public Pro(PARSE_CONTEXT context){ | |
this.context = context; | |
} | |
static String strip(String c){ | |
String d = c.replace("//>", ""); | |
return d.substring(0, d.indexOf(";")).trim(); | |
} | |
static String getType(String line){ | |
return strip(line).split("\\s")[1]; | |
} | |
static String getMethodSignature(String line){ | |
String[] arr = strip(line).split("\\s"); | |
return String.join(" ", Arrays.copyOfRange(arr, 2, arr.length )); | |
} | |
static String getConstructorSignature(String line){ | |
String[] arr = strip(line).split("\\s"); | |
return String.join(" ", Arrays.copyOfRange(arr, 1, arr.length )); | |
} | |
static List<Arg> getParameters(String line){ | |
Matcher m = PARAMETER_BLOCK_PAT.matcher(strip(line)); | |
String comment = line.substring(line.indexOf(";") + 1); | |
if(m.find()){ | |
String matched = m.group(1); | |
if(matched.trim().isEmpty()){ | |
return Collections.emptyList(); | |
} | |
List<Arg> candidate = Arrays.stream(matched.split(",")).map(Arg::new).collect(Collectors.toList()); | |
Set<String> memo = new HashSet<>(); | |
return candidate.stream().map(arg -> { | |
if(memo.contains(arg.getElementalName())){ | |
String n = arg.getElementalName(); | |
for(int i=2;i<10;i++){ | |
String candidateName = n + String.valueOf(i); | |
if(!memo.contains(candidateName)){ | |
arg.setElementalName(candidateName); | |
memo.add(candidateName); | |
break; | |
} | |
} | |
}else{ | |
memo.add(arg.getElementalName()); | |
} | |
return arg; | |
}).collect(Collectors.toList()); | |
}else{ | |
return Collections.emptyList(); | |
} | |
} | |
public boolean addLine(String line){ | |
if(line.trim().startsWith("//@GENERATE ERROR:")){ | |
return false; | |
} | |
if(DEF_COMMENT_PAT.matcher(line.trim()).find()){ | |
comments.add(line); | |
return false; | |
} else if (DECLARATION_PAT.matcher(line.trim()).find()) { | |
declare = line; | |
return true; | |
} | |
return false; | |
} | |
public void setEnclosure(TypeDef enclosure){ | |
this.enclosure = enclosure; | |
} | |
public void output(Writer writer) throws IOException { | |
String declaredName = declare.substring(0, declare.indexOf(":")).trim(); | |
for (String c : comments) { | |
String realComment = c.substring(c.indexOf(';') + 1); | |
if (c.contains(declaredName)) { | |
List<Arg> args = getParameters(c); | |
if (c.contains("constructs")) { | |
// コンストラクタ | |
// コメントブロック | |
writer.write(" /** \n"); | |
writer.write(" * " + realComment + "\n"); | |
for (Arg arg : args) { | |
writer.write(" * " + arg.getParamAnnotation("") + "\n"); | |
} | |
writer.write(" */ \n"); | |
// 定義 | |
writer.write(" constructor("); | |
writer.write(args.stream().map(Arg::getArgumentDefinition).collect(Collectors.joining(" , "))); | |
writer.write(");\n\n"); | |
} else { | |
Arg method = new Arg(getType(c), declaredName); | |
// おそらく関数 | |
// コメントブロック | |
writer.write(" /** \n"); | |
writer.write(" * " + realComment + "\n"); | |
for (Arg arg : args) { | |
writer.write(" * " + arg.getParamAnnotation("") + "\n"); | |
} | |
writer.write(" * @returns {" + method.type.getType() + "} \n"); | |
writer.write(" */ \n"); | |
// 定義 | |
defOverrides(writer, declaredName, args, method); | |
writer.write("\n"); | |
} | |
} else { | |
// おそらくプロパティ | |
// コメント行 | |
Arg a = new Arg(getType(c), declaredName); | |
writer.write(" /** " + a.getTypeAnnotation(realComment) + "*/\n"); | |
// 定義 | |
writer.write(" "); | |
if (context == PARSE_CONTEXT.PROP) { | |
writer.write("static "); | |
} | |
writer.write(declaredName); | |
writer.write(" : "); | |
writer.write(a.type.type); | |
writer.write(";\n\n"); | |
} | |
} | |
} | |
void defOverrides(Writer writer, String declaredName, List<Arg> args, Arg method) throws IOException { | |
String[] omittableParams; | |
if(TypeInfo.omittableParameterDefs.containsKey(enclosure.getName() + "#" + declaredName)){ | |
omittableParams = TypeInfo.omittableParameterDefs.get(enclosure.getName() + "#" + declaredName); | |
}else{ | |
omittableParams = new String[]{}; | |
} | |
for (int i = 0; i <= omittableParams.length; i++) { | |
final String[] omittableSubArray = Arrays.copyOfRange(omittableParams, omittableParams.length -i, omittableParams.length); | |
List<Arg> subArgs = args.stream().filter( arg -> String.join(" " , omittableSubArray).indexOf(arg.name) < 0 ).collect(Collectors.toList()); | |
writeFuncDeclare(writer, declaredName, subArgs, method); | |
} | |
} | |
void writeFuncDeclare(Writer writer, String declaredName, List<Arg> args, Arg func) throws IOException { | |
writer.write(" "); | |
if (context == PARSE_CONTEXT.PROP) { | |
writer.write("static "); | |
} else if ( enclosure.isGlobalFunction() ){ | |
writer.write("declare function "); | |
} | |
writer.write(declaredName + "("); | |
writer.write(args.stream().map(Arg::getArgumentDefinition).collect(Collectors.joining(", "))); | |
writer.write("):"); | |
writer.write(func.type.getType()); | |
writer.write(";\n"); | |
} | |
public void clear(){ | |
declare = null; | |
comments = new ArrayList<>(); | |
} | |
} | |
class TypeDef { | |
public static final Pattern CLASS_NAME_PAT = Pattern.compile("vjo.ctype\\('([^']+)'\\)"); | |
String getGroup1(String target){ | |
Matcher m = CLASS_NAME_PAT.matcher(target); | |
if(m.find()){ | |
return m.group(1); | |
} | |
return null; | |
} | |
List<Pro> pros = new ArrayList<>(); | |
String [] enclosure = new String[]{}; | |
List<String> headerComments = new ArrayList<>(); | |
public void setEnclosure(String line) { | |
String className = getGroup1(line); | |
if(className == null){ | |
throw new RuntimeException("No enclosure :" + line) ; | |
} | |
enclosure = className.split("\\."); | |
} | |
public void add(Pro pro){ | |
pros.add(pro); | |
pro.setEnclosure(this); | |
} | |
public boolean isGlobalFunction(){ | |
String className = enclosure[enclosure.length -1]; | |
return className.trim().equals("GlobalFunction"); | |
} | |
public void output(Writer w) throws IOException { | |
if(!isGlobalFunction()){ | |
outputEnclosureOpen(w); | |
} | |
for(Pro p : pros){ | |
p.output(w); | |
} | |
if(!isGlobalFunction()){ | |
outputEnclosureClose(w); | |
} | |
} | |
void outputEnclosureOpen(Writer w) throws IOException { | |
w.write("/**\n"); | |
for (String line : headerComments) { | |
w.write(" * "); | |
w.write(line); | |
w.write("\n"); | |
} | |
w.write(" */\n"); | |
String className = enclosure[enclosure.length -1]; | |
if(TypeInfo.typemap.containsKey(className)){ | |
className = TypeInfo.typemap.get(className); | |
} | |
if(enclosure.length == 1){ | |
w.write("declare class " + className + " {\n"); | |
}else { | |
String namespace = String.join( | |
".", | |
Arrays.copyOfRange(enclosure, 0, enclosure.length - 1)); | |
if(enclosure[0].equals("Module")){ | |
// Module.string, Module.number が 予約語でクラス名にできないため namespace にして回避 | |
// Module を別途クラスとして固定的に出力 | |
w.write("declare class " + namespace + "_" + className + " {\n"); | |
}else{ | |
w.write("declare namespace " + namespace + " {\n"); | |
w.write("export class " + className + " {\n"); | |
} | |
} | |
} | |
public String getName(){ | |
return String.join(".", enclosure); | |
} | |
void outputEnclosureClose(Writer w) throws IOException { | |
w.write("}\n"); | |
if(enclosure.length > 1 && !enclosure[0].equals("Module")){ | |
w.write("}\n"); | |
} | |
w.write("\n"); | |
} | |
static Pattern COMMENT_START = Pattern.compile("^(\\s*/\\*\\*+)"); | |
static Pattern COMMENT_CONTINUE = Pattern.compile("^(\\s*\\*\\s*)"); | |
static Pattern COMMENT_END = Pattern.compile("(\\**/\\s*)$"); | |
public void addHeaderComment(String line) { | |
String l = line.trim(); | |
Matcher m = COMMENT_START.matcher(l); | |
if(m.find()){ | |
l = l.replace(m.group(1), ""); | |
} | |
m = COMMENT_CONTINUE.matcher(l); | |
if(m.find()){ | |
l = l.replace(m.group(1), ""); | |
} | |
m = COMMENT_END.matcher(l); | |
if(m.find()){ | |
l = l.replace(m.group(1), ""); | |
} | |
headerComments.add(l); | |
} | |
} | |
public class Main { | |
public static final String BASE_PATH = "C:\\path-to-the\\workspace\\.metadata\\.plugins\\org.eclipse.dltk.mod.core\\WorkspaceJsLib"; | |
public static final String TO_PATH = "C:\\output-to\\index.d.ts"; | |
public static void main(String[] args) throws IOException { | |
try(BufferedWriter w = new BufferedWriter(new FileWriter(TO_PATH))){ | |
Arrays.stream(Objects.requireNonNull(Paths.get(BASE_PATH).toFile().listFiles())) | |
.sequential() //.filter( f -> f.getName().equals("IMMCurrencyManager.js")) | |
.forEach(file -> { | |
try(BufferedReader br = new BufferedReader(new FileReader(file))) { | |
TypeDef typeDef = new TypeDef(); | |
String line; | |
Pro.PARSE_CONTEXT context = Pro.PARSE_CONTEXT.HEADER; | |
Pro pro = new Pro(Pro.PARSE_CONTEXT.HEADER); | |
while((line = br.readLine()) != null){ | |
if(line.trim().isEmpty()){ | |
continue; | |
} | |
if(line.startsWith("vjo.ctype")){ | |
context = Pro.PARSE_CONTEXT.CLASS; | |
typeDef.setEnclosure(line); | |
continue; | |
} | |
if(context == Pro.PARSE_CONTEXT.HEADER){ | |
typeDef.addHeaderComment(line); | |
continue; | |
} | |
if(line.startsWith(".props({")){ | |
context = Pro.PARSE_CONTEXT.PROP; | |
pro = new Pro(context); | |
}else if(line.startsWith(".protos({")){ | |
context = Pro.PARSE_CONTEXT.PROTO; | |
pro = new Pro(context); | |
} | |
if(pro.addLine(line)){ | |
typeDef.add(pro); | |
pro = new Pro(context); | |
} | |
} | |
typeDef.output(w); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
}); | |
try { | |
w.write("declare class Module {\n"); | |
w.write(" static alert:Module_alert;\n"); | |
w.write(" static array:Module_array;\n"); | |
w.write(" static calendar:Module_calendar;\n"); | |
w.write(" static data:Module_data;\n"); | |
w.write(" static date:Module_date;\n"); | |
w.write(" static download:Module_download;\n"); | |
w.write(" static ldap:Module_ldap;\n"); | |
w.write(" static number:Module_number;\n"); | |
w.write(" static string:Module_string;\n"); | |
w.write("}\n"); | |
w.write("\n"); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment