Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 44 additions & 31 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class RequestBody(BaseModel):
input: Optional[str] = None
compileOptions: Optional[str] = None
extraExecutables: Optional[List[str]] = None
language: Optional[str] = "c"

class ScriptOutput(BaseModel):
"""Model for script output."""
Expand Down Expand Up @@ -187,50 +188,60 @@ def run_svf_tools(ll_file: str, extra_executables: List[str]) -> tuple[str, str]

return "\n".join(output_lines), "\n".join(errors)

def compile_c_to_llvm(c_code: str, compile_options: str = "") -> tuple[bool, str, str]:
def compile_code_to_llvm(source_code: str, compile_options: str = "", language: str = "c"):
"""
Compile C code to LLVM IR
Returns: (success, output, error)
Compile C or C++ source to LLVM IR.
Returns: (success, llvm_content, stderr)
"""
output_lines: List[str] = []
try:
# Write C code to temporary file
with tempfile.NamedTemporaryFile(mode='w', suffix='.c', delete=False) as f:
f.write(c_code)
c_file = f.name
lang = language.lower()
if lang == "cpp" or lang == "c++":
suffix = ".cpp"
compiler = "clang++"
default_std = "-std=c++17"
else:
suffix = ".c"
compiler = "clang"
default_std = "-std=c11"

# Output LLVM file
ll_file = c_file.replace('.c', '.ll')
# Write source to temporary file
with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, delete=False) as f:
f.write(source_code)
src_file = f.name

# Use system clang in Docker container, or LLVM 16 on macOS
clang_path = "/opt/homebrew/opt/llvm@16/bin/clang"
ll_file = src_file.rsplit(".", 1)[0] + ".ll"

# Check if LLVM 16 is available, otherwise fall back to system clang
if not os.path.exists(clang_path):
clang_path = "clang" # Use system clang
# Build compile args safely
opts = shlex.split(compile_options or "")
# add default -std if user didn't provide one
if not any(o.startswith("-std") for o in opts):
opts.insert(0, default_std)

# Compile command
cmd = f"{clang_path} {compile_options} -emit-llvm -S {c_file} -o {ll_file}"
if lang == "cpp" or lang == "c++":
opts.append("-fno-inline")

# Run compilation
result = subprocess.run(cmd, shell=True, capture_output=True, text=True,
check=False)
args = [compiler, *opts, "-emit-llvm", "-S", src_file, "-o", ll_file]
print(f"args are {args}")

result = subprocess.run(args, capture_output=True, text=True, check=False)
if result.returncode != 0:
return False, result.stdout, result.stderr
# Return stdout and stderr for diagnostics
return False, result.stdout or "", result.stderr or ""

# Read LLVM IR
with open(ll_file, 'r', encoding='utf-8') as f:
with open(ll_file, "r", encoding="utf-8") as f:
llvm_content = f.read()

# Clean up temporary files
os.unlink(c_file)
os.unlink(ll_file)
return True, llvm_content, "\n".join(output_lines)
# Cleanup
try:
os.unlink(src_file)
os.unlink(ll_file)
except OSError:
pass

return True, llvm_content, ""
except (OSError, IOError) as e:
output_lines.append(f"Compilation error: {str(e)}")
return False, "\n".join(output_lines), str(e)
return False, "", str(e)


