diff --git a/api/app.py b/api/app.py index 04b3d516..f01f8c23 100644 --- a/api/app.py +++ b/api/app.py @@ -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.""" @@ -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: @@ -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: diff --git a/src/six/api.ts b/src/six/api.ts index 365e75a7..86185d58 100644 --- a/src/six/api.ts +++ b/src/six/api.ts @@ -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'; @@ -9,6 +14,7 @@ const submitCodeFetch = async (code: string, compileOptions: string, executables input: code, compileOptions: compileOptions, extraExecutables: executables, + language: language, }; // return sampleResponse3; diff --git a/src/six/components/codeEditor/CodeEditor.tsx b/src/six/components/codeEditor/CodeEditor.tsx index 9bde0748..57c6db9f 100644 --- a/src/six/components/codeEditor/CodeEditor.tsx +++ b/src/six/components/codeEditor/CodeEditor.tsx @@ -3,6 +3,7 @@ import Editor, { OnMount, useMonaco } from '@monaco-editor/react'; import * as monaco from 'monaco-editor'; import './styles.css'; import FontSizeMenu from '../fontSizeMenu/FontSizeMenu'; +import LanguageSelector from '../languageSelector/LanguageSelector'; interface CodeEditorProps { code: string; @@ -14,6 +15,8 @@ interface CodeEditorProps { setPassedPrompt: (prompt: string) => void; externalFontSize?: number; onExternalFontSizeChange?: (size: number) => void; + lang: string; + setLang: (lang: string) => void; } const CodeEditor: React.FC = ({ @@ -26,8 +29,12 @@ const CodeEditor: React.FC = ({ setPassedPrompt, externalFontSize, onExternalFontSizeChange, + lang, + setLang, }) => { const editorRef = useRef(null); + const monacoInstance = useMonaco(); + const [fontSize, setFontSize] = useState(16); const [useLocalFontSize, setUseLocalFontSize] = useState(false); useState(null); @@ -40,16 +47,16 @@ const CodeEditor: React.FC = ({ 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(); @@ -62,7 +69,7 @@ const CodeEditor: React.FC = ({ 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) => { @@ -76,7 +83,7 @@ const CodeEditor: React.FC = ({ 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( @@ -164,16 +171,16 @@ const CodeEditor: React.FC = ({ }; // 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+)/; @@ -211,7 +218,7 @@ const CodeEditor: React.FC = ({ const safeEndColumn = lineLength + 1; markers.push({ code: undefined, - source: 'c', + source: lang, startLineNumber: lnNum, startColumn: safeStartColumn, endLineNumber: lnNum, @@ -231,12 +238,33 @@ const CodeEditor: React.FC = ({ 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]); + 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(() => { @@ -390,7 +418,6 @@ const CodeEditor: React.FC = ({ }, []); const [theme, setTheme] = useState('websvf-light'); // Monaco theme name - const monacoInstance = useMonaco(); // Create a Monaco theme that follows CSS variables const normalizeHex = (value: string, fallback: string) => { @@ -471,7 +498,10 @@ const CodeEditor: React.FC = ({ return ( <>
-
+
{ @@ -482,11 +512,12 @@ const CodeEditor: React.FC = ({ if (onExternalFontSizeChange) onExternalFontSizeChange(size); }} /> +
> = { + 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 ( +
+ + 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" + /> +
+ ); +}; + +export default LanguageSelector; diff --git a/src/six/components/multiSession/sessionManager.ts b/src/six/components/multiSession/sessionManager.ts index fcdaae86..7606c1f8 100644 --- a/src/six/components/multiSession/sessionManager.ts +++ b/src/six/components/multiSession/sessionManager.ts @@ -1,5 +1,5 @@ -// SessionManager.ts export type OutputType = 'Graph' | 'CodeGPT' | 'LLVMIR' | 'Terminal Output' | 'Terminal'; +export type Language = 'c' | 'cpp'; export interface LabeledOption { value: string; @@ -21,6 +21,7 @@ export interface Session { currentOutput: OutputType; // Now using the defined type lineNumToHighlight: number[]; tabPositions: Record; + language: Language; } const SessionManager = { @@ -30,8 +31,12 @@ const SessionManager = { if (!raw) return []; try { const parsed = JSON.parse(raw); - // Ensure we always return an array - return Array.isArray(parsed) ? parsed : []; + // Ensure we always return an array and that each session has a language (migrate older sessions) + if (!Array.isArray(parsed)) return []; + return parsed.map((s: any) => ({ + ...s, + language: s.language || 'c', + })) as Session[]; } catch { // If parsing fails (corrupted storage), reset the key and return [] to avoid crashes localStorage.removeItem('websvf-sessions'); @@ -111,6 +116,7 @@ const SessionManager = { LLVMIR: 'main', Terminal: 'main', }, + language: 'c', }; sessions.unshift(newSession); diff --git a/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.css b/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.css index 9986e5f7..4fe54b77 100644 --- a/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.css +++ b/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.css @@ -170,6 +170,44 @@ transition: opacity 0.2s; } +.session-language { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 50%; + padding: 0; + box-sizing: border-box; + font-size: 0.75rem; + line-height: 1; + cursor: default; + + /* Primary blue background and contrasting text (fallback values included) */ + background: var(--primary, #1976d2); + color: var(--primary-contrast, #ffffff); + border: 1px solid rgba(0, 0, 0, 0.08); +} + +/* Slight spacing between language badge and subsequent icons */ +.session-language + .edit-icon, +.session-language + span { + margin-left: 4px; +} + +/* Dark-mode adjustment: use a subtler filled background and lighter border */ +[data-theme='dark'] .session-language { + background: color-mix(in srgb, var(--primary, #1976d2) 18%, transparent); + color: var(--primary, #1976d2); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +/* Hover/focus states similar to the + button */ +.session-language:hover, +.session-language:focus { + filter: brightness(0.95); +} + .session-item:hover .session-actions { opacity: 1; } diff --git a/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.tsx b/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.tsx index f16ff94e..b52f82a2 100644 --- a/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.tsx +++ b/src/six/components/multiSession/sessionsSidebar/sessionsSidebar.tsx @@ -125,6 +125,13 @@ const SessionsSidebar: React.FC = ({
+
+ {session.language === 'cpp' ? 'C++' : 'C'} +
+const DEFAULT_C_CODE = `#include #include typedef struct { @@ -70,6 +72,29 @@ int main() { return 0; }`; +const DEFAULT_CPP_CODE = `#include +#include +using namespace std; +class IntArray { + vector data; +public: + IntArray(int size) : data(size) {} + void set(int index, int value) { data[index] = value; } + int get(int index) const { return data[index]; } + int size() const { return data.size(); } +}; +int main() { + IntArray arr(10); + for (int i = 0; i < arr.size(); i++) { + arr.set(i, i * 2); + } + for (int i = 0; i < arr.size(); i++) { + cout << arr.get(i) << " "; + } + cout << endl; + return 0; +}`; + const compileOptions = [ { value: '-g', label: '-g' }, { value: '-c', label: '-c' }, @@ -97,6 +122,17 @@ function GraphsPage() { // Add session management state const [sessions, setSessions] = useState([]); const [currentSessionId, setCurrentSessionId] = useState(null); + + const handleUpdateSessionLanguage = (sessionId: string, language: 'c' | 'cpp') => { + const updated = SessionManager.updateSession(sessionId, { language }); + if (updated) { + setSessions((prev) => prev.map((s) => (s.id === sessionId ? { ...s, language } : s))); + if (sessionId === currentSessionId) { + setLang(language); + } + } + }; + const [isSidebarOpen, setIsSidebarOpen] = useState(true); const { sessionId: routeSessionId } = useParams(); const navigate = useNavigate(); @@ -124,7 +160,8 @@ function GraphsPage() { const [lineNumDetails, setLineNumDetails] = useState<{ [key: string]: { nodeOrllvm: string[]; colour: string }; }>({}); - const [code, setCode] = useState(DEFAULT_CODE); + const [lang, setLang] = useState(DEFAULT_LANG); + const [code, setCode] = useState(DEFAULT_C_CODE); const [lineNumToHighlight, setlineNumToHighlight] = useState>(new Set()); const setlineNumToHighlightGuard = useCallback((next: Set) => { setlineNumToHighlight((prev) => { @@ -210,7 +247,15 @@ function GraphsPage() { // Session Management Functions const loadSession = useCallback((session: Session) => { - setCode(session.code && session.code.trim() !== '' ? session.code : DEFAULT_CODE); + setCode( + session.code && session.code.trim() !== '' + ? session.code + : lang == 'c' + ? DEFAULT_C_CODE + : DEFAULT_CPP_CODE + ); + const sessionLang = session.language || DEFAULT_LANG; + setLang(sessionLang); setSelectedCompileOptions(session.selectedCompileOptions); setSelectedExecutableOptions(session.selectedExecutableOptions || []); setLineNumDetails(session.lineNumDetails); @@ -538,7 +583,8 @@ function GraphsPage() { const response = await submitCodeFetch( code, selectedCompileOptionString, - selectedExecutableOptionsList + selectedExecutableOptionsList, + lang ); if (!response) { @@ -1035,6 +1081,12 @@ function GraphsPage() { setPassedPrompt={setPassedPrompt} externalFontSize={codeFontSize} onExternalFontSizeChange={(size) => setCodeFontSize(size)} + lang={lang} + setLang={(newLang) => { + setLang(newLang); + if (currentSessionId) + handleUpdateSessionLanguage(currentSessionId, newLang as 'c' | 'cpp'); + }} />
{/* Resizer element */}