Skip to content

Instantly share code, notes, and snippets.

@carrotsword
Created June 20, 2023 05:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carrotsword/de3a254e6ace5bd41b37952800d94535 to your computer and use it in GitHub Desktop.
Save carrotsword/de3a254e6ace5bd41b37952800d94535 to your computer and use it in GitHub Desktop.
vjet2dts : vjet用のAPI定義から .d.ts を生成する。
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