@app.post("/api/controller")
async def analyse_code(request_body: RequestBody) -> SvfResult:
Expand All @@ -250,8 +261,10 @@ async def analyse_code(request_body: RequestBody) -> SvfResult:
"Name": "Input Error"
}
)
success, llvm_ir, compile_output = compile_c_to_llvm(
request_body.input, request_body.compileOptions or ""

request_lang = (request_body.language or "c").lower()
success, llvm_ir, compile_output = compile_code_to_llvm(
request_body.input, request_body.compileOptions or "", request_lang
)

if not success:
Expand Down
8 changes: 7 additions & 1 deletion src/six/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
const submitCodeFetch = async (code: string, compileOptions: string, executables: string[]) => {
const submitCodeFetch = async (
code: string,
compileOptions: string,
executables: string[],
language: string
) => {
// Configure API URL based on environment
const isDevelopment = process.env.NODE_ENV !== 'production';
const baseUrl = isDevelopment ? 'http://localhost:8080' : 'https://api-broken-moon.fly.dev';
Expand All @@ -9,6 +14,7 @@ const submitCodeFetch = async (code: string, compileOptions: string, executables
input: code,
compileOptions: compileOptions,
extraExecutables: executables,
language: language,
};

// return sampleResponse3;
Expand Down
57 changes: 44 additions & 13 deletions src/six/components/codeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import * as monaco from 'monaco-editor';
import './styles.css';
import FontSizeMenu from '../fontSizeMenu/FontSizeMenu';
import LanguageSelector from '../languageSelector/LanguageSelector';

interface CodeEditorProps {
code: string;
Expand All @@ -14,6 +15,8 @@
setPassedPrompt: (prompt: string) => void;
externalFontSize?: number;
onExternalFontSizeChange?: (size: number) => void;
lang: string;
setLang: (lang: string) => void;
}

const CodeEditor: React.FC<CodeEditorProps> = ({
Expand All @@ -26,8 +29,12 @@
setPassedPrompt,
externalFontSize,
onExternalFontSizeChange,
lang,
setLang,
}) => {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const monacoInstance = useMonaco();

const [fontSize, setFontSize] = useState(16);
const [useLocalFontSize, setUseLocalFontSize] = useState(false);
useState<monaco.editor.IEditorDecorationsCollection | null>(null);
Expand All @@ -40,16 +47,16 @@

const handleEditorDidMount: OnMount = (editor, monaco) => {
editorRef.current = editor;
const model = monaco.editor.createModel(code, 'c', monaco.Uri.parse('inmemory://test_script'));
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse('inmemory://test_script'));
editor.setModel(model);
decorationsRef.current = editor.createDecorationsCollection();
editor.updateOptions({
fontSize: useLocalFontSize ? fontSize : externalFontSize ?? fontSize,
renderValidationDecorations: 'on',
});
monaco.languages.register({ id: 'c' });
monaco.languages.register({ id: lang });

monaco.languages.setLanguageConfiguration('c', {});
monaco.languages.setLanguageConfiguration(lang, {});

editor.onDidChangeModelContent(() => {
const value = editor.getValue();
Expand All @@ -62,7 +69,7 @@
setCurrCodeLineNum(lineNum);
});
const markers = applyMarkers();
monaco.editor.setModelMarkers(model, 'c', markers);
monaco.editor.setModelMarkers(model, lang, markers);

// Register the ask code gpt command
monaco.editor.registerCommand('askCodeGPTCommand', (_accessor, ...args) => {
Expand All @@ -76,7 +83,7 @@
codeActionProviderRef.current.dispose();
}

codeActionProviderRef.current = monaco.languages.registerCodeActionProvider('c', {
codeActionProviderRef.current = monaco.languages.registerCodeActionProvider(lang, {
provideCodeActions: (model, range) => {
const markers = monaco.editor.getModelMarkers({ resource: model.uri });
const relevantMarker = markers.find(
Expand Down Expand Up @@ -164,16 +171,16 @@
};
// Adds the red squigly line on the code editor indicating an error or warning to line of code
const applyMarkers = useCallback((): monaco.editor.IMarkerData[] => {
monaco.languages.register({ id: 'c' });
monaco.languages.register({ id: lang });

monaco.languages.setLanguageConfiguration('c', {
monaco.languages.setLanguageConfiguration(lang, {
// Ensure C language supports diagnostics markers
});
if (editorRef.current && codeError.length !== 0) {
const model = editorRef.current.getModel();
// Clear any previous markers
if (model) {
monaco.editor.setModelMarkers(model, 'c', []);
monaco.editor.setModelMarkers(model, lang, []);
}

const lnRegexcl = /ln:\s*(\d+)\s*cl:\s*(\d+)/;
Expand Down Expand Up @@ -211,7 +218,7 @@
const safeEndColumn = lineLength + 1;
markers.push({
code: undefined,
source: 'c',
source: lang,
startLineNumber: lnNum,
startColumn: safeStartColumn,
endLineNumber: lnNum,
Expand All @@ -224,19 +231,40 @@
return markers;
}
return [];
}, [codeError]);

Check warning on line 234 in src/six/components/codeEditor/CodeEditor.tsx

View workflow job for this annotation

GitHub Actions / lint-format

React Hook useCallback has a missing dependency: 'lang'. Either include it or remove the dependency array

useEffect(() => {
if (editorRef.current) {
const model = editorRef.current.getModel();
if (model) {
const markers = applyMarkers();
monaco.editor.setModelMarkers(model, 'c', markers);
monaco.editor.setModelMarkers(model, lang, markers);
setEditorKey((prevKey) => prevKey + 1);
}
}
}, [codeError, applyMarkers]);

Check warning on line 245 in src/six/components/codeEditor/CodeEditor.tsx

View workflow job for this annotation

GitHub Actions / lint-format

React Hook useEffect has a missing dependency: 'lang'. Either include it or remove the dependency array

useEffect(() => {
if (!monacoInstance || !editorRef.current) return;
const model = editorRef.current.getModel();
if (!model) return;

// change model language (this updates syntax highlighting)
monacoInstance.editor.setModelLanguage(model, lang);

// re-register / update any language-specific config you rely on
try {
monacoInstance.languages.register({ id: lang });
monacoInstance.languages.setLanguageConfiguration(lang, {});
} catch {
// ignore if registration fails (some languages are built-in)
}

// If you have code-action providers or diagnostics registered per-language,
// you should re-register/refresh them here (dispose old providers if needed).
// e.g. dispose existing provider refs and create new ones bound to `monacoLang`.
}, [lang, monacoInstance]);

// Used to detect for any changes in code
// This is needed for when lz string compression calls setcode
useEffect(() => {
Expand Down Expand Up @@ -390,7 +418,6 @@
}, []);

const [theme, setTheme] = useState<string>('websvf-light'); // Monaco theme name
const monacoInstance = useMonaco();

// Create a Monaco theme that follows CSS variables
const normalizeHex = (value: string, fallback: string) => {
Expand Down Expand Up @@ -471,7 +498,10 @@
return (
<>
<div>
<div id="codeEditor-fontSize-container">
<div
id="codeEditor-fontSize-container"
style={{ display: 'flex', alignItems: 'center', gap: 8 }}
>
<FontSizeMenu
fontSize={effectiveFontSize}
setFontSize={(size: number) => {
Expand All @@ -482,11 +512,12 @@
if (onExternalFontSizeChange) onExternalFontSizeChange(size);
}}
/>
<LanguageSelector lang={lang} setLang={setLang} />
</div>
<Editor
key={editorKey}
height="90vh"
language="c"
language={lang}
theme={theme}
value={code}
onMount={handleEditorDidMount}
Expand Down
84 changes: 84 additions & 0 deletions src/six/components/languageSelector/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import Select, { StylesConfig, GroupBase } from 'react-select';

interface LanguageOption {
value: string;
label: string;
}

const options: LanguageOption[] = [
{ value: 'c', label: 'C' },
{ value: 'cpp', label: 'C++' },
];

const customStyles: StylesConfig<LanguageOption, false, GroupBase<LanguageOption>> = {
control: (provided, state) => ({
...provided,
minHeight: 28,
height: 28,
padding: '0 6px',
backgroundColor: 'var(--surface)',
borderColor: state.isFocused ? 'var(--primary)' : 'var(--border-color)',
boxShadow: 'none',
':hover': { borderColor: 'var(--primary)' },
fontSize: '0.875rem',
}),
valueContainer: (provided) => ({
...provided,
padding: '0 6px',
}),
indicatorsContainer: (provided) => ({
...provided,
height: 28,
}),
input: (provided) => ({
...provided,
margin: 0,
padding: 0,
}),
singleValue: (provided) => ({
...provided,
color: 'var(--text-color)',
margin: 0,
}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
menu: (provided) => ({
...provided,
backgroundColor: 'var(--surface)',
color: 'var(--text-color)',
border: '1px solid var(--border-color)',
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isSelected
? 'var(--primary)'
: state.isFocused
? 'var(--muted)'
: 'var(--surface)',
color: state.isSelected ? 'var(--primary-contrast)' : 'var(--text-color)',
}),
};

const LanguageSelector = ({ lang, setLang }: { lang: string; setLang: (lang: string) => void }) => {
const value = options.find((o) => o.value === lang) || options[0];

return (
<div className="language-selector-small" title="Select language" style={{ minWidth: 72 }}>
<Select<LanguageOption, false>
styles={customStyles}
options={options}
value={value}
onChange={(selected) => {
if (selected) setLang(selected.value);
}}
isSearchable={false}
isClearable={false}
menuPortalTarget={typeof document !== 'undefined' ? document.body : undefined}
menuPosition="fixed"
classNamePrefix="react-select"
/>
</div>
);
};

export default LanguageSelector;
Loading